Skip to content

Commit 114c422

Browse files
authored
Merge pull request #2339 from bnbarham/cherry-macro-system-fixes
[5.10] Cherry-pick a couple of macro system fixes for attached accessor macros
2 parents 52748a0 + 418793a commit 114c422

14 files changed

+623
-86
lines changed

Sources/SwiftParser/Expressions.swift

Lines changed: 38 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -792,7 +792,7 @@ extension Parser {
792792
}
793793

794794
// Check for a trailing closure, if allowed.
795-
if self.at(.leftBrace) && self.withLookahead({ $0.atValidTrailingClosure(flavor: flavor) }) {
795+
if self.at(.leftBrace) && !leadingExpr.raw.kind.isLiteral && self.withLookahead({ $0.atValidTrailingClosure(flavor: flavor) }) {
796796
// FIXME: if Result has a trailing closure, break out.
797797
// Add dummy blank argument list to the call expression syntax.
798798
let list = RawLabeledExprListSyntax(elements: [], arena: self.arena)
@@ -1498,17 +1498,38 @@ extension Parser {
14981498
var elements = [RawSyntax]()
14991499
do {
15001500
var collectionProgress = LoopProgressCondition()
1501-
COLLECTION_LOOP: while self.hasProgressed(&collectionProgress) {
1501+
var keepGoing: RawTokenSyntax?
1502+
COLLECTION_LOOP: repeat {
15021503
elementKind = self.parseCollectionElement(elementKind)
15031504

1505+
/// Whether expression of an array element or the value of a dictionary
1506+
/// element is missing. If this is the case, we shouldn't recover from
1507+
/// a missing comma since most likely the closing `]` is missing.
1508+
var elementIsMissingExpression: Bool {
1509+
switch elementKind! {
1510+
case .dictionary(_, _, _, let value):
1511+
return value.is(RawMissingExprSyntax.self)
1512+
case .array(let rawExprSyntax):
1513+
return rawExprSyntax.is(RawMissingExprSyntax.self)
1514+
}
1515+
}
1516+
15041517
// Parse the ',' if exists.
1505-
let comma = self.consume(if: .comma)
1518+
if let token = self.consume(if: .comma) {
1519+
keepGoing = token
1520+
} else if !self.at(.rightSquare, .endOfFile) && !self.atStartOfLine && !elementIsMissingExpression && !self.atStartOfDeclaration()
1521+
&& !self.atStartOfStatement(preferExpr: false)
1522+
{
1523+
keepGoing = missingToken(.comma)
1524+
} else {
1525+
keepGoing = nil
1526+
}
15061527

15071528
switch elementKind! {
15081529
case .array(let el):
15091530
let element = RawArrayElementSyntax(
15101531
expression: el,
1511-
trailingComma: comma,
1532+
trailingComma: keepGoing,
15121533
arena: self.arena
15131534
)
15141535
if element.isEmpty {
@@ -1522,7 +1543,7 @@ extension Parser {
15221543
unexpectedBeforeColon,
15231544
colon: colon,
15241545
value: value,
1525-
trailingComma: comma,
1546+
trailingComma: keepGoing,
15261547
arena: self.arena
15271548
)
15281549
if element.isEmpty {
@@ -1531,29 +1552,7 @@ extension Parser {
15311552
elements.append(RawSyntax(element))
15321553
}
15331554
}
1534-
1535-
// If we saw a comma, that's a strong indicator we have more elements
1536-
// to process. If that's not the case, we have to do some legwork to
1537-
// determine if we should bail out.
1538-
guard comma == nil || self.at(.rightSquare, .endOfFile) else {
1539-
continue
1540-
}
1541-
1542-
// If we found EOF or the closing square bracket, bailout.
1543-
if self.at(.rightSquare, .endOfFile) {
1544-
break
1545-
}
1546-
1547-
// If the next token is at the beginning of a new line and can never start
1548-
// an element, break.
1549-
if self.atStartOfLine
1550-
&& (self.at(.rightBrace, .poundEndif)
1551-
|| self.atStartOfDeclaration()
1552-
|| self.atStartOfStatement(preferExpr: false))
1553-
{
1554-
break
1555-
}
1556-
}
1555+
} while keepGoing != nil && self.hasProgressed(&collectionProgress)
15571556
}
15581557

15591558
let (unexpectedBeforeRSquare, rsquare) = self.expect(.rightSquare)
@@ -2519,3 +2518,14 @@ extension Parser.Lookahead {
25192518
}
25202519
}
25212520
}
2521+
2522+
extension SyntaxKind {
2523+
fileprivate var isLiteral: Bool {
2524+
switch self {
2525+
case .arrayExpr, .booleanLiteralExpr, .dictionaryExpr, .floatLiteralExpr, .integerLiteralExpr, .nilLiteralExpr, .regexLiteralExpr, .stringLiteralExpr:
2526+
return true
2527+
default:
2528+
return false
2529+
}
2530+
}
2531+
}

Sources/SwiftSyntaxMacroExpansion/MacroSystem.swift

Lines changed: 57 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -267,7 +267,8 @@ private func expandAccessorMacroWithoutExistingAccessors(
267267
conformanceList: nil,
268268
in: context,
269269
indentationWidth: indentationWidth
270-
)
270+
),
271+
!expanded.isEmpty
271272
else {
272273
return nil
273274
}
@@ -306,7 +307,7 @@ private func expandAccessorMacroWithExistingAccessors(
306307
return nil
307308
}
308309

309-
// Separate the accessor from any existing accessors by two spaces
310+
// Separate the accessor from any existing accessors by an empty line
310311
let indentedSource = "\n" + expanded.indented(by: attachedTo.indentationOfFirstLine + indentationWidth)
311312
return "\(raw: indentedSource)"
312313
}
@@ -635,13 +636,26 @@ private class MacroApplication<Context: MacroExpansionContext>: SyntaxRewriter {
635636
context.addDiagnostics(from: MacroApplicationError.accessorMacroOnVariableWithMultipleBindings, node: node)
636637
return DeclSyntax(node)
637638
}
638-
node.bindings[node.bindings.startIndex].accessorBlock = expandAccessors(of: node, existingAccessors: binding.accessorBlock)
639+
640+
let expansion = expandAccessors(of: node, existingAccessors: binding.accessorBlock)
641+
if expansion.accessors != binding.accessorBlock {
642+
if binding.initializer != nil, expansion.expandsGetSet {
643+
// The accessor block will have a leading space, but there will already be a
644+
// space between the variable and the to-be-removed initializer. Remove the
645+
// leading trivia on the accessor block so we don't double up.
646+
node.bindings[node.bindings.startIndex].accessorBlock = expansion.accessors?.with(\.leadingTrivia, [])
647+
node.bindings[node.bindings.startIndex].initializer = nil
648+
} else {
649+
node.bindings[node.bindings.startIndex].accessorBlock = expansion.accessors
650+
}
651+
}
652+
639653
return DeclSyntax(node)
640654
}
641655

