Skip to content

Commit 178ff52

Browse files
authored
Merge pull request #589 from allevato/optional-someany
Parenthesize `some/any` types when converting `Optional` to `?`.
2 parents c5183b1 + 711a7a0 commit 178ff52

File tree

2 files changed

+85
-31
lines changed

2 files changed

+85
-31
lines changed

Sources/SwiftFormatRules/UseShorthandTypeNames.swift

Lines changed: 50 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -236,20 +236,16 @@ public final class UseShorthandTypeNames: SyntaxFormatRule {
236236
) -> TypeSyntax {
237237
var wrappedType = wrappedType
238238

239-
if let functionType = wrappedType.as(FunctionTypeSyntax.self) {
240-
// Function types must be wrapped as a tuple before using shorthand optional syntax,
241-
// otherwise the "?" applies to the return type instead of the function type. Attach the
242-
// leading trivia to the left-paren that we're adding in this case.
243-
let tupleTypeElement = TupleTypeElementSyntax(
244-
inoutKeyword: nil, firstName: nil, secondName: nil, colon: nil, type: TypeSyntax(functionType),
245-
ellipsis: nil, trailingComma: nil)
246-
let tupleTypeElementList = TupleTypeElementListSyntax([tupleTypeElement])
247-
let tupleType = TupleTypeSyntax(
248-
leftParen: TokenSyntax.leftParenToken(leadingTrivia: leadingTrivia),
249-
elements: tupleTypeElementList,
250-
rightParen: TokenSyntax.rightParenToken())
251-
wrappedType = TypeSyntax(tupleType)
252-
} else {
239+
// Function types and some-or-any types must be wrapped in parentheses before using shorthand
240+
// optional syntax, otherwise the "?" will bind incorrectly (in the function case it binds to
241+
// only the result, and in the some-or-any case it only binds to the child protocol). Attach the
242+
// leading trivia to the left-paren that we're adding in these cases.
243+
switch Syntax(wrappedType).as(SyntaxEnum.self) {
244+
case .functionType(let functionType):
245+
wrappedType = parenthesizedType(functionType, leadingTrivia: leadingTrivia)
246+
case .someOrAnyType(let someOrAnyType):
247+
wrappedType = parenthesizedType(someOrAnyType, leadingTrivia: leadingTrivia)
248+
default:
253249
// Otherwise, the argument type can safely become an optional by simply appending a "?", but
254250
// we need to transfer the leading trivia from the original `Optional` token over to it.
255251
// By doing so, something like `/* comment */ Optional<Foo>` will become `/* comment */ Foo?`
@@ -316,38 +312,58 @@ public final class UseShorthandTypeNames: SyntaxFormatRule {
316312
/// key type or value type does not have a valid expression representation.
317313
private func makeOptionalTypeExpression(
318314
wrapping wrappedType: TypeSyntax,
319-
leadingTrivia: Trivia? = nil,
315+
leadingTrivia: Trivia = [],
320316
questionMark: TokenSyntax
321317
) -> OptionalChainingExprSyntax? {
322318
guard var wrappedTypeExpr = expressionRepresentation(of: wrappedType) else { return nil }
323319

324-
if wrappedType.is(FunctionTypeSyntax.self) {
325-
// Function types must be wrapped as a tuple before using shorthand optional syntax,
326-
// otherwise the "?" applies to the return type instead of the function type. Attach the
327-
// leading trivia to the left-paren that we're adding in this case.
328-
let tupleExprElement =
329-
LabeledExprSyntax(
330-
label: nil, colon: nil, expression: wrappedTypeExpr, trailingComma: nil)
331-
let tupleExprElementList = LabeledExprListSyntax([tupleExprElement])
332-
let tupleExpr = TupleExprSyntax(
333-
leftParen: TokenSyntax.leftParenToken(leadingTrivia: leadingTrivia ?? []),
334-
elements: tupleExprElementList,
335-
rightParen: TokenSyntax.rightParenToken())
336-
wrappedTypeExpr = ExprSyntax(tupleExpr)
337-
} else {
320+
// Function types and some-or-any types must be wrapped in parentheses before using shorthand
321+
// optional syntax, otherwise the "?" will bind incorrectly (in the function case it binds to
322+
// only the result, and in the some-or-any case it only binds to the child protocol). Attach the
323+
// leading trivia to the left-paren that we're adding in these cases.
324+
switch Syntax(wrappedType).as(SyntaxEnum.self) {
325+
case .functionType, .someOrAnyType:
326+
wrappedTypeExpr = parenthesizedExpr(wrappedTypeExpr, leadingTrivia: leadingTrivia)
327+
default:
338328
// Otherwise, the argument type can safely become an optional by simply appending a "?". If
339329
// we were given leading trivia from another node (for example, from `Optional` when
340330
// converting a long-form to short-form), we need to transfer it over. By doing so, something
341331
// like `/* comment */ Optional<Foo>` will become `/* comment */ Foo?` instead of discarding
342332
// the comment.
343-
wrappedTypeExpr.leadingTrivia = leadingTrivia ?? []
333+
wrappedTypeExpr.leadingTrivia = leadingTrivia
344334
}
345335

346336
return OptionalChainingExprSyntax(
347337
expression: wrappedTypeExpr,
348338
questionMark: questionMark)
349339
}
350340

341+
/// Returns the given type wrapped in parentheses.
342+
private func parenthesizedType<TypeNode: TypeSyntaxProtocol>(
343+
_ typeToWrap: TypeNode,
344+
leadingTrivia: Trivia
345+
) -> TypeSyntax {
346+
let tupleTypeElement = TupleTypeElementSyntax(type: TypeSyntax(typeToWrap))
347+
let tupleType = TupleTypeSyntax(
348+
leftParen: .leftParenToken(leadingTrivia: leadingTrivia),
349+
elements: TupleTypeElementListSyntax([tupleTypeElement]),
350+
rightParen: .rightParenToken())
351+
return TypeSyntax(tupleType)
352+
}
353+
354+
/// Returns the given expression wrapped in parentheses.
355+
private func parenthesizedExpr<ExprNode: ExprSyntaxProtocol>(
356+
_ exprToWrap: ExprNode,
357+
leadingTrivia: Trivia
358+
) -> ExprSyntax {
359+
let tupleExprElement = LabeledExprSyntax(expression: exprToWrap)
360+
let tupleExpr = TupleExprSyntax(
361+
leftParen: .leftParenToken(leadingTrivia: leadingTrivia),
362+
elements: LabeledExprListSyntax([tupleExprElement]),
363+
rightParen: .rightParenToken())
364+
return ExprSyntax(tupleExpr)
365+
}
366+
351367
/// Returns an `ExprSyntax` that is syntactically equivalent to the given `TypeSyntax`, or nil if
352368
/// it wouldn't be valid.
353369
///
@@ -404,7 +420,7 @@ public final class UseShorthandTypeNames: SyntaxFormatRule {
404420
case .optionalType(let optionalType):
405421
let result = makeOptionalTypeExpression(
406422
wrapping: optionalType.wrappedType,
407-
leadingTrivia: optionalType.firstToken(viewMode: .sourceAccurate)?.leadingTrivia,
423+
leadingTrivia: optionalType.firstToken(viewMode: .sourceAccurate)?.leadingTrivia ?? [],
408424
questionMark: optionalType.questionMark)
409425
return ExprSyntax(result)
410426

@@ -427,6 +443,9 @@ public final class UseShorthandTypeNames: SyntaxFormatRule {
427443
rightParen: tupleType.rightParen)
428444
return ExprSyntax(result)
429445

446+
case .someOrAnyType(let someOrAnyType):
447+
return ExprSyntax(TypeExprSyntax(type: someOrAnyType))
448+
430449
default:
431450
return nil
432451
}

Tests/SwiftFormatRulesTests/UseShorthandTypeNamesTests.swift

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -413,4 +413,39 @@ final class UseShorthandTypeNamesTests: LintOrFormatRuleTestCase {
413413
var e: [String: Int?]
414414
""")
415415
}
416+
417+
func testSomeAnyTypesInOptionalsAreParenthesized() {
418+
// If we need to insert parentheses, verify that we do, but also verify that we don't insert
419+
// them unnecessarily.
420+
XCTAssertFormatting(
421+
UseShorthandTypeNames.self,
422+
input:
423+
"""
424+
func f(_: Optional<some P>) {}
425+
func g(_: Optional<any P>) {}
426+
var x: Optional<some P> = S()
427+
var y: Optional<any P> = S()
428+
var z = [Optional<any P>]([S()])
429+
430+
func f(_: Optional<(some P)>) {}
431+
func g(_: Optional<(any P)>) {}
432+
var x: Optional<(some P)> = S()
433+
var y: Optional<(any P)> = S()
434+
var z = [Optional<(any P)>]([S()])
435+
""",
436+
expected:
437+
"""
438+
func f(_: (some P)?) {}
439+
func g(_: (any P)?) {}
440+
var x: (some P)? = S()
441+
var y: (any P)? = S()
442+
var z = [(any P)?]([S()])
443+
444+
func f(_: (some P)?) {}
445+
func g(_: (any P)?) {}
446+
var x: (some P)? = S()
447+
var y: (any P)? = S()
448+
var z = [(any P)?]([S()])
449+
""")
450+
}
416451
}

0 commit comments

Comments
 (0)