From 45eb057d46240d0d2522e60646434a0a839d936c Mon Sep 17 00:00:00 2001 From: Rintaro Ishizaki Date: Tue, 9 May 2023 19:06:49 -0700 Subject: [PATCH 1/6] [Parser] Attributes on MacroExpansionDeclSyntax * Add `attributes` and `modifiers` to `MacroExpansionDeclSyntax` * Diagnose whitespaces between # and the macro name * Attach attributes to MacroExpansionDeclSyntax rdar://107386648 (cherry picked from commit fd89876a0b02474e16bc621bee29ad60463139b4) --- .../Sources/SyntaxSupport/DeclNodes.swift | 12 ++ Sources/SwiftParser/Declarations.swift | 67 ++++-- Sources/SwiftParser/Expressions.swift | 15 +- Sources/SwiftParser/TokenSpecSet.swift | 6 +- .../generated/ChildNameForDiagnostics.swift | 4 + .../generated/ChildNameForKeyPath.swift | 12 +- .../generated/raw/RawSyntaxNodes.swift | 96 +++++---- .../generated/raw/RawSyntaxValidation.swift | 22 +- .../syntaxNodes/SyntaxDeclNodes.swift | 172 +++++++++++---- .../generated/BuildableNodes.swift | 12 +- Tests/SwiftParserTest/DeclarationTests.swift | 202 ++++++++++++++++++ 11 files changed, 513 insertions(+), 107 deletions(-) diff --git a/CodeGeneration/Sources/SyntaxSupport/DeclNodes.swift b/CodeGeneration/Sources/SyntaxSupport/DeclNodes.swift index eaf2eef668a..d06221a6927 100644 --- a/CodeGeneration/Sources/SyntaxSupport/DeclNodes.swift +++ b/CodeGeneration/Sources/SyntaxSupport/DeclNodes.swift @@ -1194,6 +1194,18 @@ public let DECL_NODES: [Node] = [ "FreestandingMacroExpansion" ], children: [ + Child( + name: "Attributes", + kind: .collection(kind: "AttributeList", collectionElementName: "Attribute"), + nameForDiagnostics: "attributes", + isOptional: true + ), + Child( + name: "Modifiers", + kind: .collection(kind: "ModifierList", collectionElementName: "Modifier"), + nameForDiagnostics: "modifiers", + isOptional: true + ), Child( name: "PoundToken", kind: .token(choices: [.token(tokenKind: "PoundToken")]), diff --git a/Sources/SwiftParser/Declarations.swift b/Sources/SwiftParser/Declarations.swift index f506deaa5ef..ef726a8496a 100644 --- a/Sources/SwiftParser/Declarations.swift +++ b/Sources/SwiftParser/Declarations.swift @@ -29,18 +29,25 @@ extension DeclarationModifier { } extension TokenConsumer { + mutating func atStartOfFreestandingMacroExpansion() -> Bool { + if !self.at(.pound) { + return false + } + if self.peek().rawTokenKind != .identifier && !self.peek().isLexerClassifiedKeyword { + return false + } + if self.currentToken.trailingTriviaByteLength != 0 || self.peek().leadingTriviaByteLength != 0 { + return false + } + return true + } + mutating func atStartOfDeclaration( isAtTopLevel: Bool = false, allowInitDecl: Bool = true, allowRecovery: Bool = false ) -> Bool { if self.at(anyIn: PoundDeclarationStart.self) != nil { - // Don't treat freestanding macro expansions as declarations. They'll be - // parsed as expressions. - if self.at(.pound) { - return false - } - return true } @@ -53,12 +60,14 @@ extension TokenConsumer { _ = subparser.consumeAttributeList() } + var hasModifier = false if subparser.currentToken.isLexerClassifiedKeyword || subparser.currentToken.rawTokenKind == .identifier { var modifierProgress = LoopProgressCondition() while let (modifierKind, handle) = subparser.at(anyIn: DeclarationModifier.self), modifierKind != .class, modifierProgress.evaluate(subparser.currentToken) { + hasModifier = true subparser.eat(handle) if modifierKind != .open && subparser.at(.leftParen) && modifierKind.canHaveParenthesizedArgument { // When determining whether we are at a declaration, don't consume anything in parentheses after 'open' @@ -113,6 +122,16 @@ extension TokenConsumer { case .macroKeyword: // macro Foo ... return subparser.peek().rawTokenKind == .identifier + case .pound: + // Force parsing '#' after attributes as a macro expansion decl. + if hasAttribute || hasModifier { + return true + } + + // Otherwise, parse it as a expression. + // FIXME: C++ parser returns true if this is a top-level non-"script" files. + // But we don't have "is library" flag. + return false case .some(_): // All other decl start keywords unconditonally start a decl. return true @@ -203,10 +222,6 @@ extension Parser { return .decls(RawMemberDeclListSyntax(elements: elements, arena: parser.arena)) } return RawDeclSyntax(directive) - case (.pound, _)?: - // FIXME: If we can have attributes for macro expansions, handle this - // via DeclarationStart. - return RawDeclSyntax(self.parseMacroExpansionDeclaration()) case nil: break } @@ -258,6 +273,8 @@ extension Parser { return RawDeclSyntax(self.parseNominalTypeDeclaration(for: RawActorDeclSyntax.self, attrs: attrs, introucerHandle: handle)) case (.macroKeyword, let handle)?: return RawDeclSyntax(self.parseMacroDeclaration(attrs: attrs, introducerHandle: handle)) + case (.pound, let handle)?: + return RawDeclSyntax(self.parseMacroExpansionDeclaration(attrs, handle)) case nil: if inMemberDeclList { let isProbablyVarDecl = self.at(.identifier, .wildcard) && self.peek().rawTokenKind.is(.colon, .equal, .comma) @@ -2011,9 +2028,30 @@ extension Parser { /// ======= /// /// macro-expansion-declaration → '#' identifier expr-call-suffix? - mutating func parseMacroExpansionDeclaration() -> RawMacroExpansionDeclSyntax { - let poundKeyword = self.consumeAnyToken() - let (unexpectedBeforeMacro, macro) = self.expectIdentifier() + mutating func parseMacroExpansionDeclaration( + _ attrs: DeclAttributes, + _ handle: RecoveryConsumptionHandle + ) -> RawMacroExpansionDeclSyntax { + + let (unexpectedBeforePound, poundKeyword) = self.eat(handle) + // Don't allow space between '#' and the macro name. + if poundKeyword.trailingTriviaByteLength != 0 || self.currentToken.leadingTriviaByteLength != 0 { + return RawMacroExpansionDeclSyntax( + attributes: attrs.attributes, + modifiers: attrs.modifiers, + unexpectedBeforePound, + poundToken: poundKeyword, + macro: self.missingToken(.identifier), + genericArguments: nil, + leftParen: nil, + argumentList: .init(elements: [], arena: self.arena), + rightParen: nil, + trailingClosure: nil, + additionalTrailingClosures: nil, + arena: self.arena + ) + } + let (unexpectedBeforeMacro, macro) = self.expectIdentifier(keywordRecovery: true) // Parse the optional generic argument list. let generics: RawGenericArgumentClauseSyntax? @@ -2051,6 +2089,9 @@ extension Parser { } return RawMacroExpansionDeclSyntax( + attributes: attrs.attributes, + modifiers: attrs.modifiers, + unexpectedBeforePound, poundToken: poundKeyword, unexpectedBeforeMacro, macro: macro, diff --git a/Sources/SwiftParser/Expressions.swift b/Sources/SwiftParser/Expressions.swift index ad77e9d0fed..37fa05a8d39 100644 --- a/Sources/SwiftParser/Expressions.swift +++ b/Sources/SwiftParser/Expressions.swift @@ -1346,8 +1346,21 @@ extension Parser { pattern: PatternContext, flavor: ExprFlavor ) -> RawMacroExpansionExprSyntax { + if !atStartOfFreestandingMacroExpansion() { + return RawMacroExpansionExprSyntax( + poundToken: self.consumeAnyToken(), + macro: self.missingToken(.identifier), + genericArguments: nil, + leftParen: nil, + argumentList: .init(elements: [], arena: self.arena), + rightParen: nil, + trailingClosure: nil, + additionalTrailingClosures: nil, + arena: self.arena + ) + } let poundKeyword = self.consumeAnyToken() - let (unexpectedBeforeMacro, macro) = self.expectIdentifier() + let (unexpectedBeforeMacro, macro) = self.expectIdentifier(keywordRecovery: true) // Parse the optional generic argument list. let generics: RawGenericArgumentClauseSyntax? diff --git a/Sources/SwiftParser/TokenSpecSet.swift b/Sources/SwiftParser/TokenSpecSet.swift index 37991d76c2d..a3d8cfb73aa 100644 --- a/Sources/SwiftParser/TokenSpecSet.swift +++ b/Sources/SwiftParser/TokenSpecSet.swift @@ -248,6 +248,7 @@ enum DeclarationStart: TokenSpecSet { case typealiasKeyword case varKeyword case inoutKeyword + case pound init?(lexeme: Lexer.Lexeme) { switch PrepareForKeywordMatch(lexeme) { @@ -271,6 +272,7 @@ enum DeclarationStart: TokenSpecSet { case TokenSpec(.typealias): self = .typealiasKeyword case TokenSpec(.var): self = .varKeyword case TokenSpec(.inout): self = .inoutKeyword + case TokenSpec(.pound): self = .pound default: return nil } } @@ -297,6 +299,7 @@ enum DeclarationStart: TokenSpecSet { case .typealiasKeyword: return .keyword(.typealias) case .varKeyword: return .keyword(.var) case .inoutKeyword: return TokenSpec(.inout, recoveryPrecedence: .declKeyword) + case .pound: return TokenSpec(.pound, recoveryPrecedence: .openingPoundIf) } } } @@ -372,12 +375,10 @@ enum OperatorLike: TokenSpecSet { enum PoundDeclarationStart: TokenSpecSet { case poundIfKeyword - case pound init?(lexeme: Lexer.Lexeme) { switch lexeme.rawTokenKind { case .poundIfKeyword: self = .poundIfKeyword - case .pound: self = .pound default: return nil } } @@ -385,7 +386,6 @@ enum PoundDeclarationStart: TokenSpecSet { var spec: TokenSpec { switch self { case .poundIfKeyword: return .poundIfKeyword - case .pound: return .pound } } } diff --git a/Sources/SwiftParserDiagnostics/generated/ChildNameForDiagnostics.swift b/Sources/SwiftParserDiagnostics/generated/ChildNameForDiagnostics.swift index c1fe58ff4f3..c608e17b934 100644 --- a/Sources/SwiftParserDiagnostics/generated/ChildNameForDiagnostics.swift +++ b/Sources/SwiftParserDiagnostics/generated/ChildNameForDiagnostics.swift @@ -236,6 +236,10 @@ private func childNameForDiagnostics(_ keyPath: AnyKeyPath) -> String? { return "macro definition" case \MacroDeclSyntax.genericWhereClause: return "generic where clause" + case \MacroExpansionDeclSyntax.attributes: + return "attributes" + case \MacroExpansionDeclSyntax.modifiers: + return "modifiers" case \MemberAccessExprSyntax.base: return "base" case \MemberAccessExprSyntax.name: diff --git a/Sources/SwiftSyntax/generated/ChildNameForKeyPath.swift b/Sources/SwiftSyntax/generated/ChildNameForKeyPath.swift index bb1b234ad12..1f72b8870ba 100644 --- a/Sources/SwiftSyntax/generated/ChildNameForKeyPath.swift +++ b/Sources/SwiftSyntax/generated/ChildNameForKeyPath.swift @@ -1955,8 +1955,16 @@ public func childName(_ keyPath: AnyKeyPath) -> String? { return "genericWhereClause" case \MacroDeclSyntax.unexpectedAfterGenericWhereClause: return "unexpectedAfterGenericWhereClause" - case \MacroExpansionDeclSyntax.unexpectedBeforePoundToken: - return "unexpectedBeforePoundToken" + case \MacroExpansionDeclSyntax.unexpectedBeforeAttributes: + return "unexpectedBeforeAttributes" + case \MacroExpansionDeclSyntax.attributes: + return "attributes" + case \MacroExpansionDeclSyntax.unexpectedBetweenAttributesAndModifiers: + return "unexpectedBetweenAttributesAndModifiers" + case \MacroExpansionDeclSyntax.modifiers: + return "modifiers" + case \MacroExpansionDeclSyntax.unexpectedBetweenModifiersAndPoundToken: + return "unexpectedBetweenModifiersAndPoundToken" case \MacroExpansionDeclSyntax.poundToken: return "poundToken" case \MacroExpansionDeclSyntax.unexpectedBetweenPoundTokenAndMacro: diff --git a/Sources/SwiftSyntax/generated/raw/RawSyntaxNodes.swift b/Sources/SwiftSyntax/generated/raw/RawSyntaxNodes.swift index c1becf18037..a925190867a 100644 --- a/Sources/SwiftSyntax/generated/raw/RawSyntaxNodes.swift +++ b/Sources/SwiftSyntax/generated/raw/RawSyntaxNodes.swift @@ -13122,7 +13122,11 @@ public struct RawMacroExpansionDeclSyntax: RawDeclSyntaxNodeProtocol { } public init( - _ unexpectedBeforePoundToken: RawUnexpectedNodesSyntax? = nil, + _ unexpectedBeforeAttributes: RawUnexpectedNodesSyntax? = nil, + attributes: RawAttributeListSyntax?, + _ unexpectedBetweenAttributesAndModifiers: RawUnexpectedNodesSyntax? = nil, + modifiers: RawModifierListSyntax?, + _ unexpectedBetweenModifiersAndPoundToken: RawUnexpectedNodesSyntax? = nil, poundToken: RawTokenSyntax, _ unexpectedBetweenPoundTokenAndMacro: RawUnexpectedNodesSyntax? = nil, macro: RawTokenSyntax, @@ -13142,95 +13146,115 @@ public struct RawMacroExpansionDeclSyntax: RawDeclSyntaxNodeProtocol { arena: __shared SyntaxArena ) { let raw = RawSyntax.makeLayout( - kind: .macroExpansionDecl, uninitializedCount: 17, arena: arena) { layout in + kind: .macroExpansionDecl, uninitializedCount: 21, arena: arena) { layout in layout.initialize(repeating: nil) - layout[0] = unexpectedBeforePoundToken?.raw - layout[1] = poundToken.raw - layout[2] = unexpectedBetweenPoundTokenAndMacro?.raw - layout[3] = macro.raw - layout[4] = unexpectedBetweenMacroAndGenericArguments?.raw - layout[5] = genericArguments?.raw - layout[6] = unexpectedBetweenGenericArgumentsAndLeftParen?.raw - layout[7] = leftParen?.raw - layout[8] = unexpectedBetweenLeftParenAndArgumentList?.raw - layout[9] = argumentList.raw - layout[10] = unexpectedBetweenArgumentListAndRightParen?.raw - layout[11] = rightParen?.raw - layout[12] = unexpectedBetweenRightParenAndTrailingClosure?.raw - layout[13] = trailingClosure?.raw - layout[14] = unexpectedBetweenTrailingClosureAndAdditionalTrailingClosures?.raw - layout[15] = additionalTrailingClosures?.raw - layout[16] = unexpectedAfterAdditionalTrailingClosures?.raw + layout[0] = unexpectedBeforeAttributes?.raw + layout[1] = attributes?.raw + layout[2] = unexpectedBetweenAttributesAndModifiers?.raw + layout[3] = modifiers?.raw + layout[4] = unexpectedBetweenModifiersAndPoundToken?.raw + layout[5] = poundToken.raw + layout[6] = unexpectedBetweenPoundTokenAndMacro?.raw + layout[7] = macro.raw + layout[8] = unexpectedBetweenMacroAndGenericArguments?.raw + layout[9] = genericArguments?.raw + layout[10] = unexpectedBetweenGenericArgumentsAndLeftParen?.raw + layout[11] = leftParen?.raw + layout[12] = unexpectedBetweenLeftParenAndArgumentList?.raw + layout[13] = argumentList.raw + layout[14] = unexpectedBetweenArgumentListAndRightParen?.raw + layout[15] = rightParen?.raw + layout[16] = unexpectedBetweenRightParenAndTrailingClosure?.raw + layout[17] = trailingClosure?.raw + layout[18] = unexpectedBetweenTrailingClosureAndAdditionalTrailingClosures?.raw + layout[19] = additionalTrailingClosures?.raw + layout[20] = unexpectedAfterAdditionalTrailingClosures?.raw } self.init(unchecked: raw) } - public var unexpectedBeforePoundToken: RawUnexpectedNodesSyntax? { + public var unexpectedBeforeAttributes: RawUnexpectedNodesSyntax? { layoutView.children[0].map(RawUnexpectedNodesSyntax.init(raw:)) } + public var attributes: RawAttributeListSyntax? { + layoutView.children[1].map(RawAttributeListSyntax.init(raw:)) + } + + public var unexpectedBetweenAttributesAndModifiers: RawUnexpectedNodesSyntax? { + layoutView.children[2].map(RawUnexpectedNodesSyntax.init(raw:)) + } + + public var modifiers: RawModifierListSyntax? { + layoutView.children[3].map(RawModifierListSyntax.init(raw:)) + } + + public var unexpectedBetweenModifiersAndPoundToken: RawUnexpectedNodesSyntax? { + layoutView.children[4].map(RawUnexpectedNodesSyntax.init(raw:)) + } + public var poundToken: RawTokenSyntax { - layoutView.children[1].map(RawTokenSyntax.init(raw:))! + layoutView.children[5].map(RawTokenSyntax.init(raw:))! } public var unexpectedBetweenPoundTokenAndMacro: RawUnexpectedNodesSyntax? { - layoutView.children[2].map(RawUnexpectedNodesSyntax.init(raw:)) + layoutView.children[6].map(RawUnexpectedNodesSyntax.init(raw:)) } public var macro: RawTokenSyntax { - layoutView.children[3].map(RawTokenSyntax.init(raw:))! + layoutView.children[7].map(RawTokenSyntax.init(raw:))! } public var unexpectedBetweenMacroAndGenericArguments: RawUnexpectedNodesSyntax? { - layoutView.children[4].map(RawUnexpectedNodesSyntax.init(raw:)) + layoutView.children[8].map(RawUnexpectedNodesSyntax.init(raw:)) } public var genericArguments: RawGenericArgumentClauseSyntax? { - layoutView.children[5].map(RawGenericArgumentClauseSyntax.init(raw:)) + layoutView.children[9].map(RawGenericArgumentClauseSyntax.init(raw:)) } public var unexpectedBetweenGenericArgumentsAndLeftParen: RawUnexpectedNodesSyntax? { - layoutView.children[6].map(RawUnexpectedNodesSyntax.init(raw:)) + layoutView.children[10].map(RawUnexpectedNodesSyntax.init(raw:)) } public var leftParen: RawTokenSyntax? { - layoutView.children[7].map(RawTokenSyntax.init(raw:)) + layoutView.children[11].map(RawTokenSyntax.init(raw:)) } public var unexpectedBetweenLeftParenAndArgumentList: RawUnexpectedNodesSyntax? { - layoutView.children[8].map(RawUnexpectedNodesSyntax.init(raw:)) + layoutView.children[12].map(RawUnexpectedNodesSyntax.init(raw:)) } public var argumentList: RawTupleExprElementListSyntax { - layoutView.children[9].map(RawTupleExprElementListSyntax.init(raw:))! + layoutView.children[13].map(RawTupleExprElementListSyntax.init(raw:))! } public var unexpectedBetweenArgumentListAndRightParen: RawUnexpectedNodesSyntax? { - layoutView.children[10].map(RawUnexpectedNodesSyntax.init(raw:)) + layoutView.children[14].map(RawUnexpectedNodesSyntax.init(raw:)) } public var rightParen: RawTokenSyntax? { - layoutView.children[11].map(RawTokenSyntax.init(raw:)) + layoutView.children[15].map(RawTokenSyntax.init(raw:)) } public var unexpectedBetweenRightParenAndTrailingClosure: RawUnexpectedNodesSyntax? { - layoutView.children[12].map(RawUnexpectedNodesSyntax.init(raw:)) + layoutView.children[16].map(RawUnexpectedNodesSyntax.init(raw:)) } public var trailingClosure: RawClosureExprSyntax? { - layoutView.children[13].map(RawClosureExprSyntax.init(raw:)) + layoutView.children[17].map(RawClosureExprSyntax.init(raw:)) } public var unexpectedBetweenTrailingClosureAndAdditionalTrailingClosures: RawUnexpectedNodesSyntax? { - layoutView.children[14].map(RawUnexpectedNodesSyntax.init(raw:)) + layoutView.children[18].map(RawUnexpectedNodesSyntax.init(raw:)) } public var additionalTrailingClosures: RawMultipleTrailingClosureElementListSyntax? { - layoutView.children[15].map(RawMultipleTrailingClosureElementListSyntax.init(raw:)) + layoutView.children[19].map(RawMultipleTrailingClosureElementListSyntax.init(raw:)) } public var unexpectedAfterAdditionalTrailingClosures: RawUnexpectedNodesSyntax? { - layoutView.children[16].map(RawUnexpectedNodesSyntax.init(raw:)) + layoutView.children[20].map(RawUnexpectedNodesSyntax.init(raw:)) } } diff --git a/Sources/SwiftSyntax/generated/raw/RawSyntaxValidation.swift b/Sources/SwiftSyntax/generated/raw/RawSyntaxValidation.swift index aabc494c55d..20b88db0c0c 100644 --- a/Sources/SwiftSyntax/generated/raw/RawSyntaxValidation.swift +++ b/Sources/SwiftSyntax/generated/raw/RawSyntaxValidation.swift @@ -1673,24 +1673,28 @@ func validateLayout(layout: RawSyntaxBuffer, as kind: SyntaxKind) { assertNoError(kind, 15, verify(layout[15], as: RawGenericWhereClauseSyntax?.self)) assertNoError(kind, 16, verify(layout[16], as: RawUnexpectedNodesSyntax?.self)) case .macroExpansionDecl: - assert(layout.count == 17) + assert(layout.count == 21) assertNoError(kind, 0, verify(layout[0], as: RawUnexpectedNodesSyntax?.self)) - assertNoError(kind, 1, verify(layout[1], as: RawTokenSyntax.self, tokenChoices: [.tokenKind(.pound)])) + assertNoError(kind, 1, verify(layout[1], as: RawAttributeListSyntax?.self)) assertNoError(kind, 2, verify(layout[2], as: RawUnexpectedNodesSyntax?.self)) - assertNoError(kind, 3, verify(layout[3], as: RawTokenSyntax.self, tokenChoices: [.tokenKind(.identifier)])) + assertNoError(kind, 3, verify(layout[3], as: RawModifierListSyntax?.self)) assertNoError(kind, 4, verify(layout[4], as: RawUnexpectedNodesSyntax?.self)) - assertNoError(kind, 5, verify(layout[5], as: RawGenericArgumentClauseSyntax?.self)) + assertNoError(kind, 5, verify(layout[5], as: RawTokenSyntax.self, tokenChoices: [.tokenKind(.pound)])) assertNoError(kind, 6, verify(layout[6], as: RawUnexpectedNodesSyntax?.self)) - assertNoError(kind, 7, verify(layout[7], as: RawTokenSyntax?.self, tokenChoices: [.tokenKind(.leftParen)])) + assertNoError(kind, 7, verify(layout[7], as: RawTokenSyntax.self, tokenChoices: [.tokenKind(.identifier)])) assertNoError(kind, 8, verify(layout[8], as: RawUnexpectedNodesSyntax?.self)) - assertNoError(kind, 9, verify(layout[9], as: RawTupleExprElementListSyntax.self)) + assertNoError(kind, 9, verify(layout[9], as: RawGenericArgumentClauseSyntax?.self)) assertNoError(kind, 10, verify(layout[10], as: RawUnexpectedNodesSyntax?.self)) - assertNoError(kind, 11, verify(layout[11], as: RawTokenSyntax?.self, tokenChoices: [.tokenKind(.rightParen)])) + assertNoError(kind, 11, verify(layout[11], as: RawTokenSyntax?.self, tokenChoices: [.tokenKind(.leftParen)])) assertNoError(kind, 12, verify(layout[12], as: RawUnexpectedNodesSyntax?.self)) - assertNoError(kind, 13, verify(layout[13], as: RawClosureExprSyntax?.self)) + assertNoError(kind, 13, verify(layout[13], as: RawTupleExprElementListSyntax.self)) assertNoError(kind, 14, verify(layout[14], as: RawUnexpectedNodesSyntax?.self)) - assertNoError(kind, 15, verify(layout[15], as: RawMultipleTrailingClosureElementListSyntax?.self)) + assertNoError(kind, 15, verify(layout[15], as: RawTokenSyntax?.self, tokenChoices: [.tokenKind(.rightParen)])) assertNoError(kind, 16, verify(layout[16], as: RawUnexpectedNodesSyntax?.self)) + assertNoError(kind, 17, verify(layout[17], as: RawClosureExprSyntax?.self)) + assertNoError(kind, 18, verify(layout[18], as: RawUnexpectedNodesSyntax?.self)) + assertNoError(kind, 19, verify(layout[19], as: RawMultipleTrailingClosureElementListSyntax?.self)) + assertNoError(kind, 20, verify(layout[20], as: RawUnexpectedNodesSyntax?.self)) case .macroExpansionExpr: assert(layout.count == 17) assertNoError(kind, 0, verify(layout[0], as: RawUnexpectedNodesSyntax?.self)) diff --git a/Sources/SwiftSyntax/generated/syntaxNodes/SyntaxDeclNodes.swift b/Sources/SwiftSyntax/generated/syntaxNodes/SyntaxDeclNodes.swift index 7c222d03f1a..e530dafa780 100644 --- a/Sources/SwiftSyntax/generated/syntaxNodes/SyntaxDeclNodes.swift +++ b/Sources/SwiftSyntax/generated/syntaxNodes/SyntaxDeclNodes.swift @@ -3624,7 +3624,11 @@ public struct MacroExpansionDeclSyntax: DeclSyntaxProtocol, SyntaxHashable { public init( leadingTrivia: Trivia? = nil, - _ unexpectedBeforePoundToken: UnexpectedNodesSyntax? = nil, + _ unexpectedBeforeAttributes: UnexpectedNodesSyntax? = nil, + attributes: AttributeListSyntax? = nil, + _ unexpectedBetweenAttributesAndModifiers: UnexpectedNodesSyntax? = nil, + modifiers: ModifierListSyntax? = nil, + _ unexpectedBetweenModifiersAndPoundToken: UnexpectedNodesSyntax? = nil, poundToken: TokenSyntax = .poundToken(), _ unexpectedBetweenPoundTokenAndMacro: UnexpectedNodesSyntax? = nil, macro: TokenSyntax, @@ -3647,7 +3651,11 @@ public struct MacroExpansionDeclSyntax: DeclSyntaxProtocol, SyntaxHashable { // Extend the lifetime of all parameters so their arenas don't get destroyed // before they can be added as children of the new arena. let data: SyntaxData = withExtendedLifetime((SyntaxArena(), ( - unexpectedBeforePoundToken, + unexpectedBeforeAttributes, + attributes, + unexpectedBetweenAttributesAndModifiers, + modifiers, + unexpectedBetweenModifiersAndPoundToken, poundToken, unexpectedBetweenPoundTokenAndMacro, macro, @@ -3666,7 +3674,11 @@ public struct MacroExpansionDeclSyntax: DeclSyntaxProtocol, SyntaxHashable { unexpectedAfterAdditionalTrailingClosures ))) {(arena, _) in let layout: [RawSyntax?] = [ - unexpectedBeforePoundToken?.raw, + unexpectedBeforeAttributes?.raw, + attributes?.raw, + unexpectedBetweenAttributesAndModifiers?.raw, + modifiers?.raw, + unexpectedBetweenModifiersAndPoundToken?.raw, poundToken.raw, unexpectedBetweenPoundTokenAndMacro?.raw, macro.raw, @@ -3697,7 +3709,7 @@ public struct MacroExpansionDeclSyntax: DeclSyntaxProtocol, SyntaxHashable { self.init(data) } - public var unexpectedBeforePoundToken: UnexpectedNodesSyntax? { + public var unexpectedBeforeAttributes: UnexpectedNodesSyntax? { get { return data.child(at: 0, parent: Syntax(self)).map(UnexpectedNodesSyntax.init) } @@ -3706,85 +3718,159 @@ public struct MacroExpansionDeclSyntax: DeclSyntaxProtocol, SyntaxHashable { } } + public var attributes: AttributeListSyntax? { + get { + return data.child(at: 1, parent: Syntax(self)).map(AttributeListSyntax.init) + } + set(value) { + self = MacroExpansionDeclSyntax(data.replacingChild(at: 1, with: value?.raw, arena: SyntaxArena())) + } + } + + /// Adds the provided `Attribute` to the node's `attributes` + /// collection. + /// - param element: The new `Attribute` to add to the node's + /// `attributes` collection. + /// - returns: A copy of the receiver with the provided `Attribute` + /// appended to its `attributes` collection. + public func addAttribute(_ element: Syntax) -> MacroExpansionDeclSyntax { + var collection: RawSyntax + let arena = SyntaxArena() + if let col = raw.layoutView!.children[1] { + collection = col.layoutView!.appending(element.raw, arena: arena) + } else { + collection = RawSyntax.makeLayout(kind: SyntaxKind.attributeList, + from: [element.raw], arena: arena) + } + let newData = data.replacingChild(at: 1, with: collection, arena: arena) + return MacroExpansionDeclSyntax(newData) + } + + public var unexpectedBetweenAttributesAndModifiers: UnexpectedNodesSyntax? { + get { + return data.child(at: 2, parent: Syntax(self)).map(UnexpectedNodesSyntax.init) + } + set(value) { + self = MacroExpansionDeclSyntax(data.replacingChild(at: 2, with: value?.raw, arena: SyntaxArena())) + } + } + + public var modifiers: ModifierListSyntax? { + get { + return data.child(at: 3, parent: Syntax(self)).map(ModifierListSyntax.init) + } + set(value) { + self = MacroExpansionDeclSyntax(data.replacingChild(at: 3, with: value?.raw, arena: SyntaxArena())) + } + } + + /// Adds the provided `Modifier` to the node's `modifiers` + /// collection. + /// - param element: The new `Modifier` to add to the node's + /// `modifiers` collection. + /// - returns: A copy of the receiver with the provided `Modifier` + /// appended to its `modifiers` collection. + public func addModifier(_ element: DeclModifierSyntax) -> MacroExpansionDeclSyntax { + var collection: RawSyntax + let arena = SyntaxArena() + if let col = raw.layoutView!.children[3] { + collection = col.layoutView!.appending(element.raw, arena: arena) + } else { + collection = RawSyntax.makeLayout(kind: SyntaxKind.modifierList, + from: [element.raw], arena: arena) + } + let newData = data.replacingChild(at: 3, with: collection, arena: arena) + return MacroExpansionDeclSyntax(newData) + } + + public var unexpectedBetweenModifiersAndPoundToken: UnexpectedNodesSyntax? { + get { + return data.child(at: 4, parent: Syntax(self)).map(UnexpectedNodesSyntax.init) + } + set(value) { + self = MacroExpansionDeclSyntax(data.replacingChild(at: 4, with: value?.raw, arena: SyntaxArena())) + } + } + /// The `#` sign. public var poundToken: TokenSyntax { get { - return TokenSyntax(data.child(at: 1, parent: Syntax(self))!) + return TokenSyntax(data.child(at: 5, parent: Syntax(self))!) } set(value) { - self = MacroExpansionDeclSyntax(data.replacingChild(at: 1, with: value.raw, arena: SyntaxArena())) + self = MacroExpansionDeclSyntax(data.replacingChild(at: 5, with: value.raw, arena: SyntaxArena())) } } public var unexpectedBetweenPoundTokenAndMacro: UnexpectedNodesSyntax? { get { - return data.child(at: 2, parent: Syntax(self)).map(UnexpectedNodesSyntax.init) + return data.child(at: 6, parent: Syntax(self)).map(UnexpectedNodesSyntax.init) } set(value) { - self = MacroExpansionDeclSyntax(data.replacingChild(at: 2, with: value?.raw, arena: SyntaxArena())) + self = MacroExpansionDeclSyntax(data.replacingChild(at: 6, with: value?.raw, arena: SyntaxArena())) } } public var macro: TokenSyntax { get { - return TokenSyntax(data.child(at: 3, parent: Syntax(self))!) + return TokenSyntax(data.child(at: 7, parent: Syntax(self))!) } set(value) { - self = MacroExpansionDeclSyntax(data.replacingChild(at: 3, with: value.raw, arena: SyntaxArena())) + self = MacroExpansionDeclSyntax(data.replacingChild(at: 7, with: value.raw, arena: SyntaxArena())) } } public var unexpectedBetweenMacroAndGenericArguments: UnexpectedNodesSyntax? { get { - return data.child(at: 4, parent: Syntax(self)).map(UnexpectedNodesSyntax.init) + return data.child(at: 8, parent: Syntax(self)).map(UnexpectedNodesSyntax.init) } set(value) { - self = MacroExpansionDeclSyntax(data.replacingChild(at: 4, with: value?.raw, arena: SyntaxArena())) + self = MacroExpansionDeclSyntax(data.replacingChild(at: 8, with: value?.raw, arena: SyntaxArena())) } } public var genericArguments: GenericArgumentClauseSyntax? { get { - return data.child(at: 5, parent: Syntax(self)).map(GenericArgumentClauseSyntax.init) + return data.child(at: 9, parent: Syntax(self)).map(GenericArgumentClauseSyntax.init) } set(value) { - self = MacroExpansionDeclSyntax(data.replacingChild(at: 5, with: value?.raw, arena: SyntaxArena())) + self = MacroExpansionDeclSyntax(data.replacingChild(at: 9, with: value?.raw, arena: SyntaxArena())) } } public var unexpectedBetweenGenericArgumentsAndLeftParen: UnexpectedNodesSyntax? { get { - return data.child(at: 6, parent: Syntax(self)).map(UnexpectedNodesSyntax.init) + return data.child(at: 10, parent: Syntax(self)).map(UnexpectedNodesSyntax.init) } set(value) { - self = MacroExpansionDeclSyntax(data.replacingChild(at: 6, with: value?.raw, arena: SyntaxArena())) + self = MacroExpansionDeclSyntax(data.replacingChild(at: 10, with: value?.raw, arena: SyntaxArena())) } } public var leftParen: TokenSyntax? { get { - return data.child(at: 7, parent: Syntax(self)).map(TokenSyntax.init) + return data.child(at: 11, parent: Syntax(self)).map(TokenSyntax.init) } set(value) { - self = MacroExpansionDeclSyntax(data.replacingChild(at: 7, with: value?.raw, arena: SyntaxArena())) + self = MacroExpansionDeclSyntax(data.replacingChild(at: 11, with: value?.raw, arena: SyntaxArena())) } } public var unexpectedBetweenLeftParenAndArgumentList: UnexpectedNodesSyntax? { get { - return data.child(at: 8, parent: Syntax(self)).map(UnexpectedNodesSyntax.init) + return data.child(at: 12, parent: Syntax(self)).map(UnexpectedNodesSyntax.init) } set(value) { - self = MacroExpansionDeclSyntax(data.replacingChild(at: 8, with: value?.raw, arena: SyntaxArena())) + self = MacroExpansionDeclSyntax(data.replacingChild(at: 12, with: value?.raw, arena: SyntaxArena())) } } public var argumentList: TupleExprElementListSyntax { get { - return TupleExprElementListSyntax(data.child(at: 9, parent: Syntax(self))!) + return TupleExprElementListSyntax(data.child(at: 13, parent: Syntax(self))!) } set(value) { - self = MacroExpansionDeclSyntax(data.replacingChild(at: 9, with: value.raw, arena: SyntaxArena())) + self = MacroExpansionDeclSyntax(data.replacingChild(at: 13, with: value.raw, arena: SyntaxArena())) } } @@ -3797,67 +3883,67 @@ public struct MacroExpansionDeclSyntax: DeclSyntaxProtocol, SyntaxHashable { public func addArgument(_ element: TupleExprElementSyntax) -> MacroExpansionDeclSyntax { var collection: RawSyntax let arena = SyntaxArena() - if let col = raw.layoutView!.children[9] { + if let col = raw.layoutView!.children[13] { collection = col.layoutView!.appending(element.raw, arena: arena) } else { collection = RawSyntax.makeLayout(kind: SyntaxKind.tupleExprElementList, from: [element.raw], arena: arena) } - let newData = data.replacingChild(at: 9, with: collection, arena: arena) + let newData = data.replacingChild(at: 13, with: collection, arena: arena) return MacroExpansionDeclSyntax(newData) } public var unexpectedBetweenArgumentListAndRightParen: UnexpectedNodesSyntax? { get { - return data.child(at: 10, parent: Syntax(self)).map(UnexpectedNodesSyntax.init) + return data.child(at: 14, parent: Syntax(self)).map(UnexpectedNodesSyntax.init) } set(value) { - self = MacroExpansionDeclSyntax(data.replacingChild(at: 10, with: value?.raw, arena: SyntaxArena())) + self = MacroExpansionDeclSyntax(data.replacingChild(at: 14, with: value?.raw, arena: SyntaxArena())) } } public var rightParen: TokenSyntax? { get { - return data.child(at: 11, parent: Syntax(self)).map(TokenSyntax.init) + return data.child(at: 15, parent: Syntax(self)).map(TokenSyntax.init) } set(value) { - self = MacroExpansionDeclSyntax(data.replacingChild(at: 11, with: value?.raw, arena: SyntaxArena())) + self = MacroExpansionDeclSyntax(data.replacingChild(at: 15, with: value?.raw, arena: SyntaxArena())) } } public var unexpectedBetweenRightParenAndTrailingClosure: UnexpectedNodesSyntax? { get { - return data.child(at: 12, parent: Syntax(self)).map(UnexpectedNodesSyntax.init) + return data.child(at: 16, parent: Syntax(self)).map(UnexpectedNodesSyntax.init) } set(value) { - self = MacroExpansionDeclSyntax(data.replacingChild(at: 12, with: value?.raw, arena: SyntaxArena())) + self = MacroExpansionDeclSyntax(data.replacingChild(at: 16, with: value?.raw, arena: SyntaxArena())) } } public var trailingClosure: ClosureExprSyntax? { get { - return data.child(at: 13, parent: Syntax(self)).map(ClosureExprSyntax.init) + return data.child(at: 17, parent: Syntax(self)).map(ClosureExprSyntax.init) } set(value) { - self = MacroExpansionDeclSyntax(data.replacingChild(at: 13, with: value?.raw, arena: SyntaxArena())) + self = MacroExpansionDeclSyntax(data.replacingChild(at: 17, with: value?.raw, arena: SyntaxArena())) } } public var unexpectedBetweenTrailingClosureAndAdditionalTrailingClosures: UnexpectedNodesSyntax? { get { - return data.child(at: 14, parent: Syntax(self)).map(UnexpectedNodesSyntax.init) + return data.child(at: 18, parent: Syntax(self)).map(UnexpectedNodesSyntax.init) } set(value) { - self = MacroExpansionDeclSyntax(data.replacingChild(at: 14, with: value?.raw, arena: SyntaxArena())) + self = MacroExpansionDeclSyntax(data.replacingChild(at: 18, with: value?.raw, arena: SyntaxArena())) } } public var additionalTrailingClosures: MultipleTrailingClosureElementListSyntax? { get { - return data.child(at: 15, parent: Syntax(self)).map(MultipleTrailingClosureElementListSyntax.init) + return data.child(at: 19, parent: Syntax(self)).map(MultipleTrailingClosureElementListSyntax.init) } set(value) { - self = MacroExpansionDeclSyntax(data.replacingChild(at: 15, with: value?.raw, arena: SyntaxArena())) + self = MacroExpansionDeclSyntax(data.replacingChild(at: 19, with: value?.raw, arena: SyntaxArena())) } } @@ -3870,28 +3956,32 @@ public struct MacroExpansionDeclSyntax: DeclSyntaxProtocol, SyntaxHashable { public func addAdditionalTrailingClosure(_ element: MultipleTrailingClosureElementSyntax) -> MacroExpansionDeclSyntax { var collection: RawSyntax let arena = SyntaxArena() - if let col = raw.layoutView!.children[15] { + if let col = raw.layoutView!.children[19] { collection = col.layoutView!.appending(element.raw, arena: arena) } else { collection = RawSyntax.makeLayout(kind: SyntaxKind.multipleTrailingClosureElementList, from: [element.raw], arena: arena) } - let newData = data.replacingChild(at: 15, with: collection, arena: arena) + let newData = data.replacingChild(at: 19, with: collection, arena: arena) return MacroExpansionDeclSyntax(newData) } public var unexpectedAfterAdditionalTrailingClosures: UnexpectedNodesSyntax? { get { - return data.child(at: 16, parent: Syntax(self)).map(UnexpectedNodesSyntax.init) + return data.child(at: 20, parent: Syntax(self)).map(UnexpectedNodesSyntax.init) } set(value) { - self = MacroExpansionDeclSyntax(data.replacingChild(at: 16, with: value?.raw, arena: SyntaxArena())) + self = MacroExpansionDeclSyntax(data.replacingChild(at: 20, with: value?.raw, arena: SyntaxArena())) } } public static var structure: SyntaxNodeStructure { return .layout([ - \Self.unexpectedBeforePoundToken, + \Self.unexpectedBeforeAttributes, + \Self.attributes, + \Self.unexpectedBetweenAttributesAndModifiers, + \Self.modifiers, + \Self.unexpectedBetweenModifiersAndPoundToken, \Self.poundToken, \Self.unexpectedBetweenPoundTokenAndMacro, \Self.macro, diff --git a/Sources/SwiftSyntaxBuilder/generated/BuildableNodes.swift b/Sources/SwiftSyntaxBuilder/generated/BuildableNodes.swift index 6c6984730d8..0a16d5a56db 100644 --- a/Sources/SwiftSyntaxBuilder/generated/BuildableNodes.swift +++ b/Sources/SwiftSyntaxBuilder/generated/BuildableNodes.swift @@ -907,7 +907,11 @@ extension MacroExpansionDeclSyntax { /// A convenience initializer that allows initializing syntax collections using result builders public init( leadingTrivia: Trivia? = nil, - unexpectedBeforePoundToken: UnexpectedNodesSyntax? = nil, + unexpectedBeforeAttributes: UnexpectedNodesSyntax? = nil, + attributes: AttributeListSyntax? = nil, + unexpectedBetweenAttributesAndModifiers: UnexpectedNodesSyntax? = nil, + modifiers: ModifierListSyntax? = nil, + unexpectedBetweenModifiersAndPoundToken: UnexpectedNodesSyntax? = nil, poundToken: TokenSyntax = .poundToken(), unexpectedBetweenPoundTokenAndMacro: UnexpectedNodesSyntax? = nil, macro: TokenSyntax, @@ -928,7 +932,11 @@ extension MacroExpansionDeclSyntax { ) rethrows { try self.init( leadingTrivia: leadingTrivia, - unexpectedBeforePoundToken, + unexpectedBeforeAttributes, + attributes: attributes, + unexpectedBetweenAttributesAndModifiers, + modifiers: modifiers, + unexpectedBetweenModifiersAndPoundToken, poundToken: poundToken, unexpectedBetweenPoundTokenAndMacro, macro: macro, diff --git a/Tests/SwiftParserTest/DeclarationTests.swift b/Tests/SwiftParserTest/DeclarationTests.swift index 754381e4fdf..fa1844e1acd 100644 --- a/Tests/SwiftParserTest/DeclarationTests.swift +++ b/Tests/SwiftParserTest/DeclarationTests.swift @@ -1196,6 +1196,208 @@ final class DeclarationTests: XCTestCase { ) } + func testAttributedMacroExpansionDeclaration() { + assertParse( + """ + @attribute #topLevelWithAttr + public #topLevelWithModifier + #topLevelBare + """, + substructure: Syntax( + CodeBlockItemListSyntax([ + CodeBlockItemSyntax( + item: .decl( + DeclSyntax( + MacroExpansionDeclSyntax( + attributes: [.attribute(AttributeSyntax(attributeName: TypeSyntax("attribute")))], + macro: "topLevelWithAttr", + argumentList: [] + ) + ) + ) + ), + CodeBlockItemSyntax( + item: .decl( + DeclSyntax( + MacroExpansionDeclSyntax( + modifiers: [DeclModifierSyntax(name: .keyword(.public))], + macro: "topLevelWithModifier", + argumentList: [] + ) + ) + ) + ), + CodeBlockItemSyntax( + item: .expr( + ExprSyntax( + MacroExpansionExprSyntax( + macro: "topLevelBare", + argumentList: [] + ) + ) + ) + ), + ]) + ) + ) + + assertParse( + """ + struct S { + @attribute #memberWithAttr + public #memberWithModifier + #memberBare + } + """, + substructure: Syntax( + MemberDeclListSyntax([ + MemberDeclListItemSyntax( + decl: MacroExpansionDeclSyntax( + attributes: [.attribute(AttributeSyntax(attributeName: TypeSyntax("attribute")))], + macro: "memberWithAttr", + argumentList: [] + ) + ), + MemberDeclListItemSyntax( + decl: MacroExpansionDeclSyntax( + modifiers: [DeclModifierSyntax(name: .keyword(.public))], + macro: "memberWithModifier", + argumentList: [] + ) + ), + MemberDeclListItemSyntax( + decl: MacroExpansionDeclSyntax( + macro: "memberBare", + argumentList: [] + ) + ), + ]) + ) + ) + + assertParse( + """ + func test() { + @attribute #bodyWithAttr + public #bodyWithModifier + #bodyBare + } + """, + substructure: Syntax( + FunctionDeclSyntax( + identifier: .identifier("test"), + signature: FunctionSignatureSyntax( + input: ParameterClauseSyntax(parameterList: []) + ) + ) { + DeclSyntax( + MacroExpansionDeclSyntax( + attributes: [.attribute(AttributeSyntax(attributeName: TypeSyntax("attribute")))], + macro: "bodyWithAttr", + argumentList: [] + ) + ) + DeclSyntax( + MacroExpansionDeclSyntax( + modifiers: [DeclModifierSyntax(name: .keyword(.public))], + macro: "bodyWithModifier", + argumentList: [] + ) + ) + ExprSyntax( + MacroExpansionExprSyntax( + macro: "bodyBare", + argumentList: [] + ) + ) + } + ) + ) + + assertParse( + """ + func test() { + @attrib1 + @attrib2 + public + #declMacro + } + """, + substructure: Syntax( + FunctionDeclSyntax( + identifier: .identifier("test"), + signature: FunctionSignatureSyntax( + input: ParameterClauseSyntax(parameterList: []) + ) + ) { + DeclSyntax( + MacroExpansionDeclSyntax( + attributes: [ + .attribute(AttributeSyntax(attributeName: TypeSyntax("attrib1"))), + .attribute(AttributeSyntax(attributeName: TypeSyntax("attrib2"))), + ], + modifiers: [DeclModifierSyntax(name: .keyword(.public))], + macro: "declMacro", + argumentList: [] + ) + ) + } + ) + ) + + assertParse( + """ + struct S { + @attrib #1️⃣class + #2️⃣struct + } + """, + substructure: Syntax( + MemberDeclListSyntax([ + MemberDeclListItemSyntax( + decl: MacroExpansionDeclSyntax( + attributes: [.attribute(AttributeSyntax(attributeName: TypeSyntax("attrib")))], + poundToken: .poundToken(), + /*unexpectedBetweenPoundTokenAndMacro:*/ [ + TokenSyntax.keyword(.class) + ], + macro: .identifier("", presence: .missing), + argumentList: [] + ) + ), + MemberDeclListItemSyntax( + decl: MacroExpansionDeclSyntax( + poundToken: .poundToken(), + /*unexpectedBetweenPoundTokenAndMacro:*/ [ + TokenSyntax.keyword(.struct) + ], + macro: .identifier("", presence: .missing), + argumentList: [] + ) + ), + ]) + ), + diagnostics: [ + DiagnosticSpec( + locationMarker: "1️⃣", + message: "keyword 'class' cannot be used as an identifier here", + fixIts: ["if this name is unavoidable, use backticks to escape it"] + ), + DiagnosticSpec( + locationMarker: "2️⃣", + message: "keyword 'struct' cannot be used as an identifier here", + fixIts: ["if this name is unavoidable, use backticks to escape it"] + ), + ], + fixedSource: """ + struct S { + @attrib #`class` + #`struct` + } + """ + ) + } + func testVariableDeclWithGetSetButNoBrace() { assertParse( """ From 8b4d93cf542b7ccf0f442358e3ef31fa26e65f46 Mon Sep 17 00:00:00 2001 From: Rintaro Ishizaki Date: Thu, 11 May 2023 17:06:19 -0700 Subject: [PATCH 2/6] [Macros] Introduce SwiftSyntaxMacroExpansion module This module is a unified macro expansion logic shared between 'swift' ASTGen and SwiftCompilerPluginMessageHandling (cherry picked from commit c57b410149dc85b1bff5ab15263cce7b16e3af01) --- Package.swift | 9 +- Sources/CMakeLists.txt | 1 + .../CMakeLists.txt | 1 + .../Macros.swift | 186 +++------------- .../SwiftSyntaxMacroExpansion/CMakeLists.txt | 8 + .../MacroExpansion.swift | 203 ++++++++++++++++++ 6 files changed, 251 insertions(+), 157 deletions(-) create mode 100644 Sources/SwiftSyntaxMacroExpansion/CMakeLists.txt create mode 100644 Sources/SwiftSyntaxMacroExpansion/MacroExpansion.swift diff --git a/Package.swift b/Package.swift index bde40559291..818ca707056 100644 --- a/Package.swift +++ b/Package.swift @@ -57,6 +57,7 @@ let package = Package( .library(name: "SwiftSyntax", targets: ["SwiftSyntax"]), .library(name: "SwiftSyntaxBuilder", targets: ["SwiftSyntaxBuilder"]), .library(name: "SwiftSyntaxMacros", targets: ["SwiftSyntaxMacros"]), + .library(name: "SwiftSyntaxMacroExpansion", targets: ["SwiftSyntaxMacroExpansion"]), .library(name: "SwiftSyntaxMacrosTestSupport", targets: ["SwiftSyntaxMacrosTestSupport"]), ], targets: [ @@ -113,7 +114,7 @@ let package = Package( .target( name: "SwiftCompilerPluginMessageHandling", - dependencies: ["SwiftDiagnostics", "SwiftOperators", "SwiftParser", "SwiftSyntax", "SwiftSyntaxMacros"], + dependencies: ["SwiftDiagnostics", "SwiftOperators", "SwiftParser", "SwiftSyntax", "SwiftSyntaxMacros", "SwiftSyntaxMacroExpansion"], exclude: ["CMakeLists.txt"] ), @@ -178,6 +179,12 @@ let package = Package( exclude: ["CMakeLists.txt"] ), + .target( + name: "SwiftSyntaxMacroExpansion", + dependencies: ["SwiftSyntax", "SwiftSyntaxMacros"], + exclude: ["CMakeLists.txt"] + ), + .testTarget( name: "SwiftSyntaxMacrosTest", dependencies: ["_SwiftSyntaxTestSupport", "SwiftDiagnostics", "SwiftOperators", "SwiftParser", "SwiftSyntaxBuilder", "SwiftSyntaxMacros", "SwiftSyntaxMacrosTestSupport"] diff --git a/Sources/CMakeLists.txt b/Sources/CMakeLists.txt index bae4daa7df7..3b317b0891d 100644 --- a/Sources/CMakeLists.txt +++ b/Sources/CMakeLists.txt @@ -39,5 +39,6 @@ add_subdirectory(SwiftParserDiagnostics) add_subdirectory(SwiftOperators) add_subdirectory(SwiftSyntaxBuilder) add_subdirectory(SwiftSyntaxMacros) +add_subdirectory(SwiftSyntaxMacroExpansion) add_subdirectory(SwiftCompilerPluginMessageHandling) add_subdirectory(SwiftIDEUtils) diff --git a/Sources/SwiftCompilerPluginMessageHandling/CMakeLists.txt b/Sources/SwiftCompilerPluginMessageHandling/CMakeLists.txt index 657126f5034..aa8bd6a31dc 100644 --- a/Sources/SwiftCompilerPluginMessageHandling/CMakeLists.txt +++ b/Sources/SwiftCompilerPluginMessageHandling/CMakeLists.txt @@ -20,4 +20,5 @@ target_link_libraries(SwiftCompilerPluginMessageHandling PUBLIC SwiftDiagnostics SwiftParser SwiftSyntaxMacros + SwiftSyntaxMacroExpansion SwiftOperators) diff --git a/Sources/SwiftCompilerPluginMessageHandling/Macros.swift b/Sources/SwiftCompilerPluginMessageHandling/Macros.swift index 4675d0ea019..c56e82c1833 100644 --- a/Sources/SwiftCompilerPluginMessageHandling/Macros.swift +++ b/Sources/SwiftCompilerPluginMessageHandling/Macros.swift @@ -14,6 +14,7 @@ import SwiftBasicFormat import SwiftDiagnostics import SwiftSyntax import SwiftSyntaxMacros +import SwiftSyntaxMacroExpansion extension CompilerPluginMessageHandler { /// Get concrete macro type from a pair of module name and type name. @@ -35,7 +36,7 @@ extension CompilerPluginMessageHandler { expansionDiscriminator: discriminator ) - let expandedSource: String + let expandedSource: String? do { guard let macroSyntax = syntax.asProtocol(FreestandingMacroExpansionSyntax.self) else { throw MacroExpansionError.freestandingMacroSyntaxIsNotMacro @@ -44,34 +45,14 @@ extension CompilerPluginMessageHandler { throw MacroExpansionError.macroTypeNotFound } - switch macroDefinition { - case let exprMacroDef as ExpressionMacro.Type: - func _expand(node: Node) throws -> ExprSyntax { - try exprMacroDef.expansion(of: node, in: context) - } - let rewritten = try _openExistential(macroSyntax, do: _expand) - expandedSource = rewritten.formattedExpansion(macroDefinition.formatMode) - - case let declMacroDef as DeclarationMacro.Type: - func _expand(node: Node) throws -> [DeclSyntax] { - try declMacroDef.expansion(of: node, in: context) - } - let rewritten = try _openExistential(macroSyntax, do: _expand) - expandedSource = CodeBlockItemListSyntax(rewritten.map { CodeBlockItemSyntax(item: .decl($0)) }).formattedExpansion(macroDefinition.formatMode) - - case let codeItemMacroDef as CodeItemMacro.Type: - func _expand(node: Node) throws -> [CodeBlockItemSyntax] { - try codeItemMacroDef.expansion(of: node, in: context) - } - let rewritten = try _openExistential(macroSyntax, do: _expand) - expandedSource = CodeBlockItemListSyntax(rewritten).formattedExpansion(macroDefinition.formatMode) - - default: - throw MacroExpansionError.unmathedMacroRole - } + expandedSource = SwiftSyntaxMacroExpansion.expandFreestandingMacro( + definition: macroDefinition, + node: macroSyntax, + in: context + ) } catch { context.addDiagnostics(from: error, node: syntax) - expandedSource = "" + expandedSource = nil } let diagnostics = context.diagnostics.map { @@ -99,132 +80,25 @@ extension CompilerPluginMessageHandler { let attributeNode = sourceManager.add(attributeSyntax).cast(AttributeSyntax.self) let declarationNode = sourceManager.add(declSyntax).cast(DeclSyntax.self) + let parentDeclNode = parentDeclSyntax.map { sourceManager.add($0).cast(DeclSyntax.self) } - let expandedSources: [String] + let expandedSources: [String]? do { guard let macroDefinition = resolveMacro(macro) else { throw MacroExpansionError.macroTypeNotFound } - switch (macroDefinition, macroRole) { - case (let attachedMacro as AccessorMacro.Type, .accessor): - let accessors = try attachedMacro.expansion( - of: attributeNode, - providingAccessorsOf: declarationNode, - in: context - ) - expandedSources = accessors.map { - $0.formattedExpansion(macroDefinition.formatMode) - } - - case (let attachedMacro as MemberAttributeMacro.Type, .memberAttribute): - guard - let parentDeclSyntax = parentDeclSyntax, - let parentDeclGroup = sourceManager.add(parentDeclSyntax).asProtocol(DeclGroupSyntax.self) - else { - // Compiler error: 'parentDecl' is mandatory for MemberAttributeMacro. - throw MacroExpansionError.invalidExpansionMessage - } - - // Local function to expand a member atribute macro once we've opened up - // the existential. - func expandMemberAttributeMacro( - _ node: Node - ) throws -> [AttributeSyntax] { - return try attachedMacro.expansion( - of: attributeNode, - attachedTo: node, - providingAttributesFor: declarationNode, - in: context - ) - } - - let attributes = try _openExistential( - parentDeclGroup, - do: expandMemberAttributeMacro - ) - - // Form a buffer containing an attribute list to return to the caller. - expandedSources = attributes.map { - $0.formattedExpansion(macroDefinition.formatMode) - } - - case (let attachedMacro as MemberMacro.Type, .member): - guard let declGroup = declarationNode.asProtocol(DeclGroupSyntax.self) - else { - // Compiler error: declNode for member macro must be DeclGroupSyntax. - throw MacroExpansionError.invalidExpansionMessage - } - - // Local function to expand a member macro once we've opened up - // the existential. - func expandMemberMacro( - _ node: Node - ) throws -> [DeclSyntax] { - return try attachedMacro.expansion( - of: attributeNode, - providingMembersOf: node, - in: context - ) - } - - let members = try _openExistential(declGroup, do: expandMemberMacro) - - // Form a buffer of member declarations to return to the caller. - expandedSources = members.map { $0.formattedExpansion(macroDefinition.formatMode) } - - case (let attachedMacro as PeerMacro.Type, .peer): - let peers = try attachedMacro.expansion( - of: attributeNode, - providingPeersOf: declarationNode, - in: context - ) - - // Form a buffer of peer declarations to return to the caller. - expandedSources = peers.map { - $0.formattedExpansion(macroDefinition.formatMode) - } - - case (let attachedMacro as ConformanceMacro.Type, .conformance): - guard - let declGroup = declarationNode.asProtocol(DeclGroupSyntax.self), - let identified = declarationNode.asProtocol(IdentifiedDeclSyntax.self) - else { - // Compiler error: type mismatch. - throw MacroExpansionError.invalidExpansionMessage - } - - // Local function to expand a conformance macro once we've opened up - // the existential. - func expandConformanceMacro( - _ node: Node - ) throws -> [(TypeSyntax, GenericWhereClauseSyntax?)] { - return try attachedMacro.expansion( - of: attributeNode, - providingConformancesOf: node, - in: context - ) - } - - let conformances = try _openExistential( - declGroup, - do: expandConformanceMacro - ) - - // Form a buffer of extension declarations to return to the caller. - expandedSources = conformances.map { typeSyntax, whereClause in - let typeName = identified.identifier.trimmedDescription - let protocolName = typeSyntax.trimmedDescription - let whereClause = whereClause?.trimmedDescription ?? "" - return "extension \(typeName) : \(protocolName) \(whereClause) {}" - } - - default: - throw MacroExpansionError.unmathedMacroRole - } + expandedSources = SwiftSyntaxMacroExpansion.expandAttachedMacro( + definition: macroDefinition, + macroRole: MacroRole(messageMacroRole: macroRole), + attributeNode: attributeNode, + declarationNode: declarationNode, + parentDeclNode: parentDeclNode, + in: context + ) } catch { context.addDiagnostics(from: error, node: attributeNode) - expandedSources = [] + expandedSources = nil } let diagnostics = context.diagnostics.map { @@ -236,17 +110,17 @@ extension CompilerPluginMessageHandler { } } -fileprivate extension SyntaxProtocol { - /// Perform a format if required and then trim any leading/trailing - /// whitespace. - func formattedExpansion(_ mode: FormatMode) -> String { - let formatted: Syntax - switch mode { - case .auto: - formatted = self.formatted() - case .disabled: - formatted = Syntax(self) +private extension MacroRole { + init(messageMacroRole: PluginMessage.MacroRole) { + switch messageMacroRole { + case .expression: self = .expression + case .declaration: self = .declaration + case .accessor: self = .accessor + case .memberAttribute: self = .memberAttribute + case .member: self = .member + case .peer: self = .peer + case .conformance: self = .conformance + case .codeItem: self = .codeItem } - return formatted.trimmedDescription(matching: { $0.isWhitespace }) } } diff --git a/Sources/SwiftSyntaxMacroExpansion/CMakeLists.txt b/Sources/SwiftSyntaxMacroExpansion/CMakeLists.txt new file mode 100644 index 00000000000..2a043c00719 --- /dev/null +++ b/Sources/SwiftSyntaxMacroExpansion/CMakeLists.txt @@ -0,0 +1,8 @@ +add_swift_host_library(SwiftSyntaxMacroExpansion + MacroExpansion.swift +) + +target_link_libraries(SwiftSyntaxMacroExpansion PUBLIC + SwiftSyntax + SwiftSyntaxMacros +) diff --git a/Sources/SwiftSyntaxMacroExpansion/MacroExpansion.swift b/Sources/SwiftSyntaxMacroExpansion/MacroExpansion.swift new file mode 100644 index 00000000000..282dbb1801c --- /dev/null +++ b/Sources/SwiftSyntaxMacroExpansion/MacroExpansion.swift @@ -0,0 +1,203 @@ +import SwiftSyntax +import SwiftSyntaxMacros + +public enum MacroRole { + case expression + case declaration + case accessor + case memberAttribute + case member + case peer + case conformance + case codeItem +} + +/// Simple diagnostic message +private struct MacroExpansionError: Error, CustomStringConvertible { + let description: String +} + +/// Expand `@freestanding(XXX)` macros. +public func expandFreestandingMacro( + definition: Macro.Type, + node: FreestandingMacroExpansionSyntax, + in context: some MacroExpansionContext +) -> String? { + do { + func _expand(node: some FreestandingMacroExpansionSyntax) throws -> String { + let expandedSyntax: Syntax + switch definition { + case let exprMacroDef as ExpressionMacro.Type: + expandedSyntax = try Syntax(exprMacroDef.expansion(of: node, in: context)) + + case let declMacroDef as DeclarationMacro.Type: + let rewritten = try declMacroDef.expansion(of: node, in: context) + expandedSyntax = Syntax( + CodeBlockItemListSyntax( + rewritten.map { + CodeBlockItemSyntax(item: .decl($0)) + } + ) + ) + + case let codeItemMacroDef as CodeItemMacro.Type: + let rewritten = try codeItemMacroDef.expansion(of: node, in: context) + expandedSyntax = Syntax(CodeBlockItemListSyntax(rewritten)) + + default: + throw MacroExpansionError(description: "macro doesn't conform to required macro role") + } + return expandedSyntax.formattedExpansion(definition.formatMode) + } + return try _openExistential(node, do: _expand) + } catch { + context.addDiagnostics(from: error, node: node) + return nil + } +} + +/// Expand `@attached(XXX)` macros. +public func expandAttachedMacro( + definition: Macro.Type, + macroRole: MacroRole, + attributeNode: AttributeSyntax, + declarationNode: DeclSyntax, + parentDeclNode: DeclSyntax?, + in context: Context +) -> [String]? { + do { + switch (definition, macroRole) { + case (let attachedMacro as AccessorMacro.Type, .accessor): + let accessors = try attachedMacro.expansion( + of: attributeNode, + providingAccessorsOf: declarationNode, + in: context + ) + return accessors.map { + $0.formattedExpansion(definition.formatMode) + } + + case (let attachedMacro as MemberAttributeMacro.Type, .memberAttribute): + guard + let parentDeclGroup = parentDeclNode?.asProtocol(DeclGroupSyntax.self) + else { + // Compiler error: 'parentDecl' is mandatory for MemberAttributeMacro. + throw MacroExpansionError(description: "parent decl group is nil") + } + + // Local function to expand a member attribute macro once we've opened up + // the existential. + func expandMemberAttributeMacro( + _ node: some DeclGroupSyntax + ) throws -> [AttributeSyntax] { + return try attachedMacro.expansion( + of: attributeNode, + attachedTo: node, + providingAttributesFor: declarationNode, + in: context + ) + } + + let attributes = try _openExistential( + parentDeclGroup, + do: expandMemberAttributeMacro + ) + + // Form a buffer containing an attribute list to return to the caller. + return attributes.map { + $0.formattedExpansion(definition.formatMode) + } + + case (let attachedMacro as MemberMacro.Type, .member): + guard let declGroup = declarationNode.asProtocol(DeclGroupSyntax.self) + else { + // Compiler error: declNode for member macro must be DeclGroupSyntax. + throw MacroExpansionError(description: "declaration is not a decl group syntax") + } + + // Local function to expand a member macro once we've opened up + // the existential. + func expandMemberMacro( + _ node: some DeclGroupSyntax + ) throws -> [DeclSyntax] { + return try attachedMacro.expansion( + of: attributeNode, + providingMembersOf: node, + in: context + ) + } + + let members = try _openExistential(declGroup, do: expandMemberMacro) + + // Form a buffer of member declarations to return to the caller. + return members.map { $0.formattedExpansion(definition.formatMode) } + + case (let attachedMacro as PeerMacro.Type, .peer): + let peers = try attachedMacro.expansion( + of: attributeNode, + providingPeersOf: declarationNode, + in: context + ) + + // Form a buffer of peer declarations to return to the caller. + return peers.map { + $0.formattedExpansion(definition.formatMode) + } + + case (let attachedMacro as ConformanceMacro.Type, .conformance): + guard + let declGroup = declarationNode.asProtocol(DeclGroupSyntax.self), + let identified = declarationNode.asProtocol(IdentifiedDeclSyntax.self) + else { + // Compiler error: type mismatch. + throw MacroExpansionError(description: "declaration is not a identified decl group") + } + + // Local function to expand a conformance macro once we've opened up + // the existential. + func expandConformanceMacro( + _ node: some DeclGroupSyntax + ) throws -> [(TypeSyntax, GenericWhereClauseSyntax?)] { + return try attachedMacro.expansion( + of: attributeNode, + providingConformancesOf: node, + in: context + ) + } + + let conformances = try _openExistential( + declGroup, + do: expandConformanceMacro + ) + + // Form a buffer of extension declarations to return to the caller. + return conformances.map { typeSyntax, whereClause in + let typeName = identified.identifier.trimmedDescription + let protocolName = typeSyntax.trimmedDescription + let whereClause = whereClause?.trimmedDescription ?? "" + return "extension \(typeName) : \(protocolName) \(whereClause) {}" + } + + default: + throw MacroExpansionError(description: "macro doesn't conform to required macro role") + } + } catch { + context.addDiagnostics(from: error, node: attributeNode) + return nil + } +} + +fileprivate extension SyntaxProtocol { + /// Perform a format if required and then trim any leading/trailing + /// whitespace. + func formattedExpansion(_ mode: FormatMode) -> String { + let formatted: Syntax + switch mode { + case .auto: + formatted = self.formatted() + case .disabled: + formatted = Syntax(self) + } + return formatted.trimmedDescription(matching: { $0.isWhitespace }) + } +} From 227b0d6d18d8e531613724a9c1de83af94a4dfef Mon Sep 17 00:00:00 2001 From: Rintaro Ishizaki Date: Fri, 12 May 2023 10:39:05 -0700 Subject: [PATCH 3/6] Rename traits 'Attributed' to 'WithAttributes', introduce 'WithModifiers' (cherry picked from commit 992a9da578b70847499b6d51cdf67f4b12abced7) --- .../Sources/SyntaxSupport/CommonNodes.swift | 3 +- .../Sources/SyntaxSupport/DeclNodes.swift | 65 ++++--- .../Sources/SyntaxSupport/ExprNodes.swift | 6 +- .../Sources/SyntaxSupport/GenericNodes.swift | 2 +- .../Sources/SyntaxSupport/Traits.swift | 18 +- .../Sources/SyntaxSupport/TypeNodes.swift | 2 +- .../generated/SwiftSyntax.md | 3 +- .../SwiftSyntaxCompatibility.swift | 3 + .../SwiftSyntax/generated/SyntaxTraits.swift | 162 +++++++++++------- Sources/SwiftSyntaxMacros/MacroSystem.swift | 8 +- 10 files changed, 171 insertions(+), 101 deletions(-) diff --git a/CodeGeneration/Sources/SyntaxSupport/CommonNodes.swift b/CodeGeneration/Sources/SyntaxSupport/CommonNodes.swift index 5d7fbaa35bb..098a4af7877 100644 --- a/CodeGeneration/Sources/SyntaxSupport/CommonNodes.swift +++ b/CodeGeneration/Sources/SyntaxSupport/CommonNodes.swift @@ -147,7 +147,8 @@ public let COMMON_NODES: [Node] = [ description: "In case the source code is missing a declaration, this node stands in place of the missing declaration.", kind: "Decl", traits: [ - "Attributed" + "WithAttributes", + "WithModifiers", ], children: [ Child( diff --git a/CodeGeneration/Sources/SyntaxSupport/DeclNodes.swift b/CodeGeneration/Sources/SyntaxSupport/DeclNodes.swift index d06221a6927..36c418caa42 100644 --- a/CodeGeneration/Sources/SyntaxSupport/DeclNodes.swift +++ b/CodeGeneration/Sources/SyntaxSupport/DeclNodes.swift @@ -68,7 +68,7 @@ public let DECL_NODES: [Node] = [ nameForDiagnostics: "accessor", kind: "Decl", traits: [ - "Attributed" + "WithAttributes" ], parserFunction: "parseAccessorDecl", children: [ @@ -154,7 +154,8 @@ public let DECL_NODES: [Node] = [ traits: [ "DeclGroup", "IdentifiedDecl", - "Attributed", + "WithAttributes", + "WithModifiers", ], children: [ Child( @@ -213,7 +214,8 @@ public let DECL_NODES: [Node] = [ kind: "Decl", traits: [ "IdentifiedDecl", - "Attributed", + "WithAttributes", + "WithModifiers", ], children: [ Child( @@ -270,7 +272,8 @@ public let DECL_NODES: [Node] = [ traits: [ "DeclGroup", "IdentifiedDecl", - "Attributed", + "WithAttributes", + "WithModifiers", ], children: [ Child( @@ -401,7 +404,8 @@ public let DECL_NODES: [Node] = [ nameForDiagnostics: "deinitializer", kind: "Decl", traits: [ - "Attributed" + "WithAttributes", + "WithModifiers", ], children: [ Child( @@ -505,7 +509,8 @@ public let DECL_NODES: [Node] = [ nameForDiagnostics: "parameter", kind: "Syntax", traits: [ - "WithTrailingComma" + "WithTrailingComma", + "WithModifiers", ], parserFunction: "parseEnumCaseParameter", children: [ @@ -559,7 +564,8 @@ public let DECL_NODES: [Node] = [ description: "A `case` declaration of a Swift `enum`. It can have 1 or more `EnumCaseElement`s inside, each declaring a different case of the enum.", kind: "Decl", traits: [ - "Attributed" + "WithAttributes", + "WithModifiers", ], children: [ Child( @@ -642,7 +648,8 @@ public let DECL_NODES: [Node] = [ traits: [ "DeclGroup", "IdentifiedDecl", - "Attributed", + "WithAttributes", + "WithModifiers", ], children: [ Child( @@ -710,7 +717,8 @@ public let DECL_NODES: [Node] = [ kind: "Decl", traits: [ "DeclGroup", - "Attributed", + "WithAttributes", + "WithModifiers", ], children: [ Child( @@ -758,7 +766,8 @@ public let DECL_NODES: [Node] = [ kind: "Decl", traits: [ "IdentifiedDecl", - "Attributed", + "WithAttributes", + "WithModifiers", ], children: [ Child( @@ -822,7 +831,8 @@ public let DECL_NODES: [Node] = [ kind: "Syntax", traits: [ "WithTrailingComma", - "Attributed", + "WithAttributes", + "WithModifiers", ], parserFunction: "parseFunctionParameter", children: [ @@ -983,7 +993,8 @@ public let DECL_NODES: [Node] = [ nameForDiagnostics: "import", kind: "Decl", traits: [ - "Attributed" + "WithAttributes", + "WithModifiers", ], children: [ Child( @@ -1073,7 +1084,8 @@ public let DECL_NODES: [Node] = [ """, kind: "Decl", traits: [ - "Attributed" + "WithAttributes", + "WithModifiers", ], children: [ Child( @@ -1136,7 +1148,8 @@ public let DECL_NODES: [Node] = [ kind: "Decl", traits: [ "IdentifiedDecl", - "Attributed", + "WithAttributes", + "WithModifiers", ], children: [ Child( @@ -1191,7 +1204,9 @@ public let DECL_NODES: [Node] = [ nameForDiagnostics: "macro expansion", kind: "Decl", traits: [ - "FreestandingMacroExpansion" + "FreestandingMacroExpansion", + "WithAttributes", + "WithModifiers", ], children: [ Child( @@ -1343,7 +1358,8 @@ public let DECL_NODES: [Node] = [ kind: "Decl", traits: [ "IdentifiedDecl", - "Attributed", + "WithAttributes", + "WithModifiers", ], children: [ Child( @@ -1619,7 +1635,8 @@ public let DECL_NODES: [Node] = [ kind: "Decl", traits: [ "IdentifiedDecl", - "Attributed", + "WithAttributes", + "WithModifiers", ], children: [ Child( @@ -1721,7 +1738,8 @@ public let DECL_NODES: [Node] = [ traits: [ "DeclGroup", "IdentifiedDecl", - "Attributed", + "WithAttributes", + "WithModifiers", ], children: [ Child( @@ -1823,7 +1841,8 @@ public let DECL_NODES: [Node] = [ traits: [ "DeclGroup", "IdentifiedDecl", - "Attributed", + "WithAttributes", + "WithModifiers", ], children: [ Child( @@ -1876,7 +1895,8 @@ public let DECL_NODES: [Node] = [ nameForDiagnostics: "subscript", kind: "Decl", traits: [ - "Attributed" + "WithAttributes", + "WithModifiers", ], children: [ Child( @@ -1977,7 +1997,7 @@ public let DECL_NODES: [Node] = [ kind: "Decl", traits: [ "IdentifiedDecl", - "Attributed", + "WithAttributes", ], children: [ Child( @@ -2024,7 +2044,8 @@ public let DECL_NODES: [Node] = [ nameForDiagnostics: "variable", kind: "Decl", traits: [ - "Attributed" + "WithAttributes", + "WithModifiers", ], children: [ Child( diff --git a/CodeGeneration/Sources/SyntaxSupport/ExprNodes.swift b/CodeGeneration/Sources/SyntaxSupport/ExprNodes.swift index 79cd9f01ed0..f45bd5f2254 100644 --- a/CodeGeneration/Sources/SyntaxSupport/ExprNodes.swift +++ b/CodeGeneration/Sources/SyntaxSupport/ExprNodes.swift @@ -306,7 +306,9 @@ public let EXPR_NODES: [Node] = [ nameForDiagnostics: "parameter", kind: "Syntax", traits: [ - "WithTrailingComma" + "WithTrailingComma", + "WithAttributes", + "WithModifiers", ], parserFunction: "parseClosureParameter", children: [ @@ -461,7 +463,7 @@ public let EXPR_NODES: [Node] = [ nameForDiagnostics: "closure signature", kind: "Syntax", traits: [ - "Attributed" + "WithAttributes" ], children: [ Child( diff --git a/CodeGeneration/Sources/SyntaxSupport/GenericNodes.swift b/CodeGeneration/Sources/SyntaxSupport/GenericNodes.swift index a08ed27113d..7fe9ee2a16a 100644 --- a/CodeGeneration/Sources/SyntaxSupport/GenericNodes.swift +++ b/CodeGeneration/Sources/SyntaxSupport/GenericNodes.swift @@ -75,7 +75,7 @@ public let GENERIC_NODES: [Node] = [ kind: "Syntax", traits: [ "WithTrailingComma", - "Attributed", + "WithAttributes", ], children: [ Child( diff --git a/CodeGeneration/Sources/SyntaxSupport/Traits.swift b/CodeGeneration/Sources/SyntaxSupport/Traits.swift index 62a097b7442..bd9976c0453 100644 --- a/CodeGeneration/Sources/SyntaxSupport/Traits.swift +++ b/CodeGeneration/Sources/SyntaxSupport/Traits.swift @@ -23,12 +23,6 @@ public class Trait { } public let TRAITS: [Trait] = [ - Trait( - traitName: "Attributed", - children: [ - Child(name: "Attributes", kind: .node(kind: "AttributeList"), isOptional: true) - ] - ), Trait( traitName: "Braced", children: [ @@ -80,12 +74,24 @@ public let TRAITS: [Trait] = [ Child(name: "RightParen", kind: .token(choices: [.token(tokenKind: "RightParenToken")])), ] ), + Trait( + traitName: "WithAttributes", + children: [ + Child(name: "Attributes", kind: .node(kind: "AttributeList"), isOptional: true) + ] + ), Trait( traitName: "WithCodeBlock", children: [ Child(name: "Body", kind: .node(kind: "CodeBlock")) ] ), + Trait( + traitName: "WithModifiers", + children: [ + Child(name: "Modifiers", kind: .node(kind: "ModifierList"), isOptional: true) + ] + ), Trait( traitName: "WithStatements", children: [ diff --git a/CodeGeneration/Sources/SyntaxSupport/TypeNodes.swift b/CodeGeneration/Sources/SyntaxSupport/TypeNodes.swift index 60a07d9e52a..30bf6a95a9a 100644 --- a/CodeGeneration/Sources/SyntaxSupport/TypeNodes.swift +++ b/CodeGeneration/Sources/SyntaxSupport/TypeNodes.swift @@ -39,7 +39,7 @@ public let TYPE_NODES: [Node] = [ nameForDiagnostics: "type", kind: "Type", traits: [ - "Attributed" + "WithAttributes" ], children: [ Child( diff --git a/Sources/SwiftSyntax/Documentation.docc/generated/SwiftSyntax.md b/Sources/SwiftSyntax/Documentation.docc/generated/SwiftSyntax.md index 2173fe8de97..75198a232b9 100644 --- a/Sources/SwiftSyntax/Documentation.docc/generated/SwiftSyntax.md +++ b/Sources/SwiftSyntax/Documentation.docc/generated/SwiftSyntax.md @@ -358,14 +358,15 @@ allows Swift tools to parse, inspect, generate, and transform Swift source code. ### Traits -- - - - - - - +- - +- - - diff --git a/Sources/SwiftSyntax/SwiftSyntaxCompatibility.swift b/Sources/SwiftSyntax/SwiftSyntaxCompatibility.swift index 4c7239cfbe3..0c328af00ae 100644 --- a/Sources/SwiftSyntax/SwiftSyntaxCompatibility.swift +++ b/Sources/SwiftSyntax/SwiftSyntaxCompatibility.swift @@ -19,3 +19,6 @@ public extension DeclGroupSyntax { return self.memberBlock } } + +@available(*, deprecated, renamed: "WithAttributesSyntax") +public typealias AttributedSyntax = WithAttributesSyntax diff --git a/Sources/SwiftSyntax/generated/SyntaxTraits.swift b/Sources/SwiftSyntax/generated/SyntaxTraits.swift index 00f641eff88..da04cbb8e45 100644 --- a/Sources/SwiftSyntax/generated/SyntaxTraits.swift +++ b/Sources/SwiftSyntax/generated/SyntaxTraits.swift @@ -12,42 +12,6 @@ // //===----------------------------------------------------------------------===// -// MARK: - AttributedSyntax - -public protocol AttributedSyntax: SyntaxProtocol { - var attributes: AttributeListSyntax? { - get - set - } -} - -public extension AttributedSyntax { - /// Without this function, the `with` function defined on `SyntaxProtocol` - /// does not work on existentials of this protocol type. - @_disfavoredOverload - func with(_ keyPath: WritableKeyPath, _ newChild: T) -> AttributedSyntax { - var copy: AttributedSyntax = self - copy[keyPath: keyPath] = newChild - return copy - } -} - -public extension SyntaxProtocol { - /// Check whether the non-type erased version of this syntax node conforms to - /// `AttributedSyntax`. - /// Note that this will incur an existential conversion. - func isProtocol(_: AttributedSyntax.Protocol) -> Bool { - return self.asProtocol(AttributedSyntax.self) != nil - } - - /// Return the non-type erased version of this syntax node if it conforms to - /// `AttributedSyntax`. Otherwise return `nil`. - /// Note that this will incur an existential conversion. - func asProtocol(_: AttributedSyntax.Protocol) -> AttributedSyntax? { - return Syntax(self).asProtocol(SyntaxProtocol.self) as? AttributedSyntax - } -} - // MARK: - BracedSyntax public protocol BracedSyntax: SyntaxProtocol { @@ -339,6 +303,42 @@ public extension SyntaxProtocol { } } +// MARK: - WithAttributesSyntax + +public protocol WithAttributesSyntax: SyntaxProtocol { + var attributes: AttributeListSyntax? { + get + set + } +} + +public extension WithAttributesSyntax { + /// Without this function, the `with` function defined on `SyntaxProtocol` + /// does not work on existentials of this protocol type. + @_disfavoredOverload + func with(_ keyPath: WritableKeyPath, _ newChild: T) -> WithAttributesSyntax { + var copy: WithAttributesSyntax = self + copy[keyPath: keyPath] = newChild + return copy + } +} + +public extension SyntaxProtocol { + /// Check whether the non-type erased version of this syntax node conforms to + /// `WithAttributesSyntax`. + /// Note that this will incur an existential conversion. + func isProtocol(_: WithAttributesSyntax.Protocol) -> Bool { + return self.asProtocol(WithAttributesSyntax.self) != nil + } + + /// Return the non-type erased version of this syntax node if it conforms to + /// `WithAttributesSyntax`. Otherwise return `nil`. + /// Note that this will incur an existential conversion. + func asProtocol(_: WithAttributesSyntax.Protocol) -> WithAttributesSyntax? { + return Syntax(self).asProtocol(SyntaxProtocol.self) as? WithAttributesSyntax + } +} + // MARK: - WithCodeBlockSyntax public protocol WithCodeBlockSyntax: SyntaxProtocol { @@ -375,6 +375,42 @@ public extension SyntaxProtocol { } } +// MARK: - WithModifiersSyntax + +public protocol WithModifiersSyntax: SyntaxProtocol { + var modifiers: ModifierListSyntax? { + get + set + } +} + +public extension WithModifiersSyntax { + /// Without this function, the `with` function defined on `SyntaxProtocol` + /// does not work on existentials of this protocol type. + @_disfavoredOverload + func with(_ keyPath: WritableKeyPath, _ newChild: T) -> WithModifiersSyntax { + var copy: WithModifiersSyntax = self + copy[keyPath: keyPath] = newChild + return copy + } +} + +public extension SyntaxProtocol { + /// Check whether the non-type erased version of this syntax node conforms to + /// `WithModifiersSyntax`. + /// Note that this will incur an existential conversion. + func isProtocol(_: WithModifiersSyntax.Protocol) -> Bool { + return self.asProtocol(WithModifiersSyntax.self) != nil + } + + /// Return the non-type erased version of this syntax node if it conforms to + /// `WithModifiersSyntax`. Otherwise return `nil`. + /// Note that this will incur an existential conversion. + func asProtocol(_: WithModifiersSyntax.Protocol) -> WithModifiersSyntax? { + return Syntax(self).asProtocol(SyntaxProtocol.self) as? WithModifiersSyntax + } +} + // MARK: - WithStatementsSyntax public protocol WithStatementsSyntax: SyntaxProtocol { @@ -449,19 +485,19 @@ public extension SyntaxProtocol { extension AccessorBlockSyntax: BracedSyntax {} -extension AccessorDeclSyntax: AttributedSyntax {} +extension AccessorDeclSyntax: WithAttributesSyntax {} extension AccessorEffectSpecifiersSyntax: EffectSpecifiersSyntax {} extension AccessorParameterSyntax: ParenthesizedSyntax {} -extension ActorDeclSyntax: DeclGroupSyntax, IdentifiedDeclSyntax, AttributedSyntax {} +extension ActorDeclSyntax: DeclGroupSyntax, IdentifiedDeclSyntax, WithAttributesSyntax, WithModifiersSyntax {} extension ArrayElementSyntax: WithTrailingCommaSyntax {} -extension AssociatedtypeDeclSyntax: IdentifiedDeclSyntax, AttributedSyntax {} +extension AssociatedtypeDeclSyntax: IdentifiedDeclSyntax, WithAttributesSyntax, WithModifiersSyntax {} -extension AttributedTypeSyntax: AttributedSyntax {} +extension AttributedTypeSyntax: WithAttributesSyntax {} extension CaseItemSyntax: WithTrailingCommaSyntax {} @@ -469,7 +505,7 @@ extension CatchClauseSyntax: WithCodeBlockSyntax {} extension CatchItemSyntax: WithTrailingCommaSyntax {} -extension ClassDeclSyntax: DeclGroupSyntax, IdentifiedDeclSyntax, AttributedSyntax {} +extension ClassDeclSyntax: DeclGroupSyntax, IdentifiedDeclSyntax, WithAttributesSyntax, WithModifiersSyntax {} extension ClosureCaptureItemSyntax: WithTrailingCommaSyntax {} @@ -479,9 +515,9 @@ extension ClosureParamSyntax: WithTrailingCommaSyntax {} extension ClosureParameterClauseSyntax: ParenthesizedSyntax {} -extension ClosureParameterSyntax: WithTrailingCommaSyntax {} +extension ClosureParameterSyntax: WithTrailingCommaSyntax, WithAttributesSyntax, WithModifiersSyntax {} -extension ClosureSignatureSyntax: AttributedSyntax {} +extension ClosureSignatureSyntax: WithAttributesSyntax {} extension CodeBlockSyntax: BracedSyntax, WithStatementsSyntax {} @@ -493,7 +529,7 @@ extension DeclNameArgumentsSyntax: ParenthesizedSyntax {} extension DeferStmtSyntax: WithCodeBlockSyntax {} -extension DeinitializerDeclSyntax: AttributedSyntax {} +extension DeinitializerDeclSyntax: WithAttributesSyntax, WithModifiersSyntax {} extension DictionaryElementSyntax: WithTrailingCommaSyntax {} @@ -503,33 +539,33 @@ extension DoStmtSyntax: WithCodeBlockSyntax {} extension DocumentationAttributeArgumentSyntax: WithTrailingCommaSyntax {} -extension EnumCaseDeclSyntax: AttributedSyntax {} +extension EnumCaseDeclSyntax: WithAttributesSyntax, WithModifiersSyntax {} extension EnumCaseElementSyntax: WithTrailingCommaSyntax {} extension EnumCaseParameterClauseSyntax: ParenthesizedSyntax {} -extension EnumCaseParameterSyntax: WithTrailingCommaSyntax {} +extension EnumCaseParameterSyntax: WithTrailingCommaSyntax, WithModifiersSyntax {} -extension EnumDeclSyntax: DeclGroupSyntax, IdentifiedDeclSyntax, AttributedSyntax {} +extension EnumDeclSyntax: DeclGroupSyntax, IdentifiedDeclSyntax, WithAttributesSyntax, WithModifiersSyntax {} extension ExpressionSegmentSyntax: ParenthesizedSyntax {} -extension ExtensionDeclSyntax: DeclGroupSyntax, AttributedSyntax {} +extension ExtensionDeclSyntax: DeclGroupSyntax, WithAttributesSyntax, WithModifiersSyntax {} extension ForInStmtSyntax: WithCodeBlockSyntax {} -extension FunctionDeclSyntax: IdentifiedDeclSyntax, AttributedSyntax {} +extension FunctionDeclSyntax: IdentifiedDeclSyntax, WithAttributesSyntax, WithModifiersSyntax {} extension FunctionEffectSpecifiersSyntax: EffectSpecifiersSyntax {} -extension FunctionParameterSyntax: WithTrailingCommaSyntax, AttributedSyntax {} +extension FunctionParameterSyntax: WithTrailingCommaSyntax, WithAttributesSyntax, WithModifiersSyntax {} extension FunctionTypeSyntax: ParenthesizedSyntax {} extension GenericArgumentSyntax: WithTrailingCommaSyntax {} -extension GenericParameterSyntax: WithTrailingCommaSyntax, AttributedSyntax {} +extension GenericParameterSyntax: WithTrailingCommaSyntax, WithAttributesSyntax {} extension GenericRequirementSyntax: WithTrailingCommaSyntax {} @@ -537,25 +573,25 @@ extension GuardStmtSyntax: WithCodeBlockSyntax {} extension IfExprSyntax: WithCodeBlockSyntax {} -extension ImportDeclSyntax: AttributedSyntax {} +extension ImportDeclSyntax: WithAttributesSyntax, WithModifiersSyntax {} extension InheritedTypeSyntax: WithTrailingCommaSyntax {} -extension InitializerDeclSyntax: AttributedSyntax {} +extension InitializerDeclSyntax: WithAttributesSyntax, WithModifiersSyntax {} extension LabeledSpecializeEntrySyntax: WithTrailingCommaSyntax {} -extension MacroDeclSyntax: IdentifiedDeclSyntax, AttributedSyntax {} +extension MacroDeclSyntax: IdentifiedDeclSyntax, WithAttributesSyntax, WithModifiersSyntax {} -extension MacroExpansionDeclSyntax: FreestandingMacroExpansionSyntax {} +extension MacroExpansionDeclSyntax: FreestandingMacroExpansionSyntax, WithAttributesSyntax, WithModifiersSyntax {} extension MacroExpansionExprSyntax: FreestandingMacroExpansionSyntax {} extension MemberDeclBlockSyntax: BracedSyntax {} -extension MissingDeclSyntax: AttributedSyntax {} +extension MissingDeclSyntax: WithAttributesSyntax, WithModifiersSyntax {} -extension OperatorDeclSyntax: IdentifiedDeclSyntax, AttributedSyntax {} +extension OperatorDeclSyntax: IdentifiedDeclSyntax, WithAttributesSyntax, WithModifiersSyntax {} extension ParameterClauseSyntax: ParenthesizedSyntax {} @@ -563,19 +599,19 @@ extension PatternBindingSyntax: WithTrailingCommaSyntax {} extension PoundSourceLocationSyntax: ParenthesizedSyntax {} -extension PrecedenceGroupDeclSyntax: IdentifiedDeclSyntax, AttributedSyntax {} +extension PrecedenceGroupDeclSyntax: IdentifiedDeclSyntax, WithAttributesSyntax, WithModifiersSyntax {} extension PrimaryAssociatedTypeSyntax: WithTrailingCommaSyntax {} -extension ProtocolDeclSyntax: DeclGroupSyntax, IdentifiedDeclSyntax, AttributedSyntax {} +extension ProtocolDeclSyntax: DeclGroupSyntax, IdentifiedDeclSyntax, WithAttributesSyntax, WithModifiersSyntax {} extension RepeatWhileStmtSyntax: WithCodeBlockSyntax {} extension SourceFileSyntax: WithStatementsSyntax {} -extension StructDeclSyntax: DeclGroupSyntax, IdentifiedDeclSyntax, AttributedSyntax {} +extension StructDeclSyntax: DeclGroupSyntax, IdentifiedDeclSyntax, WithAttributesSyntax, WithModifiersSyntax {} -extension SubscriptDeclSyntax: AttributedSyntax {} +extension SubscriptDeclSyntax: WithAttributesSyntax, WithModifiersSyntax {} extension SwitchCaseSyntax: WithStatementsSyntax {} @@ -597,8 +633,8 @@ extension TupleTypeSyntax: ParenthesizedSyntax {} extension TypeEffectSpecifiersSyntax: EffectSpecifiersSyntax {} -extension TypealiasDeclSyntax: IdentifiedDeclSyntax, AttributedSyntax {} +extension TypealiasDeclSyntax: IdentifiedDeclSyntax, WithAttributesSyntax {} -extension VariableDeclSyntax: AttributedSyntax {} +extension VariableDeclSyntax: WithAttributesSyntax, WithModifiersSyntax {} extension WhileStmtSyntax: WithCodeBlockSyntax {} diff --git a/Sources/SwiftSyntaxMacros/MacroSystem.swift b/Sources/SwiftSyntaxMacros/MacroSystem.swift index ebdfbfcebfe..8a3e2d3252b 100644 --- a/Sources/SwiftSyntaxMacros/MacroSystem.swift +++ b/Sources/SwiftSyntaxMacros/MacroSystem.swift @@ -89,12 +89,12 @@ class MacroApplication: SyntaxRewriter { } if let declSyntax = node.as(DeclSyntax.self), - let attributedNode = node.asProtocol(AttributedSyntax.self), + let attributedNode = node.asProtocol(WithAttributesSyntax.self), let attributes = attributedNode.attributes { // Visit the node. skipNodes.insert(node) - let visitedNode = self.visit(declSyntax).asProtocol(AttributedSyntax.self)! + let visitedNode = self.visit(declSyntax).asProtocol(WithAttributesSyntax.self)! skipNodes.remove(node) // Remove any attached attributes. @@ -340,7 +340,7 @@ extension MacroApplication { attachedTo decl: DeclSyntax, ofType: MacroType.Type ) -> [(AttributeSyntax, MacroType)] { - guard let attributedNode = decl.asProtocol(AttributedSyntax.self), + guard let attributedNode = decl.asProtocol(WithAttributesSyntax.self), let attributes = attributedNode.attributes else { return [] @@ -433,7 +433,7 @@ extension MacroApplication { attachedTo decl: DeclSyntax, annotating member: MemberDeclListSyntax.Element ) -> MemberDeclListSyntax.Element { - guard let attributedDecl = member.decl.asProtocol(AttributedSyntax.self) else { + guard let attributedDecl = member.decl.asProtocol(WithAttributesSyntax.self) else { return member } From 30a365ad57a201984cdc933a9746e0b517143559 Mon Sep 17 00:00:00 2001 From: Rintaro Ishizaki Date: Fri, 12 May 2023 11:32:42 -0700 Subject: [PATCH 4/6] [Macros] Copy attrs/modifiers on MacroExpansionDecl to expanded decls As per the SE-0397 amendment, copy attributes and modifiers on MacroExpansionDecl to the expanded declarations. --- .../swiftsyntax/SyntaxCollectionsFile.swift | 2 + Sources/SwiftParser/CMakeLists.txt | 1 + .../generated/SyntaxCollections.swift | 3 + .../MacroExpansion.swift | 10 ++- Sources/SwiftSyntaxMacros/MacroSystem.swift | 68 +++++++++++++--- .../MacroSystemTests.swift | 78 +++++++++++++++++++ 6 files changed, 149 insertions(+), 13 deletions(-) diff --git a/CodeGeneration/Sources/generate-swiftsyntax/templates/swiftsyntax/SyntaxCollectionsFile.swift b/CodeGeneration/Sources/generate-swiftsyntax/templates/swiftsyntax/SyntaxCollectionsFile.swift index 4eb556246af..5cbbacb9680 100644 --- a/CodeGeneration/Sources/generate-swiftsyntax/templates/swiftsyntax/SyntaxCollectionsFile.swift +++ b/CodeGeneration/Sources/generate-swiftsyntax/templates/swiftsyntax/SyntaxCollectionsFile.swift @@ -19,6 +19,8 @@ let syntaxCollectionsFile = SourceFileSyntax(leadingTrivia: copyrightHeader) { DeclSyntax( """ public protocol SyntaxCollection: SyntaxProtocol, Sequence where Element: SyntaxProtocol { + /// Creates a new collection with the elements. + init(_ children: [Element]) /// The number of elements, `present` or `missing`, in this collection. var count: Int { get } } diff --git a/Sources/SwiftParser/CMakeLists.txt b/Sources/SwiftParser/CMakeLists.txt index 45b41144fce..c131bb0c1c0 100644 --- a/Sources/SwiftParser/CMakeLists.txt +++ b/Sources/SwiftParser/CMakeLists.txt @@ -27,6 +27,7 @@ add_swift_host_library(SwiftParser Specifiers.swift Statements.swift StringLiterals.swift + StringLiteralRepresentedLiteralValue.swift SyntaxUtils.swift TokenConsumer.swift TokenPrecedence.swift diff --git a/Sources/SwiftSyntax/generated/SyntaxCollections.swift b/Sources/SwiftSyntax/generated/SyntaxCollections.swift index f185e513707..6481a4f08b0 100644 --- a/Sources/SwiftSyntax/generated/SyntaxCollections.swift +++ b/Sources/SwiftSyntax/generated/SyntaxCollections.swift @@ -13,6 +13,9 @@ //===----------------------------------------------------------------------===// public protocol SyntaxCollection: SyntaxProtocol, Sequence where Element: SyntaxProtocol { + /// Creates a new collection with the elements. + init(_ children: [Element]) + /// The number of elements, `present` or `missing`, in this collection. var count: Int { get diff --git a/Sources/SwiftSyntaxMacroExpansion/MacroExpansion.swift b/Sources/SwiftSyntaxMacroExpansion/MacroExpansion.swift index 282dbb1801c..34d692a1198 100644 --- a/Sources/SwiftSyntaxMacroExpansion/MacroExpansion.swift +++ b/Sources/SwiftSyntaxMacroExpansion/MacroExpansion.swift @@ -1,5 +1,5 @@ import SwiftSyntax -import SwiftSyntaxMacros +@_spi(MacroExpansion) import SwiftSyntaxMacros public enum MacroRole { case expression @@ -31,7 +31,13 @@ public func expandFreestandingMacro( expandedSyntax = try Syntax(exprMacroDef.expansion(of: node, in: context)) case let declMacroDef as DeclarationMacro.Type: - let rewritten = try declMacroDef.expansion(of: node, in: context) + var rewritten = try declMacroDef.expansion(of: node, in: context) + // Copy attributes and modifiers to the generated decls. + if let expansionDecl = node.as(MacroExpansionDeclSyntax.self) { + rewritten = rewritten.map { + $0.applying(attributes: expansionDecl.attributes, modifiers: expansionDecl.modifiers) + } + } expandedSyntax = Syntax( CodeBlockItemListSyntax( rewritten.map { diff --git a/Sources/SwiftSyntaxMacros/MacroSystem.swift b/Sources/SwiftSyntaxMacros/MacroSystem.swift index 8a3e2d3252b..37bc20f41e0 100644 --- a/Sources/SwiftSyntaxMacros/MacroSystem.swift +++ b/Sources/SwiftSyntaxMacros/MacroSystem.swift @@ -125,24 +125,26 @@ class MacroApplication: SyntaxRewriter { override func visit(_ node: CodeBlockItemListSyntax) -> CodeBlockItemListSyntax { var newItems: [CodeBlockItemSyntax] = [] for item in node { - // Expand declaration macros that were parsed as macro expansion - // expressions in this context. - if case let .expr(exprItem) = item.item, - let exprExpansion = exprItem.as(MacroExpansionExprSyntax.self), - let macro = macroSystem.macros[exprExpansion.macro.text] + if let expansion = item.item.asProtocol(FreestandingMacroExpansionSyntax.self), + let macro = macroSystem.macros[expansion.macro.text] { - do { + func _expand(expansion: some FreestandingMacroExpansionSyntax) throws { if let macro = macro as? CodeItemMacro.Type { let expandedItemList = try macro.expansion( - of: exprExpansion, + of: expansion, in: context ) newItems.append(contentsOf: expandedItemList) } else if let macro = macro as? DeclarationMacro.Type { - let expandedItemList = try macro.expansion( - of: exprExpansion, + var expandedItemList = try macro.expansion( + of: expansion, in: context ) + if let declExpansion = expansion.as(MacroExpansionDeclSyntax.self) { + expandedItemList = expandedItemList.map { + $0.applying(attributes: declExpansion.attributes, modifiers: declExpansion.modifiers) + } + } newItems.append( contentsOf: expandedItemList.map { CodeBlockItemSyntax(item: .decl($0)) @@ -150,11 +152,14 @@ class MacroApplication: SyntaxRewriter { ) } else if let macro = macro as? ExpressionMacro.Type { let expandedExpr = try macro.expansion( - of: exprExpansion, + of: expansion, in: context ) newItems.append(CodeBlockItemSyntax(item: .init(expandedExpr))) } + } + do { + try _openExistential(expansion, do: _expand) } catch { context.addDiagnostics(from: error, node: node) } @@ -189,10 +194,13 @@ class MacroApplication: SyntaxRewriter { let freestandingMacro = macro as? DeclarationMacro.Type { do { - let expandedList = try freestandingMacro.expansion( + var expandedList = try freestandingMacro.expansion( of: declExpansion, in: context ) + expandedList = expandedList.map { + $0.applying(attributes: declExpansion.attributes, modifiers: declExpansion.modifiers) + } newItems.append( contentsOf: expandedList.map { decl in @@ -468,6 +476,44 @@ extension MacroApplication { } } +extension DeclSyntax { + /// Returns this node with `attributes` and `modifiers` prepended to the + /// node’s attributes and modifiers, respectively. If the node doesn’t contain + /// attributes or modifiers, `attributes` or `modifiers` are ignored and not + /// applied. + @_spi(MacroExpansion) + public func applying( + attributes: AttributeListSyntax?, + modifiers: ModifierListSyntax? + ) -> DeclSyntax { + func _combine(_ left: C, _ right: C?) -> C? { + guard let right = right else { return left } + var elems: [C.Element] = [] + elems.append(contentsOf: left) + elems.append(contentsOf: right) + return C(elems) + } + var node = self + if let attributes = attributes, + let withAttrs = node.asProtocol(WithAttributesSyntax.self) + { + node = withAttrs.with( + \.attributes, + _combine(attributes, withAttrs.attributes) + ).cast(DeclSyntax.self) + } + if let modifiers = modifiers, + let withModifiers = node.asProtocol(WithModifiersSyntax.self) + { + node = withModifiers.with( + \.modifiers, + _combine(modifiers, withModifiers.modifiers) + ).cast(DeclSyntax.self) + } + return node + } +} + extension SyntaxProtocol { /// Expand all uses of the given set of macros within this syntax /// node. diff --git a/Tests/SwiftSyntaxMacrosTest/MacroSystemTests.swift b/Tests/SwiftSyntaxMacrosTest/MacroSystemTests.swift index 7ab790b3eaa..ea0dbe610c1 100644 --- a/Tests/SwiftSyntaxMacrosTest/MacroSystemTests.swift +++ b/Tests/SwiftSyntaxMacrosTest/MacroSystemTests.swift @@ -651,6 +651,27 @@ public struct UnwrapMacro: CodeItemMacro { } } +public struct DeclsFromStringsMacro: DeclarationMacro { + public static func expansion( + of node: some FreestandingMacroExpansionSyntax, + in context: some MacroExpansionContext + ) throws -> [DeclSyntax] { + var strings: [String] = [] + for arg in node.argumentList { + guard + let value = arg.expression.as(StringLiteralExprSyntax.self)?.representedLiteralValue + else { + continue + } + strings.append(value) + } + + return strings.map { + "\(raw: $0)" + } + } +} + // MARK: Tests /// The set of test macros we use here. @@ -1037,4 +1058,61 @@ final class MacroSystemTests: XCTestCase { indentationWidth: indentationWidth ) } + + func testDeclsFromStringLiterals() { + assertMacroExpansion( + #""" + struct S { + public #decls( + """ + static func foo() { + print("value") } + """, + "struct Inner {\n\n}" + ) + } + """#, + expandedSource: #""" + struct S { + public static func foo() { + print("value") + } + public struct Inner { + + } + } + """#, + macros: ["decls": DeclsFromStringsMacro.self], + indentationWidth: indentationWidth + ) + + assertMacroExpansion( + #""" + struct S { + @attr static #decls("var value1 = 1", "typealias A = B") + } + """#, + expandedSource: #""" + struct S { + @attr static var value1 = 1 + @attr typealias A = B + } + """#, + macros: ["decls": DeclsFromStringsMacro.self], + indentationWidth: indentationWidth + ) + + assertMacroExpansion( + #""" + @attribute + @otherAttribute(x: 1) #decls("@moreAttibute var global = 42") + """#, + expandedSource: #""" + @attribute + @otherAttribute(x: 1) @moreAttibute var global = 42 + """#, + macros: ["decls": DeclsFromStringsMacro.self], + indentationWidth: indentationWidth + ) + } } From 6f84e85ceb2b03e7415b163bcd7aeee218dca952 Mon Sep 17 00:00:00 2001 From: Rintaro Ishizaki Date: Mon, 15 May 2023 20:28:09 -0700 Subject: [PATCH 5/6] Address review comments in https://github.com/apple/swift-syntax/pull/1654 --- Package.swift | 12 +++-- .../MacroExpansion.swift | 46 +++++++++++++++---- 2 files changed, 43 insertions(+), 15 deletions(-) diff --git a/Package.swift b/Package.swift index 818ca707056..bbe0893835e 100644 --- a/Package.swift +++ b/Package.swift @@ -179,17 +179,19 @@ let package = Package( exclude: ["CMakeLists.txt"] ), + .testTarget( + name: "SwiftSyntaxMacrosTest", + dependencies: ["_SwiftSyntaxTestSupport", "SwiftDiagnostics", "SwiftOperators", "SwiftParser", "SwiftSyntaxBuilder", "SwiftSyntaxMacros", "SwiftSyntaxMacrosTestSupport"] + ), + + // MARK: SwiftSyntaxMacroExpansion + .target( name: "SwiftSyntaxMacroExpansion", dependencies: ["SwiftSyntax", "SwiftSyntaxMacros"], exclude: ["CMakeLists.txt"] ), - .testTarget( - name: "SwiftSyntaxMacrosTest", - dependencies: ["_SwiftSyntaxTestSupport", "SwiftDiagnostics", "SwiftOperators", "SwiftParser", "SwiftSyntaxBuilder", "SwiftSyntaxMacros", "SwiftSyntaxMacrosTestSupport"] - ), - // MARK: SwiftSyntaxMacrosTestSupport .target( diff --git a/Sources/SwiftSyntaxMacroExpansion/MacroExpansion.swift b/Sources/SwiftSyntaxMacroExpansion/MacroExpansion.swift index 34d692a1198..7e241879195 100644 --- a/Sources/SwiftSyntaxMacroExpansion/MacroExpansion.swift +++ b/Sources/SwiftSyntaxMacroExpansion/MacroExpansion.swift @@ -13,11 +13,23 @@ public enum MacroRole { } /// Simple diagnostic message -private struct MacroExpansionError: Error, CustomStringConvertible { - let description: String +private enum MacroExpansionError: String, Error, CustomStringConvertible { + case unmathedMacroRole = "macro doesn't conform to required macro role" + case parentDeclGroupNil = "parent decl group is nil" + case declarationNotDeclGroup = "declaration is not a decl group syntax" + case declarationNotIdentified = "declaration is not a 'Identified' syntax" + var description: String { self.rawValue } } /// Expand `@freestanding(XXX)` macros. +/// +/// - Parameters: +/// - definition: a type conforms to one of freestanding `Macro` protocol. +/// - node: macro expansion syntax node (e.g. `#macroName(argument)`). +/// - in: context of the expansion. +/// - Returns: expanded source text. Upon failure (i.e. `defintion.expansion()` +/// throws) returns `nil`, and the diagnostics representing the `Error` are +/// guaranteed to be added to context. public func expandFreestandingMacro( definition: Macro.Type, node: FreestandingMacroExpansionSyntax, @@ -51,7 +63,7 @@ public func expandFreestandingMacro( expandedSyntax = Syntax(CodeBlockItemListSyntax(rewritten)) default: - throw MacroExpansionError(description: "macro doesn't conform to required macro role") + throw MacroExpansionError.unmathedMacroRole } return expandedSyntax.formattedExpansion(definition.formatMode) } @@ -63,6 +75,18 @@ public func expandFreestandingMacro( } /// Expand `@attached(XXX)` macros. +/// +/// - Parameters: +/// - definition: a type that conforms to one or more attached `Macro` protocols. +/// - macroRole: indicates which `Macro` protocol expansion should be performed +/// - attributeNode: attribute syntax node (e.g. `@macroName(argument)`). +/// - declarationNode: target declaration syntax node to apply the expansion. +/// - parentDeclNode: Only used for `MacroRole.memberAttribute`. The parent +/// context node of `declarationNode`. +/// - in: context of the expansion. +/// - Returns: A list of expanded source text. Upon failure (i.e. +/// `defintion.expansion()` throws) returns `nil`, and the diagnostics +/// representing the `Error` are guaranteed to be added to context. public func expandAttachedMacro( definition: Macro.Type, macroRole: MacroRole, @@ -88,7 +112,7 @@ public func expandAttachedMacro( let parentDeclGroup = parentDeclNode?.asProtocol(DeclGroupSyntax.self) else { // Compiler error: 'parentDecl' is mandatory for MemberAttributeMacro. - throw MacroExpansionError(description: "parent decl group is nil") + throw MacroExpansionError.parentDeclGroupNil } // Local function to expand a member attribute macro once we've opened up @@ -118,7 +142,7 @@ public func expandAttachedMacro( guard let declGroup = declarationNode.asProtocol(DeclGroupSyntax.self) else { // Compiler error: declNode for member macro must be DeclGroupSyntax. - throw MacroExpansionError(description: "declaration is not a decl group syntax") + throw MacroExpansionError.declarationNotDeclGroup } // Local function to expand a member macro once we've opened up @@ -151,12 +175,14 @@ public func expandAttachedMacro( } case (let attachedMacro as ConformanceMacro.Type, .conformance): - guard - let declGroup = declarationNode.asProtocol(DeclGroupSyntax.self), - let identified = declarationNode.asProtocol(IdentifiedDeclSyntax.self) + guard let declGroup = declarationNode.asProtocol(DeclGroupSyntax.self) else { + // Compiler error: type mismatch. + throw MacroExpansionError.declarationNotDeclGroup + } + guard let identified = declarationNode.asProtocol(IdentifiedDeclSyntax.self) else { // Compiler error: type mismatch. - throw MacroExpansionError(description: "declaration is not a identified decl group") + throw MacroExpansionError.declarationNotIdentified } // Local function to expand a conformance macro once we've opened up @@ -185,7 +211,7 @@ public func expandAttachedMacro( } default: - throw MacroExpansionError(description: "macro doesn't conform to required macro role") + throw MacroExpansionError.unmathedMacroRole } } catch { context.addDiagnostics(from: error, node: attributeNode) From d403bc01eb8e57a33478fa14e34077bbdf795a67 Mon Sep 17 00:00:00 2001 From: Nikolai Ruhe Date: Sun, 2 Apr 2023 11:15:45 +0200 Subject: [PATCH 6/6] Implement StringLiteralExprSyntax/representedLiteralValue. (cherry picked from commit 19c7184b8abaf94dcce5faec9c58a9ad11be1588) --- ...StringLiteralRepresentedLiteralValue.swift | 105 ++++++ ...gLiteralRepresentedLiteralValueTests.swift | 318 ++++++++++++++++++ 2 files changed, 423 insertions(+) create mode 100644 Sources/SwiftParser/StringLiteralRepresentedLiteralValue.swift create mode 100644 Tests/SwiftParserTest/StringLiteralRepresentedLiteralValueTests.swift diff --git a/Sources/SwiftParser/StringLiteralRepresentedLiteralValue.swift b/Sources/SwiftParser/StringLiteralRepresentedLiteralValue.swift new file mode 100644 index 00000000000..391de61a01d --- /dev/null +++ b/Sources/SwiftParser/StringLiteralRepresentedLiteralValue.swift @@ -0,0 +1,105 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2023 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 +// +//===----------------------------------------------------------------------===// + +@_spi(RawSyntax) import SwiftSyntax + +extension StringLiteralExprSyntax { + + /// Returns the string value of the literal as the parsed program would see + /// it: Multiline strings are combined into one string, escape sequences are + /// resolved. + /// + /// Returns nil if the literal contains interpolation segments. + public var representedLiteralValue: String? { + // Currently the implementation relies on properly parsed literals. + guard !hasError else { return nil } + guard let stringLiteralKind else { return nil } + + // Concatenate unescaped string literal segments. For example multiline + // strings consist of multiple segments. Abort on finding string + // interpolation. + var result = "" + for segment in segments { + switch segment { + case .stringSegment(let stringSegmentSyntax): + stringSegmentSyntax.appendUnescapedLiteralValue( + stringLiteralKind: stringLiteralKind, + delimiterLength: delimiterLength, + to: &result + ) + case .expressionSegment: + // Bail out if there are any interpolation segments. + return nil + } + } + + return result + } + + fileprivate var stringLiteralKind: StringLiteralKind? { + switch openQuote.tokenKind { + case .stringQuote: + return .singleLine + case .multilineStringQuote: + return .multiLine + case .singleQuote: + return .singleQuote + default: + return nil + } + } + + fileprivate var delimiterLength: Int { + openDelimiter?.text.count ?? 0 + } +} + +extension StringSegmentSyntax { + fileprivate func appendUnescapedLiteralValue( + stringLiteralKind: StringLiteralKind, + delimiterLength: Int, + to output: inout String + ) { + precondition(!hasError, "appendUnescapedLiteralValue relies on properly parsed literals") + + var text = content.text + text.withUTF8 { buffer in + var cursor = Lexer.Cursor(input: buffer, previous: 0) + + // Put the cursor in the string literal lexing state. This is just + // defensive as it's currently not used by `lexCharacterInStringLiteral`. + let state = Lexer.Cursor.State.inStringLiteral(kind: stringLiteralKind, delimiterLength: delimiterLength) + let transition = Lexer.StateTransition.push(newState: state) + cursor.perform(stateTransition: transition, stateAllocator: BumpPtrAllocator(slabSize: 256)) + + while true { + let lex = cursor.lexCharacterInStringLiteral( + stringLiteralKind: stringLiteralKind, + delimiterLength: delimiterLength + ) + + switch lex { + case .success(let scalar): + output.append(Character(scalar)) + case .validatedEscapeSequence(let character): + output.append(character) + case .endOfString, .error: + // We get an error at the end of the string because + // `lexCharacterInStringLiteral` expects the closing quote. + // We can assume the error just signals the end of string + // because we made sure the token lexed fine before. + return + } + } + } + } +} diff --git a/Tests/SwiftParserTest/StringLiteralRepresentedLiteralValueTests.swift b/Tests/SwiftParserTest/StringLiteralRepresentedLiteralValueTests.swift new file mode 100644 index 00000000000..d0605583237 --- /dev/null +++ b/Tests/SwiftParserTest/StringLiteralRepresentedLiteralValueTests.swift @@ -0,0 +1,318 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2023 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 XCTest +import SwiftSyntax +import SwiftParser +import SwiftSyntaxBuilder + +/// Test ``StringLiteralExprSyntax/representedLiteralValue``. +/// +/// Most tests are expressed by a single function call that compares the +/// run-time String value against the parsed node's `representedLiteralValue`. +public class StringLiteralRepresentedLiteralValueTests: XCTestCase { + + func testIntro() { + test( + #""" + This test suite verifies the correctness of ``StringLiteralExprSyntax/\ + representedLiteralValue.`` It does so by comparing the run-time String + value of various string literals to the value returned from + `representedLiteralValue`. + + The SwiftSyntax parsed representation `StringLiteralExprSyntax` contains + the source-accurate representation of the string literal. This + representation differs from the runtime value: + + - Escaped character sequences (\n, \u{1F600}) are not evaluated. + - Some strings are split into segments (i.e. multiline strings). + + The idea of `representedLiteralValue` is to create the run-time value of a + string literal during parse time. Obviously this is only possible for + static Strings so interpolated strings do not work. + """# + ) + } + + // MARK: double quoted string literal + + func testDoubleQuoted_emptyString() { + test("") + } + + func testDoubleQuoted_singleSpace() { + test(" ") + } + + func testDoubleQuoted_singleQuote() { + test("'") + } + + func testDoubleQuoted_emoji() { + test("😀") + } + + func testDoubleQuoted_escapedNul() { + test("\0") + } + + func testDoubleQuoted_escapedNL() { + test("\n") + } + + func testDoubleQuoted_escapedCR() { + test("\r") + } + + func testDoubleQuoted_escapedTab() { + test("\t") + } + + func testDoubleQuoted_escapedDoubleQuote() { + test("\"") + } + + func testDoubleQuoted_escapedSingleQuote() { + test("\'") + } + + func testDoubleQuoted_escapedEscape() { + test("\\") + } + + func testDoubleQuoted_escapedUnicodeDot() { + test("\u{2e}") + } + + func testDoubleQuoted_escapedEmoji() { + test("\u{1F600}") + } + + // MARK: raw double quoted string literal + + func testRawDoubleQuoted_emptyString() { + test(#""#) + } + + func testRawDoubleQuoted_singleSpace() { + test(#" "#) + } + + func testRawDoubleQuoted_unescapedDoubleQuote() { + test(#"""#) + } + + func testRawDoubleQuoted_unescapedBackslash() { + test(#"\"#) + } + + func testRawDoubleQuoted_emoji() { + test(#"😀"#) + } + + func testRawDoubleQuoted_escapedNul() { + test(#"\#0"#) + } + + func testRawDoubleQuoted_escapedNL() { + test(#"\#n"#) + } + + func testRawDoubleQuoted_escapedCR() { + test(#"\#r"#) + } + + func testRawDoubleQuoted_escapedTab() { + test(#"\#t"#) + } + + func testRawDoubleQuoted_escapedDoubleQuote() { + test(#"\#""#) + } + + func testRawDoubleQuoted_escapedSingleQuote() { + test(#"\#'"#) + } + + func testRawDoubleQuoted_escapedEscape() { + test(#"\#\#"#) + } + + func testRawDoubleQuoted_escapedUnicodeDot() { + test(#"\#u{2e}"#) + } + + func testRawDoubleQuoted_escapedEmoji() { + test(#"\#u{1F600}"#) + } + + // MARK: multi line string literal + + func testMultiLine_emptyString() { + test( + """ + + """ + ) + } + + func testMultiLine_emptyLine() { + test( + """ + + + """ + ) + } + + func testMultiLine_helloWorld() { + test( + """ + Hello, world! + """ + ) + } + + func testMultiLine_indentedLines() { + test( + """ + not indented + 2 spaces indented + 4 spaces indented + """ + ) + } + + func testMultiLine_escapedLine() { + test( + """ + Line 1\ + .still on Line 1 + """ + ) + } + + // MARK: raw multi line string literal + + func testRawMultiLine_emptyString() { + test( + #""" + + """# + ) + } + + func testRawMultiLine_emptyLine() { + test( + #""" + + + """# + ) + } + + func testRawMultiLine_helloWorld() { + test( + #""" + Hello, world! + """# + ) + } + + func testRawMultiLine_indentedLines() { + test( + #""" + not indented + 2 spaces indented + 4 spaces indented + """# + ) + } + + func testRawMultiLine_escapedLine() { + test( + #""" + Line 1\ + .still on Line 1 + """# + ) + } + + // MARK: literal value not available + + func testMissingQuoteStringLiteral() throws { + let stringLiteral = StringLiteralExprSyntax(#""a"# as ExprSyntax)! + XCTAssertNil(stringLiteral.representedLiteralValue, "only fully parsed string literals should produce a literal value") + } + + func testInterpolatedStringLiteral() throws { + let stringLiteral = StringLiteralExprSyntax(#""abc\(1)""# as ExprSyntax)! + XCTAssertNil(stringLiteral.representedLiteralValue, "interpolated string literals cannot produce a literal value") + } + + func testMalformedMultiLineStringLiteral() throws { + let stringLiteral = StringLiteralExprSyntax(#""""a""""# as ExprSyntax)! + XCTAssertNil(stringLiteral.representedLiteralValue, "missing newline in multiline string literal cannot produce a literal value") + } + + // MARK: supporting code + + /// This helper function takes a string literal argument and compares its + /// value with the parsed node of type StringLiteralExprSyntax. To produce the + /// node it parses the contents of this source file and matches the literal by + /// position in source. + func test(_ expected: StaticString, file: StaticString = #filePath, line: UInt = #line) { + guard let literal = Self.literals[at: line] else { + fatalError("string literal not found at line \(line)") + } + + guard let representedLiteralValue = literal.representedLiteralValue else { + XCTFail("literal unexpectedly product nil value", file: file, line: line) + return + } + + XCTAssertEqual(representedLiteralValue, expected.description, file: file, line: line) + } + + static let literals = try! StringLiteralCollector() + + /// Helper class to find string literals in this source file + class StringLiteralCollector: SyntaxVisitor { + var locationConverter: SourceLocationConverter? = nil + var literals: [UInt: [StringLiteralExprSyntax]] = [:] + + init(file: String = #filePath) throws { + let url = URL(fileURLWithPath: file) + let fileData = try Data(contentsOf: url) + let source = fileData.withUnsafeBytes { + String(decoding: $0.bindMemory(to: UInt8.self), as: UTF8.self) + } + let syntax = Parser.parse(source: source) + + super.init(viewMode: .sourceAccurate) + + self.locationConverter = SourceLocationConverter(file: "", tree: syntax) + self.walk(syntax) + self.locationConverter = nil + } + + subscript(at line: UInt, index: Int = 0) -> StringLiteralExprSyntax? { + literals[line]?[index] + } + + override func visit(_ node: StringLiteralExprSyntax) -> SyntaxVisitorContinueKind { + let line = UInt(locationConverter?.location(for: node.position).line ?? 0) + literals[line, default: []].append(node) + return .visitChildren + } + } +}