642656
override func visit(_ node: SubscriptDeclSyntax) -> DeclSyntax {
643657
var node = super.visit(node).cast(SubscriptDeclSyntax.self)
644-
node.accessorBlock = expandAccessors(of: node, existingAccessors: node.accessorBlock)
658+
node.accessorBlock = expandAccessors(of: node, existingAccessors: node.accessorBlock).accessors
645659
return DeclSyntax(node)
646660
}
647661
}
@@ -802,14 +816,23 @@ extension MacroApplication {
802816
}
803817
}
804818

805-
/// Expand all 'accessor' macros attached to `storage` and return the `storage`
806-
/// node.
819+
/// Expand all 'accessor' macros attached to `storage`.
807820
///
808-
/// - Returns: The storage node with all macro-synthesized accessors applied.
809-
private func expandAccessors(of storage: some DeclSyntaxProtocol, existingAccessors: AccessorBlockSyntax?) -> AccessorBlockSyntax? {
821+
/// - Returns: The final accessors block that includes both the existing
822+
/// and expanded accessors, as well as whether any `get`/`set` were
823+
/// expanded (in which case any initializer on `storage` should be
824+
/// removed).
825+
private func expandAccessors(of storage: some DeclSyntaxProtocol, existingAccessors: AccessorBlockSyntax?) -> (
826+
accessors: AccessorBlockSyntax?, expandsGetSet: Bool
827+
) {
810828
let accessorMacros = macroAttributes(attachedTo: DeclSyntax(storage), ofType: AccessorMacro.Type.self)
811829

812830
var newAccessorsBlock = existingAccessors
831+
var expandsGetSet = false
832+
func checkExpansions(_ accessors: AccessorDeclListSyntax?) {
833+
guard let accessors else { return }
834+
expandsGetSet = expandsGetSet || accessors.contains(where: \.isGetOrSet)
835+
}
813836

814837
for macro in accessorMacros {
815838
do {
@@ -827,6 +850,8 @@ extension MacroApplication {
827850
in: context,
828851
indentationWidth: indentationWidth
829852
) {
853+
checkExpansions(newAccessors)
854+
830855
// If existingAccessors is not `nil`, then we also set
831856
// `newAccessorBlock` above to a a non-nil value, so
832857
// `newAccessorsBlock` also isn’t `nil`.
@@ -835,31 +860,33 @@ extension MacroApplication {
835860
indentationWidth: self.indentationWidth
836861
)
837862
}
838-
} else {
839-
let newAccessors = try expandAccessorMacroWithoutExistingAccessors(
840-
definition: macro.definition,
841-
attributeNode: macro.attributeNode,
842-
attachedTo: DeclSyntax(storage),
843-
in: context,
844-
indentationWidth: indentationWidth
845-
)
846-
if newAccessorsBlock == nil {
847-
newAccessorsBlock = newAccessors
848-
} else if let newAccessors = newAccessors {
849-
guard case .accessors(let accessorList) = newAccessors.accessors else {
850-
throw MacroApplicationError.malformedAccessor
851-
}
852-
newAccessorsBlock = newAccessorsBlock!.addingAccessors(
863+
} else if let newAccessors = try expandAccessorMacroWithoutExistingAccessors(
864+
definition: macro.definition,
865+
attributeNode: macro.attributeNode,
866+
attachedTo: DeclSyntax(storage),
867+
in: context,
868+
indentationWidth: indentationWidth
869+
) {
870+
guard case .accessors(let accessorList) = newAccessors.accessors else {
871+
throw MacroApplicationError.malformedAccessor
872+
}
873+
874+
checkExpansions(accessorList)
875+
876+
if let oldBlock = newAccessorsBlock {
877+
newAccessorsBlock = oldBlock.addingAccessors(
853878
from: accessorList,
854879
indentationWidth: self.indentationWidth
855880
)
881+
} else {
882+
newAccessorsBlock = newAccessors
856883
}
857884
}
858885
} catch {
859886
context.addDiagnostics(from: error, node: macro.attributeNode)
860887
}
861888
}
862-
return newAccessorsBlock
889+
return (newAccessorsBlock, expandsGetSet)
863890
}
864891
}
865892

@@ -1063,3 +1090,9 @@ private extension AttributeSyntax {
10631090
return (detach(in: context, foldingWith: operatorTable) as Syntax).cast(Self.self)
10641091
}
10651092
}
1093+
1094+
private extension AccessorDeclSyntax {
1095+
var isGetOrSet: Bool {
1096+
return accessorSpecifier.tokenKind == .keyword(.get) || accessorSpecifier.tokenKind == .keyword(.set)
1097+
}
1098+
}

Tests/SwiftParserTest/DeclarationTests.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2513,4 +2513,11 @@ final class DeclarationTests: ParserTestCase {
25132513
"""
25142514
)
25152515
}
2516+
2517+
func testLiteralInitializerWithTrailingClosure() {
2518+
assertParse(
2519+
"let foo = 1 { return 1 }",
2520+
substructure: AccessorBlockSyntax(accessors: .getter([CodeBlockItemSyntax("return 1")]))
2521+
)
2522+
}
25162523
}

0 commit comments

Comments
 (0)