Skip to content

[Parse] Improve freestanding macro expansion parsing #65967

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions include/swift/AST/DiagnosticsParse.def
Original file line number Diff line number Diff line change
Expand Up @@ -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 "
Expand Down
10 changes: 10 additions & 0 deletions include/swift/Parse/Parser.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 &poundLoc, DeclNameLoc &macroNameLoc, DeclNameRef &macroNameRef,
SourceLoc &leftAngleLoc, SmallVectorImpl<TypeRepr *> &genericArgs,
SourceLoc &rightAngleLoc, ArgumentList *&argList, bool isExprBasic,
const Diagnostic &diag);

ParserResult<Expr> parseExprIdentifier();
Expr *parseExprEditorPlaceholder(Token PlaceholderTok,
Identifier PlaceholderId);
Expand Down
70 changes: 23 additions & 47 deletions lib/Parse/ParseDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -5031,16 +5032,20 @@ bool Parser::isStartOfSILDecl() {
}

bool Parser::isStartOfFreestandingMacroExpansion() {
// Check if "'#' <identifier>" without any whitespace between them.
// Check if "'#' <identifier>" 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,
Expand Down Expand Up @@ -9814,47 +9819,18 @@ ParserResult<MacroDecl> Parser::parseDeclMacro(DeclAttributes &attributes) {
ParserResult<MacroExpansionDecl>
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<TypeRepr *, 8> genericArgs;
if (canParseAsGenericArgumentList()) {
auto genericArgsStatus = parseGenericArguments(
genericArgs, leftAngleLoc, rightAngleLoc);
status |= genericArgsStatus;
if (genericArgsStatus.isErrorOrHasCompletion())
diagnose(leftAngleLoc, diag::while_parsing_as_left_angle_bracket);
}

SmallVector<TypeRepr *, 4> 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<MacroExpansionDecl>();
argList = result.getPtrOrNull();
} else if (Tok.is(tok::l_brace)) {
SmallVector<Argument, 2> 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,
Expand Down
110 changes: 74 additions & 36 deletions lib/Parse/ParseExpr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2269,6 +2269,71 @@ DeclNameRef Parser::parseDeclNameRef(DeclNameLoc &loc,
return DeclNameRef({ Context, baseName, argumentLabels });
}

ParserStatus Parser::parseFreestandingMacroExpansion(
SourceLoc &poundLoc, DeclNameLoc &macroNameLoc, DeclNameRef &macroNameRef,
SourceLoc &leftAngleLoc, SmallVectorImpl<TypeRepr *> &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<Argument, 2> 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<Expr> Parser::parseExprIdentifier() {
Expand Down Expand Up @@ -3376,45 +3441,18 @@ Parser::parseExprCallSuffix(ParserResult<Expr> fn, bool isExprBasic) {
}

ParserResult<Expr> 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<TypeRepr *, 8> genericArgs;
if (canParseAsGenericArgumentList()) {
auto genericArgsStatus = parseGenericArguments(
genericArgs, leftAngleLoc, rightAngleLoc);
status |= genericArgsStatus;
if (genericArgsStatus.isErrorOrHasCompletion())
diagnose(leftAngleLoc, diag::while_parsing_as_left_angle_bracket);
}

SmallVector<TypeRepr *, 4> 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<Argument, 2> 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,
Expand Down
61 changes: 61 additions & 0 deletions test/Macros/macro_keywordname.swift
Original file line number Diff line number Diff line change
@@ -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()
}
4 changes: 2 additions & 2 deletions test/Macros/macro_self.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down
8 changes: 8 additions & 0 deletions test/Parse/macro_decl.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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}}
}
14 changes: 14 additions & 0 deletions test/Parse/macro_expr.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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}}
}
5 changes: 4 additions & 1 deletion test/decl/import/import.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down