mirror of
https://github.com/apple/sourcekit-lsp.git
synced 2026-10-05 20:35:20 +02:00
372 lines
14 KiB
Swift
372 lines
14 KiB
Swift
//===----------------------------------------------------------------------===//
|
|
//
|
|
// This source file is part of the Swift.org open source project
|
|
//
|
|
// Copyright (c) 2014 - 2026 Apple Inc. and the Swift project authors
|
|
// Licensed under Apache License v2.0 with Runtime Library Exception
|
|
//
|
|
// See https://swift.org/LICENSE.txt for license information
|
|
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
import SwiftRefactor
|
|
@_spi(RawSyntax) @_spi(ExperimentalLanguageFeatures) package import SwiftSyntax
|
|
|
|
/// Removes redundant parentheses from expressions.
|
|
///
|
|
/// Examples:
|
|
/// - `((x))` -> `x`
|
|
/// - `(x)` -> `x` (where x is a simple expression)
|
|
///
|
|
package struct RemoveRedundantParentheses: SyntaxRefactoringProvider {
|
|
package static func refactor(
|
|
syntax: TupleExprSyntax,
|
|
in context: Void
|
|
) throws -> ExprSyntax {
|
|
// If the syntax tree has errors, we should not attempt to refactor it.
|
|
guard !syntax.hasError else {
|
|
throw RefactoringNotApplicableError("syntax has errors")
|
|
}
|
|
|
|
// Check if the tuple expression has exactly one element and no label.
|
|
guard let innerExpr = syntax.elements.singleUnlabeledExpression else {
|
|
throw RefactoringNotApplicableError("not a parenthesized expression")
|
|
}
|
|
|
|
// Case 1: Nested parentheses ((expression)) -> (expression)
|
|
// Recursively strip inner parentheses to handle cases like (((x))) -> x
|
|
if let innerTuple = innerExpr.as(TupleExprSyntax.self) {
|
|
do {
|
|
let refactoredInner = try refactor(syntax: innerTuple, in: ())
|
|
return preserveTrivia(from: syntax, to: refactoredInner)
|
|
} catch {
|
|
// Inner refactoring not applicable (e.g., inner is a multi-element tuple like (x, y)),
|
|
// but we can still remove the outer parentheses around the inner tuple.
|
|
return preserveTrivia(from: syntax, to: innerExpr)
|
|
}
|
|
}
|
|
|
|
// Case 2: Parentheses around simple expressions
|
|
if canRemoveParentheses(tuple: syntax, around: innerExpr) {
|
|
return preserveTrivia(from: syntax, to: innerExpr)
|
|
}
|
|
|
|
// Default: Parentheses are not redundant
|
|
throw RefactoringNotApplicableError("parentheses are not redundant")
|
|
}
|
|
|
|
private static func preserveTrivia(from outer: TupleExprSyntax, to inner: ExprSyntax) -> ExprSyntax {
|
|
let leadingTrivia = outer.leftParen.leadingTrivia
|
|
.merging(outer.leftParen.trailingTrivia)
|
|
.merging(inner.leadingTrivia)
|
|
let trailingTrivia = inner.trailingTrivia
|
|
.merging(outer.rightParen.leadingTrivia)
|
|
.merging(outer.rightParen.trailingTrivia)
|
|
return
|
|
inner
|
|
.with(\.leadingTrivia, leadingTrivia)
|
|
.with(\.trailingTrivia, trailingTrivia)
|
|
}
|
|
|
|
private static func canRemoveParentheses(tuple: TupleExprSyntax, around expr: ExprSyntax) -> Bool {
|
|
// Safety Check: Immediately-invoked closures
|
|
// If parent is a FunctionCallExprSyntax and inner expr is a closure, it's an immediately invoked closure.
|
|
// The parentheses are required for disambiguation: `let x = ({ 1 })()` not `let x = { 1 }()`.
|
|
if let parent = tuple.parent, parent.is(FunctionCallExprSyntax.self), expr.is(ClosureExprSyntax.self) {
|
|
return false
|
|
}
|
|
|
|
// Safety Check: Ambiguous Closures
|
|
// Closures and trailing closures inside conditions need parentheses to avoid ambiguity.
|
|
// e.g. `if ({ true }) == ({ true }) {}` or `if (call { true }) == false {}`
|
|
// This applies to if/while/guard (ConditionElementSyntax), repeat-while (RepeatStmtSyntax),
|
|
// and where clauses (WhereClauseSyntax).
|
|
// It also applies to InitializerClauseSyntax if it is inside a condition (e.g. `if let x = ({...})`).
|
|
let isInCondition = isInContext(
|
|
tuple,
|
|
keyPaths: [
|
|
\ConditionElementSyntax.condition,
|
|
\RepeatStmtSyntax.condition,
|
|
\WhereClauseSyntax.condition,
|
|
]
|
|
)
|
|
|
|
let isInSwitchSubject = isInContext(tuple, keyPaths: [\SwitchExprSyntax.subject])
|
|
let isInForInSequence = isInContext(tuple, keyPaths: [\ForInStmtSyntax.sequence])
|
|
|
|
// Safety Check: Conditions and where clauses
|
|
if isInCondition && requiresParenForAmbiguousClosure(expr) {
|
|
return false
|
|
}
|
|
|
|
// Safety Check: Switch subjects
|
|
// `switch { true } {}` is invalid; a closure literal must be parenthesized.
|
|
if isInSwitchSubject && requiresParenForAmbiguousClosure(expr) {
|
|
return false
|
|
}
|
|
|
|
// Safety Check: for-in sequences
|
|
// Trailing closures (or IIFEs) in the sequence position should keep parentheses
|
|
// to avoid ambiguity warnings (e.g. `for _ in (call { ... })`).
|
|
if isInForInSequence && requiresParenForAmbiguousClosure(expr) {
|
|
return false
|
|
}
|
|
|
|
// Allowlist: Check keyPathInParent to explicitly know that this expression
|
|
// occurs in a place where the parentheses are redundant.
|
|
if let keyPath = tuple.keyPathInParent {
|
|
switch keyPath {
|
|
case \ConditionElementSyntax.condition,
|
|
\InitializerClauseSyntax.value,
|
|
\RepeatStmtSyntax.condition,
|
|
\ReturnStmtSyntax.expression,
|
|
\SwitchExprSyntax.subject,
|
|
\ThrowStmtSyntax.expression:
|
|
return true
|
|
default:
|
|
break
|
|
}
|
|
}
|
|
|
|
// Fallback: Allow if the expression itself is "simple"
|
|
guard isSimpleExpression(expr) else {
|
|
return false
|
|
}
|
|
|
|
// Safety Check: Postfix and Binary Precedence
|
|
// Expressions like `try`, `await`, `consume`, and `copy` bind looser than postfix and infix expressions.
|
|
// e.g., `(try? f()).description` is different from `try? f().description`.
|
|
// The former accesses `.description` on the Optional result, the latter on the unwrapped value.
|
|
// Similarly, `(try? f()) + 1` is different from `try? f() + 1` (Int? + Int vs Int + Int).
|
|
if let parent = tuple.parent, hasTighterBindingThanEffect(parent) {
|
|
switch expr.as(ExprSyntaxEnum.self) {
|
|
case .tryExpr, .awaitExpr, .unsafeExpr, .consumeExpr, .copyExpr:
|
|
return false
|
|
default:
|
|
break
|
|
}
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
/// Returns true if the node is an expression with higher precedence than effects (try/await/etc).
|
|
/// This includes postfix expressions (member access, subscript, call, force unwrap, optional chaining),
|
|
/// infix operators, type casting (as/is), and ternary expressions.
|
|
private static func hasTighterBindingThanEffect(_ node: Syntax) -> Bool {
|
|
if node.is(ExprListSyntax.self) {
|
|
return true
|
|
}
|
|
|
|
guard let expr = node.as(ExprSyntax.self) else {
|
|
return false
|
|
}
|
|
switch expr.as(ExprSyntaxEnum.self) {
|
|
// Postfix expressions: member access, subscript, function call, force unwrap, and postfix operators
|
|
// These all bind tighter than effect expressions (try/await/etc).
|
|
// For member access, since we're a TupleExprSyntax, we are always the base.
|
|
case .memberAccessExpr, .subscriptCallExpr, .functionCallExpr, .forceUnwrapExpr, .postfixOperatorExpr:
|
|
return true
|
|
|
|
case .optionalChainingExpr:
|
|
// Optional chaining (?.) binds tighter than effects
|
|
return true
|
|
|
|
// Infix operators and sequence expressions bind tighter than effects.
|
|
// For sequence expressions (before SwiftOperators folding), the parent chain
|
|
// is: TupleExpr -> ExprList -> SequenceExpr, e.g., `(try? f()) + 1`.
|
|
case .infixOperatorExpr, .sequenceExpr:
|
|
return true
|
|
|
|
// Type casting operators (as, is) bind tighter than effects.
|
|
// Ternary operator also binds tighter than effects.
|
|
case .asExpr, .isExpr, .ternaryExpr:
|
|
return true
|
|
|
|
// All other expression types do not bind tighter than effects
|
|
case .arrayExpr, .arrowExpr, .assignmentExpr, .awaitExpr, .binaryOperatorExpr,
|
|
.booleanLiteralExpr, .borrowExpr, ._canImportExpr, ._canImportVersionInfo,
|
|
.closureExpr, .consumeExpr, .copyExpr, .declReferenceExpr, .dictionaryExpr,
|
|
.discardAssignmentExpr, .doExpr, .editorPlaceholderExpr, .floatLiteralExpr,
|
|
.genericSpecializationExpr, .ifExpr, .inOutExpr, .integerLiteralExpr,
|
|
.keyPathExpr, .macroExpansionExpr, .missingExpr, .nilLiteralExpr,
|
|
.packElementExpr, .packExpansionExpr, .patternExpr, .postfixIfConfigExpr,
|
|
.prefixOperatorExpr, .regexLiteralExpr, .simpleStringLiteralExpr,
|
|
.stringLiteralExpr, .superExpr, .switchExpr, .tryExpr, .tupleExpr,
|
|
.typeExpr, .unresolvedAsExpr, .unresolvedIsExpr, .unresolvedTernaryExpr,
|
|
.unsafeExpr:
|
|
return false
|
|
#if RESILIENT_LIBRARIES
|
|
@unknown default:
|
|
return false
|
|
#endif
|
|
}
|
|
}
|
|
|
|
private static func hasTrailingClosure(_ expr: ExprSyntax) -> Bool {
|
|
switch expr.as(ExprSyntaxEnum.self) {
|
|
case .functionCallExpr(let functionCall):
|
|
return functionCall.trailingClosure != nil || !functionCall.additionalTrailingClosures.isEmpty
|
|
case .macroExpansionExpr(let macroExpansion):
|
|
return macroExpansion.trailingClosure != nil || !macroExpansion.additionalTrailingClosures.isEmpty
|
|
case .subscriptCallExpr(let subscriptCall):
|
|
return subscriptCall.trailingClosure != nil || !subscriptCall.additionalTrailingClosures.isEmpty
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
private static func requiresParenForAmbiguousClosure(_ expr: ExprSyntax) -> Bool {
|
|
expr.is(ClosureExprSyntax.self)
|
|
|| hasTrailingClosure(expr)
|
|
|| isImmediatelyInvokedClosure(expr)
|
|
}
|
|
|
|
private static func isInContext(_ tuple: TupleExprSyntax, keyPaths: [AnyKeyPath]) -> Bool {
|
|
return tuple.ancestorOrSelf(mapping: { node in
|
|
if let keyPathInParent = node.keyPathInParent,
|
|
keyPaths.contains(where: { $0 == keyPathInParent })
|
|
{
|
|
return true
|
|
}
|
|
return nil
|
|
}) ?? false
|
|
}
|
|
|
|
private static func isImmediatelyInvokedClosure(_ expr: ExprSyntax) -> Bool {
|
|
guard let functionCall = expr.as(FunctionCallExprSyntax.self) else {
|
|
return false
|
|
}
|
|
if functionCall.calledExpression.is(ClosureExprSyntax.self) {
|
|
return true
|
|
}
|
|
if let tuple = functionCall.calledExpression.as(TupleExprSyntax.self),
|
|
tuple.elements.singleUnlabeledExpression?.is(ClosureExprSyntax.self) == true
|
|
{
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
/// Checks if a type is simple enough to not require parentheses.
|
|
/// Complex types like `any Equatable`, `some P`, or `A & B` need parentheses, e.g. `(any Equatable).self`.
|
|
private static func isSimpleType(_ type: TypeSyntax) -> Bool {
|
|
switch type.as(TypeSyntaxEnum.self) {
|
|
case .arrayType,
|
|
.classRestrictionType,
|
|
.dictionaryType,
|
|
.identifierType,
|
|
.implicitlyUnwrappedOptionalType,
|
|
.inlineArrayType,
|
|
.memberType,
|
|
.metatypeType,
|
|
.missingType,
|
|
.optionalType,
|
|
.tupleType:
|
|
return true
|
|
case .attributedType, // @escaping, @Sendable, etc.
|
|
.compositionType, // A & B
|
|
.functionType, // (A) -> B
|
|
.namedOpaqueReturnType,
|
|
.packElementType,
|
|
.packExpansionType,
|
|
.someOrAnyType, // some P, any P
|
|
.suppressedType: // ~Copyable
|
|
return false
|
|
#if RESILIENT_LIBRARIES
|
|
@unknown default:
|
|
return false
|
|
#endif
|
|
}
|
|
}
|
|
|
|
private static func isSimpleExpression(_ expr: ExprSyntax) -> Bool {
|
|
// Allowlist of simple expressions that typically don't depend on precedence
|
|
// in a way that requires parentheses when used in most contexts,
|
|
// or are self-contained.
|
|
switch expr.as(ExprSyntaxEnum.self) {
|
|
// Simple expressions that don't require parentheses
|
|
case .arrayExpr,
|
|
.booleanLiteralExpr,
|
|
.closureExpr,
|
|
.declReferenceExpr,
|
|
.dictionaryExpr,
|
|
.floatLiteralExpr,
|
|
.forceUnwrapExpr,
|
|
.integerLiteralExpr,
|
|
.macroExpansionExpr,
|
|
.memberAccessExpr,
|
|
.nilLiteralExpr,
|
|
.optionalChainingExpr,
|
|
.regexLiteralExpr,
|
|
.simpleStringLiteralExpr,
|
|
.stringLiteralExpr,
|
|
.subscriptCallExpr,
|
|
.superExpr:
|
|
return true
|
|
|
|
// Types, effects, await, unsafe are simple only if the underlying type is simple
|
|
case .typeExpr(let typeExpr):
|
|
return isSimpleType(typeExpr.type)
|
|
case .awaitExpr(let awaitExpr):
|
|
return isSimpleExpression(awaitExpr.expression)
|
|
case .unsafeExpr(let unsafeExpr):
|
|
return isSimpleExpression(unsafeExpr.expression)
|
|
|
|
case .tryExpr(let tryExpr):
|
|
// Only try! and try? are simple; regular try is NOT simple
|
|
// because it affects precedence (e.g., try (try! foo()).bar() vs try try! foo().bar())
|
|
guard tryExpr.questionOrExclamationMark != nil else {
|
|
return false
|
|
}
|
|
return isSimpleExpression(tryExpr.expression)
|
|
case .functionCallExpr(let functionCall):
|
|
// A function call is simple enough to remove parentheses around it.
|
|
// Immediately-invoked closures need parentheses for disambiguation.
|
|
// Without parentheses, `let x = { 1 }()` parses as `let x = { 1 }` followed by `()` as a separate
|
|
// statement, rather than calling the closure. With parentheses: `let x = ({ 1 })()` works correctly.
|
|
return !functionCall.calledExpression.is(ClosureExprSyntax.self)
|
|
|
|
// Complex expressions that are NOT simple
|
|
case .arrowExpr,
|
|
.asExpr,
|
|
.assignmentExpr,
|
|
.binaryOperatorExpr,
|
|
.borrowExpr,
|
|
._canImportExpr,
|
|
._canImportVersionInfo,
|
|
.consumeExpr,
|
|
.copyExpr,
|
|
.discardAssignmentExpr,
|
|
.doExpr,
|
|
.editorPlaceholderExpr,
|
|
.genericSpecializationExpr,
|
|
.ifExpr,
|
|
.inOutExpr,
|
|
.infixOperatorExpr,
|
|
.isExpr,
|
|
.keyPathExpr,
|
|
.missingExpr,
|
|
.packElementExpr,
|
|
.packExpansionExpr,
|
|
.patternExpr,
|
|
.postfixIfConfigExpr,
|
|
.postfixOperatorExpr,
|
|
.prefixOperatorExpr,
|
|
.sequenceExpr,
|
|
.switchExpr,
|
|
.ternaryExpr,
|
|
.tupleExpr,
|
|
.unresolvedAsExpr,
|
|
.unresolvedIsExpr,
|
|
.unresolvedTernaryExpr:
|
|
return false
|
|
#if RESILIENT_LIBRARIES
|
|
@unknown default:
|
|
return false
|
|
#endif
|
|
}
|
|
}
|
|
}
|