From e3a0f24cfe23c84e464397acda8c42b2bc60fe03 Mon Sep 17 00:00:00 2001 From: Rintaro Ishizaki Date: Mon, 15 May 2023 13:30:55 -0700 Subject: [PATCH] [Parse] Improve macro expansion parsing * Unify macro expansion parsing logic between MacroExpansionExpr and MacroExpansionDecl * Diagnose whitespace between '#' and the macro name * Diagnose keyword as a macro name --- include/swift/AST/DiagnosticsParse.def | 2 + include/swift/Parse/Parser.h | 10 +++ lib/Parse/ParseDecl.cpp | 70 ++++++---------- lib/Parse/ParseExpr.cpp | 110 +++++++++++++++++-------- test/Macros/macro_keywordname.swift | 61 ++++++++++++++ test/Macros/macro_self.swift | 4 +- test/Parse/macro_decl.swift | 8 ++ test/Parse/macro_expr.swift | 14 ++++ test/decl/import/import.swift | 5 +- 9 files changed, 198 insertions(+), 86 deletions(-) create mode 100644 test/Macros/macro_keywordname.swift diff --git a/include/swift/AST/DiagnosticsParse.def b/include/swift/AST/DiagnosticsParse.def index 96d7c48e93f6b..70fc86debe913 100644 --- a/include/swift/AST/DiagnosticsParse.def +++ b/include/swift/AST/DiagnosticsParse.def @@ -2057,6 +2057,8 @@ ERROR(macro_expansion_expr_expected_macro_identifier,PointsToFirstBadToken, "expected a macro identifier for a pound literal expression", ()) ERROR(macro_expansion_decl_expected_macro_identifier,PointsToFirstBadToken, "expected a macro identifier for a pound literal declaration", ()) +ERROR(extra_whitespace_macro_expansion_identifier,PointsToFirstBadToken, + "extraneous whitespace between '#' and macro name is not permitted", ()) ERROR(macro_role_attr_expected_kind,PointsToFirstBadToken, "expected %select{a freestanding|an attached}0 macro role such as " diff --git a/include/swift/Parse/Parser.h b/include/swift/Parse/Parser.h index 58817a7cc0850..fbe291cd18f5f 100644 --- a/include/swift/Parse/Parser.h +++ b/include/swift/Parse/Parser.h @@ -1751,6 +1751,16 @@ class Parser { DeclNameRef parseDeclNameRef(DeclNameLoc &loc, const Diagnostic &diag, DeclNameOptions flags); + /// Parse macro expansion. + /// + /// macro-expansion: + /// '#' identifier generic-arguments? expr-paren? trailing-closure? + ParserStatus parseFreestandingMacroExpansion( + SourceLoc £Loc, DeclNameLoc ¯oNameLoc, DeclNameRef ¯oNameRef, + SourceLoc &leftAngleLoc, SmallVectorImpl &genericArgs, + SourceLoc &rightAngleLoc, ArgumentList *&argList, bool isExprBasic, + const Diagnostic &diag); + ParserResult parseExprIdentifier(); Expr *parseExprEditorPlaceholder(Token PlaceholderTok, Identifier PlaceholderId); diff --git a/lib/Parse/ParseDecl.cpp b/lib/Parse/ParseDecl.cpp index cc79dc456948f..9936ff0833c89 100644 --- a/lib/Parse/ParseDecl.cpp +++ b/lib/Parse/ParseDecl.cpp @@ -4945,8 +4945,9 @@ bool Parser::isStartOfSwiftDecl(bool allowPoundIfAttributes, } // 'macro' name - if (Tok.isContextualKeyword("macro") && Tok2.is(tok::identifier)) - return true; + if (Tok.isContextualKeyword("macro")) { + return Tok2.is(tok::identifier); + } if (Tok.isContextualKeyword("package")) { // If `case` is the next token after `return package` statement, @@ -5031,16 +5032,20 @@ bool Parser::isStartOfSILDecl() { } bool Parser::isStartOfFreestandingMacroExpansion() { - // Check if "'#' " without any whitespace between them. + // Check if "'#' " where the identifier is on the sameline. if (!Tok.is(tok::pound)) return false; - if (Tok.getRange().getEnd() != peekToken().getLoc()) - return false; - if (!peekToken().isAny(tok::identifier, tok::code_complete) && - // allow keywords right after '#' so we can diagnose it when parsing. - !peekToken().isKeyword()) + const Token &Tok2 = peekToken(); + if (Tok2.isAtStartOfLine()) return false; - return true; + + if (Tok2.isAny(tok::identifier, tok::code_complete)) + return true; + if (Tok2.isKeyword()) { + // allow keywords right after '#' so we can diagnose it when parsing. + return Tok.getRange().getEnd() == Tok2.getLoc(); + } + return false; } void Parser::consumeDecl(ParserPosition BeginParserPosition, @@ -9814,47 +9819,18 @@ ParserResult Parser::parseDeclMacro(DeclAttributes &attributes) { ParserResult Parser::parseDeclMacroExpansion(ParseDeclOptions flags, DeclAttributes &attributes) { - SourceLoc poundLoc = consumeToken(tok::pound); + SourceLoc poundLoc; DeclNameLoc macroNameLoc; - DeclNameRef macroNameRef = parseDeclNameRef( - macroNameLoc, diag::macro_expansion_decl_expected_macro_identifier, - DeclNameOptions()); - if (!macroNameRef) - return makeParserError(); - - ParserStatus status; + DeclNameRef macroNameRef; SourceLoc leftAngleLoc, rightAngleLoc; - SmallVector genericArgs; - if (canParseAsGenericArgumentList()) { - auto genericArgsStatus = parseGenericArguments( - genericArgs, leftAngleLoc, rightAngleLoc); - status |= genericArgsStatus; - if (genericArgsStatus.isErrorOrHasCompletion()) - diagnose(leftAngleLoc, diag::while_parsing_as_left_angle_bracket); - } - + SmallVector genericArgs; ArgumentList *argList = nullptr; - if (Tok.isFollowingLParen()) { - auto result = parseArgumentList(tok::l_paren, tok::r_paren, - /*isExprBasic*/ false, - /*allowTrailingClosure*/ true); - status |= result; - if (result.hasCodeCompletion()) - return makeParserCodeCompletionResult(); - argList = result.getPtrOrNull(); - } else if (Tok.is(tok::l_brace)) { - SmallVector trailingClosures; - auto closuresStatus = parseTrailingClosures(/*isExprBasic*/ false, - macroNameLoc.getSourceRange(), - trailingClosures); - status |= closuresStatus; - - if (!trailingClosures.empty()) { - argList = ArgumentList::createParsed(Context, SourceLoc(), - trailingClosures, SourceLoc(), - /*trailingClosureIdx*/ 0); - } - } + ParserStatus status = parseFreestandingMacroExpansion( + poundLoc, macroNameLoc, macroNameRef, leftAngleLoc, genericArgs, + rightAngleLoc, argList, /*isExprBasic=*/false, + diag::macro_expansion_decl_expected_macro_identifier); + if (!macroNameRef) + return status; auto *med = new (Context) MacroExpansionDecl( CurDeclContext, poundLoc, macroNameRef, macroNameLoc, leftAngleLoc, diff --git a/lib/Parse/ParseExpr.cpp b/lib/Parse/ParseExpr.cpp index 964914ee9483a..ef2b8f0086fa6 100644 --- a/lib/Parse/ParseExpr.cpp +++ b/lib/Parse/ParseExpr.cpp @@ -2269,6 +2269,71 @@ DeclNameRef Parser::parseDeclNameRef(DeclNameLoc &loc, return DeclNameRef({ Context, baseName, argumentLabels }); } +ParserStatus Parser::parseFreestandingMacroExpansion( + SourceLoc £Loc, DeclNameLoc ¯oNameLoc, DeclNameRef ¯oNameRef, + SourceLoc &leftAngleLoc, SmallVectorImpl &genericArgs, + SourceLoc &rightAngleLoc, ArgumentList *&argList, bool isExprBasic, + const Diagnostic &diag) { + SourceLoc poundEndLoc = Tok.getRange().getEnd(); + poundLoc = consumeToken(tok::pound); + + if (Tok.isAtStartOfLine()) { + // Diagnose and bail out if there's no macro name on the same line. + diagnose(poundEndLoc, diag); + return makeParserError(); + } + + bool hasWhitespaceBeforeName = poundEndLoc != Tok.getLoc(); + + // Diagnose and parse keyword right after `#`. + if (Tok.isKeyword() && !hasWhitespaceBeforeName) { + diagnose(Tok, diag::keyword_cant_be_identifier, Tok.getText()); + diagnose(Tok, diag::backticks_to_escape) + .fixItReplace(Tok.getLoc(), "`" + Tok.getText().str() + "`"); + + // Let 'parseDeclNameRef' to parse this as an identifier. + Tok.setKind(tok::identifier); + } + macroNameRef = parseDeclNameRef(macroNameLoc, diag, DeclNameOptions()); + if (!macroNameRef) + return makeParserError(); + + // Diagnose whitespace between '#' and the macro name, and continue parsing. + if (hasWhitespaceBeforeName) { + diagnose(poundEndLoc, diag::extra_whitespace_macro_expansion_identifier) + .fixItRemoveChars(poundEndLoc, macroNameLoc.getStartLoc()); + } + + ParserStatus status; + if (canParseAsGenericArgumentList()) { + auto genericArgsStatus = + parseGenericArguments(genericArgs, leftAngleLoc, rightAngleLoc); + status |= genericArgsStatus; + if (genericArgsStatus.isErrorOrHasCompletion()) + diagnose(leftAngleLoc, diag::while_parsing_as_left_angle_bracket); + } + + if (Tok.isFollowingLParen()) { + auto result = parseArgumentList(tok::l_paren, tok::r_paren, isExprBasic, + /*allowTrailingClosure*/ true); + status |= result; + argList = result.getPtrOrNull(); + } else if (Tok.is(tok::l_brace)) { + SmallVector trailingClosures; + auto closuresStatus = parseTrailingClosures( + isExprBasic, macroNameLoc.getSourceRange(), trailingClosures); + status |= closuresStatus; + + if (!trailingClosures.empty()) { + argList = ArgumentList::createParsed(Context, SourceLoc(), + trailingClosures, SourceLoc(), + /*trailingClosureIdx*/ 0); + } + } + + return status; +} + /// expr-identifier: /// unqualified-decl-name generic-args? ParserResult Parser::parseExprIdentifier() { @@ -3376,45 +3441,18 @@ Parser::parseExprCallSuffix(ParserResult fn, bool isExprBasic) { } ParserResult Parser::parseExprMacroExpansion(bool isExprBasic) { - SourceLoc poundLoc = consumeToken(tok::pound); + SourceLoc poundLoc; DeclNameLoc macroNameLoc; - DeclNameRef macroNameRef = parseDeclNameRef( - macroNameLoc, diag::macro_expansion_expr_expected_macro_identifier, - DeclNameOptions()); - if (!macroNameRef) - return makeParserError(); - - ParserStatus status; + DeclNameRef macroNameRef; SourceLoc leftAngleLoc, rightAngleLoc; - SmallVector genericArgs; - if (canParseAsGenericArgumentList()) { - auto genericArgsStatus = parseGenericArguments( - genericArgs, leftAngleLoc, rightAngleLoc); - status |= genericArgsStatus; - if (genericArgsStatus.isErrorOrHasCompletion()) - diagnose(leftAngleLoc, diag::while_parsing_as_left_angle_bracket); - } - + SmallVector genericArgs; ArgumentList *argList = nullptr; - if (Tok.isFollowingLParen()) { - auto result = parseArgumentList(tok::l_paren, tok::r_paren, isExprBasic, - /*allowTrailingClosure*/ true); - argList = result.getPtrOrNull(); - status |= result; - } else if (Tok.is(tok::l_brace) && - isValidTrailingClosure(isExprBasic, *this)) { - SmallVector trailingClosures; - auto closureStatus = parseTrailingClosures(isExprBasic, - macroNameLoc.getSourceRange(), - trailingClosures); - status |= closureStatus; - - if (!trailingClosures.empty()) { - argList = ArgumentList::createParsed(Context, SourceLoc(), - trailingClosures, SourceLoc(), - /*trailingClosureIdx*/ 0); - } - } + ParserStatus status = parseFreestandingMacroExpansion( + poundLoc, macroNameLoc, macroNameRef, leftAngleLoc, genericArgs, + rightAngleLoc, argList, isExprBasic, + diag::macro_expansion_expr_expected_macro_identifier); + if (!macroNameRef) + return status; return makeParserResult( status, diff --git a/test/Macros/macro_keywordname.swift b/test/Macros/macro_keywordname.swift new file mode 100644 index 0000000000000..3a302538b312e --- /dev/null +++ b/test/Macros/macro_keywordname.swift @@ -0,0 +1,61 @@ +// REQUIRES: swift_swift_parser + +// RUN: %empty-directory(%t) +// RUN: mkdir -p %t/src +// RUN: mkdir -p %t/plugins +// RUN: mkdir -p %t/lib + +// RUN: split-file %s %t/src + +//#-- Prepare the macro dylib plugin. +// RUN: %host-build-swift \ +// RUN: -swift-version 5 \ +// RUN: -emit-library -o %t/plugins/%target-library-name(MacroDefinition) \ +// RUN: -module-name MacroDefinition \ +// RUN: %t/src/MacroDefinition.swift \ +// RUN: -g -no-toolchain-stdlib-rpath + +//#-- Prepare the macro library. +// RUN: %target-swift-frontend \ +// RUN: -swift-version 5 \ +// RUN: -emit-module -o %t/lib/MacroLib.swiftmodule \ +// RUN: -module-name MacroLib \ +// RUN: -plugin-path %t/plugins \ +// RUN: %t/src/MacroLib.swift + +// RUN: %target-swift-frontend \ +// RUN: -typecheck -verify \ +// RUN: -I %t/lib \ +// RUN: -plugin-path %t/plugins \ +// RUN: %t/src/test.swift + +//--- MacroDefinition.swift +import SwiftSyntax +import SwiftSyntaxMacros + +public struct OneMacro: ExpressionMacro { + public static func expansion( + of node: some FreestandingMacroExpansionSyntax, + in context: some MacroExpansionContext + ) throws -> ExprSyntax { + return "1" + } +} + + +//--- MacroLib.swift +@freestanding(expression) public macro `public`() -> Int = #externalMacro(module: "MacroDefinition", type: "OneMacro") +@freestanding(expression) public macro `escaped`() -> Int = #externalMacro(module: "MacroDefinition", type: "OneMacro") +@freestanding(expression) public macro normal() -> Int = #externalMacro(module: "MacroDefinition", type: "OneMacro") + +//--- test.swift +import MacroLib +@freestanding(expression) public macro `class`() -> Int = #externalMacro(module: "MacroDefinition", type: "OneMacro") + +func test() { + let _: Int = #public() // expected-error {{keyword 'public' cannot be used as an identifier here}} expected-note {{if this name is unavoidable, use backticks to escape it}} + let _: Int = #`public`() + let _: Int = #escaped() + let _: Int = #`class`() + let _: Int = #normal() +} diff --git a/test/Macros/macro_self.swift b/test/Macros/macro_self.swift index dce2806fa938a..4e8b39b0fc173 100644 --- a/test/Macros/macro_self.swift +++ b/test/Macros/macro_self.swift @@ -9,11 +9,11 @@ func sync() {} macro Self() = #externalMacro(module: "MacroDefinition", type: "InvalidMacro") func testSelfAsFreestandingMacro() { - _ = #self // expected-error {{expected a macro identifier for a pound literal expression}} + _ = #self // expected-error {{keyword 'self' cannot be used as an identifier here}} expected-note {{use backticks to escape it}} } func testCapitalSelfAsFreestandingMacro() { - _ = #Self // expected-error {{expected a macro identifier for a pound literal expression}} + _ = #Self // expected-error {{keyword 'Self' cannot be used as an identifier here}} expected-note {{use backticks to escape it}} } func testSelfAsAttachedMacro() { diff --git a/test/Parse/macro_decl.swift b/test/Parse/macro_decl.swift index d5fa9c3737dc8..3fc66878c1f8d 100644 --- a/test/Parse/macro_decl.swift +++ b/test/Parse/macro_decl.swift @@ -46,3 +46,11 @@ func test() { @CustomAttr isolated #someFunc } + +public # someFunc // expected-error {{extraneous whitespace between '#' and macro name is not permitted}} {{9-10=}} + +struct S { + # someFunc // expected-error {{extraneous whitespace between '#' and macro name is not permitted}} {{4-5=}} + #class // expected-error {{keyword 'class' cannot be used as an identifier here}} expected-note {{if this name is unavoidable, use backticks to escape it}} {{4-9=`class`}} + # struct Inner {} // expected-error {{expected a macro identifier for a pound literal declaration}} expected-error {{consecutive declarations on a line}} +} diff --git a/test/Parse/macro_expr.swift b/test/Parse/macro_expr.swift index 235502c091a25..3e9a6d061931a 100644 --- a/test/Parse/macro_expr.swift +++ b/test/Parse/macro_expr.swift @@ -35,3 +35,17 @@ _ = #another { // expected-error @+1 {{expected a macro identifier for a pound literal expression}} _ = #() + +do { + _ = # // expected-error {{expected a macro identifier for a pound literal expression}} + name() +} +do { + _ = # macro() // expected-error {{extraneous whitespace between '#' and macro name is not permitted}} {{8-9=}} +} +do { + _ = #public() // expected-error {{keyword 'public' cannot be used as an identifier here}} expected-note {{if this name is unavoidable, use backticks to escape it}} {{8-14=`public`}} +} +do { + _ = # public() // expected-error {{expected a macro identifier for a pound literal expression}} +} diff --git a/test/decl/import/import.swift b/test/decl/import/import.swift index 23ee6dddb1e1f..6f7c10f537fcf 100644 --- a/test/decl/import/import.swift +++ b/test/decl/import/import.swift @@ -34,7 +34,10 @@ func f1(_ a: Swift.Int) -> Swift.Void { print(a) } import func Swift.print // rdar://14418336 -#import something_nonexistent // expected-error {{expected a macro identifier}} expected-error {{no such module 'something_nonexistent'}} +#import something_nonexistent +// expected-error@-1 {{keyword 'import' cannot be used as an identifier here}} expected-note@-1 {{use backticks to escape it}} +// expected-error@-2 {{no macro named 'import'}} +// expected-error@-3 {{consecutive statements on a line}} expected-error@-3 {{cannot find 'something_nonexistent' in scope}} // Import specific decls import typealias Swift.Int