Skip to content

Commit 3a4811e

Browse files
committed
[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
1 parent cd78e8b commit 3a4811e

File tree

7 files changed

+192
-83
lines changed

7 files changed

+192
-83
lines changed

include/swift/AST/DiagnosticsParse.def

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2057,6 +2057,8 @@ ERROR(macro_expansion_expr_expected_macro_identifier,PointsToFirstBadToken,
20572057
"expected a macro identifier for a pound literal expression", ())
20582058
ERROR(macro_expansion_decl_expected_macro_identifier,PointsToFirstBadToken,
20592059
"expected a macro identifier for a pound literal declaration", ())
2060+
ERROR(extra_whitespace_macro_expansion_identifier,PointsToFirstBadToken,
2061+
"extraneous whitespace between '#' and macro name is not permitted", ())
20602062

20612063
ERROR(macro_role_attr_expected_kind,PointsToFirstBadToken,
20622064
"expected %select{a freestanding|an attached}0 macro role such as "

include/swift/Parse/Parser.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1731,6 +1731,9 @@ class Parser {
17311731
/// cases this doesn't actually make sense but we need to accept them for
17321732
/// backwards compatibility.
17331733
AllowLowercaseAndUppercaseSelf = 1 << 6,
1734+
1735+
/// Parse keyword as identifier but emit fix-it to escape it.
1736+
AllowKeywordWithBacktickEscape = AllowKeywords | 1 << 7
17341737
};
17351738
using DeclNameOptions = OptionSet<DeclNameFlag>;
17361739

@@ -1751,6 +1754,16 @@ class Parser {
17511754
DeclNameRef parseDeclNameRef(DeclNameLoc &loc, const Diagnostic &diag,
17521755
DeclNameOptions flags);
17531756

1757+
/// Parse macro expansion.
1758+
///
1759+
/// macro-expansion:
1760+
/// '#' identifier generic-arguments? expr-paren? trailing-closure?
1761+
ParserStatus parseFreestandingMacroExpansion(
1762+
SourceLoc &poundLoc, DeclNameLoc &macroNameLoc, DeclNameRef &macroNameRef,
1763+
SourceLoc &leftAngleLoc, SmallVectorImpl<TypeRepr *> &genericArgs,
1764+
SourceLoc &rightAngleLoc, ArgumentList *&argList, bool isExprBasic,
1765+
const Diagnostic &diag);
1766+
17541767
ParserResult<Expr> parseExprIdentifier();
17551768
Expr *parseExprEditorPlaceholder(Token PlaceholderTok,
17561769
Identifier PlaceholderId);

lib/Parse/ParseDecl.cpp

Lines changed: 23 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -4945,8 +4945,9 @@ bool Parser::isStartOfSwiftDecl(bool allowPoundIfAttributes,
49454945
}
49464946

49474947
// 'macro' name
4948-
if (Tok.isContextualKeyword("macro") && Tok2.is(tok::identifier))
4949-
return true;
4948+
if (Tok.isContextualKeyword("macro")) {
4949+
return Tok2.is(tok::identifier);
4950+
}
49504951

49514952
if (Tok.isContextualKeyword("package")) {
49524953
// If `case` is the next token after `return package` statement,
@@ -5031,16 +5032,20 @@ bool Parser::isStartOfSILDecl() {
50315032
}
50325033

50335034
bool Parser::isStartOfFreestandingMacroExpansion() {
5034-
// Check if "'#' <identifier>" without any whitespace between them.
5035+
// Check if "'#' <identifier>" where the identifier is on the sameline.
50355036
if (!Tok.is(tok::pound))
50365037
return false;
5037-
if (Tok.getRange().getEnd() != peekToken().getLoc())
5038-
return false;
5039-
if (!peekToken().isAny(tok::identifier, tok::code_complete) &&
5040-
// allow keywords right after '#' so we can diagnose it when parsing.
5041-
!peekToken().isKeyword())
5038+
const Token &Tok2 = peekToken();
5039+
if (Tok2.isAtStartOfLine())
50425040
return false;
5043-
return true;
5041+
5042+
if (Tok2.isAny(tok::identifier, tok::code_complete))
5043+
return true;
5044+
if (Tok2.isKeyword()) {
5045+
// allow keywords right after '#' so we can diagnose it when parsing.
5046+
return Tok.getRange().getEnd() == Tok2.getLoc();
5047+
}
5048+
return false;
50445049
}
50455050

50465051
void Parser::consumeDecl(ParserPosition BeginParserPosition,
@@ -9814,47 +9819,18 @@ ParserResult<MacroDecl> Parser::parseDeclMacro(DeclAttributes &attributes) {
98149819
ParserResult<MacroExpansionDecl>
98159820
Parser::parseDeclMacroExpansion(ParseDeclOptions flags,
98169821
DeclAttributes &attributes) {
9817-
SourceLoc poundLoc = consumeToken(tok::pound);
9822+
SourceLoc poundLoc;
98189823
DeclNameLoc macroNameLoc;
9819-
DeclNameRef macroNameRef = parseDeclNameRef(
9820-
macroNameLoc, diag::macro_expansion_decl_expected_macro_identifier,
9821-
DeclNameOptions());
9822-
if (!macroNameRef)
9823-
return makeParserError();
9824-
9825-
ParserStatus status;
9824+
DeclNameRef macroNameRef;
98269825
SourceLoc leftAngleLoc, rightAngleLoc;
9827-
SmallVector<TypeRepr *, 8> genericArgs;
9828-
if (canParseAsGenericArgumentList()) {
9829-
auto genericArgsStatus = parseGenericArguments(
9830-
genericArgs, leftAngleLoc, rightAngleLoc);
9831-
status |= genericArgsStatus;
9832-
if (genericArgsStatus.isErrorOrHasCompletion())
9833-
diagnose(leftAngleLoc, diag::while_parsing_as_left_angle_bracket);
9834-
}
9835-
9826+
SmallVector<TypeRepr *, 4> genericArgs;
98369827
ArgumentList *argList = nullptr;
9837-
if (Tok.isFollowingLParen()) {
9838-
auto result = parseArgumentList(tok::l_paren, tok::r_paren,
9839-
/*isExprBasic*/ false,
9840-
/*allowTrailingClosure*/ true);
9841-
status |= result;
9842-
if (result.hasCodeCompletion())
9843-
return makeParserCodeCompletionResult<MacroExpansionDecl>();
9844-
argList = result.getPtrOrNull();
9845-
} else if (Tok.is(tok::l_brace)) {
9846-
SmallVector<Argument, 2> trailingClosures;
9847-
auto closuresStatus = parseTrailingClosures(/*isExprBasic*/ false,
9848-
macroNameLoc.getSourceRange(),
9849-
trailingClosures);
9850-
status |= closuresStatus;
9851-
9852-
if (!trailingClosures.empty()) {
9853-
argList = ArgumentList::createParsed(Context, SourceLoc(),
9854-
trailingClosures, SourceLoc(),
9855-
/*trailingClosureIdx*/ 0);
9856-
}
9857-
}
9828+
ParserStatus status = parseFreestandingMacroExpansion(
9829+
poundLoc, macroNameLoc, macroNameRef, leftAngleLoc, genericArgs,
9830+
rightAngleLoc, argList, /*isExprBasic=*/false,
9831+
diag::macro_expansion_decl_expected_macro_identifier);
9832+
if (!macroNameRef)
9833+
return status;
98589834

98599835
auto *med = new (Context) MacroExpansionDecl(
98609836
CurDeclContext, poundLoc, macroNameRef, macroNameLoc, leftAngleLoc,

lib/Parse/ParseExpr.cpp

Lines changed: 74 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -2269,6 +2269,71 @@ DeclNameRef Parser::parseDeclNameRef(DeclNameLoc &loc,
22692269
return DeclNameRef({ Context, baseName, argumentLabels });
22702270
}
22712271

2272+
ParserStatus Parser::parseFreestandingMacroExpansion(
2273+
SourceLoc &poundLoc, DeclNameLoc &macroNameLoc, DeclNameRef &macroNameRef,
2274+
SourceLoc &leftAngleLoc, SmallVectorImpl<TypeRepr *> &genericArgs,
2275+
SourceLoc &rightAngleLoc, ArgumentList *&argList, bool isExprBasic,
2276+
const Diagnostic &diag) {
2277+
SourceLoc poundEndLoc = Tok.getRange().getEnd();
2278+
poundLoc = consumeToken(tok::pound);
2279+
2280+
if (Tok.isAtStartOfLine()) {
2281+
// Diagnose and bail out if there's no macro name on the same line.
2282+
diagnose(poundEndLoc, diag);
2283+
return makeParserError();
2284+
}
2285+
2286+
bool hasWhitespaceBeforeName = poundEndLoc != Tok.getLoc();
2287+
2288+
// Diagnose and parse keyword right after `#`.
2289+
if (Tok.isKeyword() && !hasWhitespaceBeforeName) {
2290+
diagnose(Tok, diag::keyword_cant_be_identifier, Tok.getText());
2291+
diagnose(Tok, diag::backticks_to_escape)
2292+
.fixItReplace(Tok.getLoc(), "`" + Tok.getText().str() + "`");
2293+
2294+
// Let 'parseDeclNameRef' to parse this as an identifier.
2295+
Tok.setKind(tok::identifier);
2296+
}
2297+
macroNameRef = parseDeclNameRef(macroNameLoc, diag, DeclNameOptions());
2298+
if (!macroNameRef)
2299+
return makeParserError();
2300+
2301+
// Diagnose whitespace between '#' and the macro name, and continue parsing.
2302+
if (hasWhitespaceBeforeName) {
2303+
diagnose(poundEndLoc, diag::extra_whitespace_macro_expansion_identifier)
2304+
.fixItRemoveChars(poundEndLoc, macroNameLoc.getStartLoc());
2305+
}
2306+
2307+
ParserStatus status;
2308+
if (canParseAsGenericArgumentList()) {
2309+
auto genericArgsStatus =
2310+
parseGenericArguments(genericArgs, leftAngleLoc, rightAngleLoc);
2311+
status |= genericArgsStatus;
2312+
if (genericArgsStatus.isErrorOrHasCompletion())
2313+
diagnose(leftAngleLoc, diag::while_parsing_as_left_angle_bracket);
2314+
}
2315+
2316+
if (Tok.isFollowingLParen()) {
2317+
auto result = parseArgumentList(tok::l_paren, tok::r_paren, isExprBasic,
2318+
/*allowTrailingClosure*/ true);
2319+
status |= result;
2320+
argList = result.getPtrOrNull();
2321+
} else if (Tok.is(tok::l_brace)) {
2322+
SmallVector<Argument, 2> trailingClosures;
2323+
auto closuresStatus = parseTrailingClosures(
2324+
/*isExprBasic*/ false, macroNameLoc.getSourceRange(), trailingClosures);
2325+
status |= closuresStatus;
2326+
2327+
if (!trailingClosures.empty()) {
2328+
argList = ArgumentList::createParsed(Context, SourceLoc(),
2329+
trailingClosures, SourceLoc(),
2330+
/*trailingClosureIdx*/ 0);
2331+
}
2332+
}
2333+
2334+
return status;
2335+
}
2336+
22722337
/// expr-identifier:
22732338
/// unqualified-decl-name generic-args?
22742339
ParserResult<Expr> Parser::parseExprIdentifier() {
@@ -3376,45 +3441,18 @@ Parser::parseExprCallSuffix(ParserResult<Expr> fn, bool isExprBasic) {
33763441
}
33773442

33783443
ParserResult<Expr> Parser::parseExprMacroExpansion(bool isExprBasic) {
3379-
SourceLoc poundLoc = consumeToken(tok::pound);
3444+
SourceLoc poundLoc;
33803445
DeclNameLoc macroNameLoc;
3381-
DeclNameRef macroNameRef = parseDeclNameRef(
3382-
macroNameLoc, diag::macro_expansion_expr_expected_macro_identifier,
3383-
DeclNameOptions());
3384-
if (!macroNameRef)
3385-
return makeParserError();
3386-
3387-
ParserStatus status;
3446+
DeclNameRef macroNameRef;
33883447
SourceLoc leftAngleLoc, rightAngleLoc;
3389-
SmallVector<TypeRepr *, 8> genericArgs;
3390-
if (canParseAsGenericArgumentList()) {
3391-
auto genericArgsStatus = parseGenericArguments(
3392-
genericArgs, leftAngleLoc, rightAngleLoc);
3393-
status |= genericArgsStatus;
3394-
if (genericArgsStatus.isErrorOrHasCompletion())
3395-
diagnose(leftAngleLoc, diag::while_parsing_as_left_angle_bracket);
3396-
}
3397-
3448+
SmallVector<TypeRepr *, 4> genericArgs;
33983449
ArgumentList *argList = nullptr;
3399-
if (Tok.isFollowingLParen()) {
3400-
auto result = parseArgumentList(tok::l_paren, tok::r_paren, isExprBasic,
3401-
/*allowTrailingClosure*/ true);
3402-
argList = result.getPtrOrNull();
3403-
status |= result;
3404-
} else if (Tok.is(tok::l_brace) &&
3405-
isValidTrailingClosure(isExprBasic, *this)) {
3406-
SmallVector<Argument, 2> trailingClosures;
3407-
auto closureStatus = parseTrailingClosures(isExprBasic,
3408-
macroNameLoc.getSourceRange(),
3409-
trailingClosures);
3410-
status |= closureStatus;
3411-
3412-
if (!trailingClosures.empty()) {
3413-
argList = ArgumentList::createParsed(Context, SourceLoc(),
3414-
trailingClosures, SourceLoc(),
3415-
/*trailingClosureIdx*/ 0);
3416-
}
3417-
}
3450+
ParserStatus status = parseFreestandingMacroExpansion(
3451+
poundLoc, macroNameLoc, macroNameRef, leftAngleLoc, genericArgs,
3452+
rightAngleLoc, argList, isExprBasic,
3453+
diag::macro_expansion_expr_expected_macro_identifier);
3454+
if (!macroNameRef)
3455+
return status;
34183456

34193457
return makeParserResult(
34203458
status,

test/Macros/macro_keywordname.swift

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
// REQUIRES: swift_swift_parser
2+
3+
// RUN: %empty-directory(%t)
4+
// RUN: mkdir -p %t/src
5+
// RUN: mkdir -p %t/plugins
6+
// RUN: mkdir -p %t/lib
7+
8+
// RUN: split-file %s %t/src
9+
10+
//#-- Prepare the macro dylib plugin.
11+
// RUN: %host-build-swift \
12+
// RUN: -swift-version 5 \
13+
// RUN: -emit-library -o %t/plugins/%target-library-name(MacroDefinition) \
14+
// RUN: -module-name MacroDefinition \
15+
// RUN: %t/src/MacroDefinition.swift \
16+
// RUN: -g -no-toolchain-stdlib-rpath
17+
18+
//#-- Prepare the macro library.
19+
// RUN: %target-swift-frontend \
20+
// RUN: -swift-version 5 \
21+
// RUN: -emit-module -o %t/lib/MacroLib.swiftmodule \
22+
// RUN: -module-name MacroLib \
23+
// RUN: -plugin-path %t/plugins \
24+
// RUN: %t/src/MacroLib.swift
25+
26+
// RUN: %target-swift-frontend \
27+
// RUN: -typecheck -verify \
28+
// RUN: -I %t/lib \
29+
// RUN: -plugin-path %t/plugins \
30+
// RUN: %t/src/test.swift
31+
32+
//--- MacroDefinition.swift
33+
import SwiftSyntax
34+
import SwiftSyntaxMacros
35+
36+
public struct OneMacro: ExpressionMacro {
37+
public static func expansion(
38+
of node: some FreestandingMacroExpansionSyntax,
39+
in context: some MacroExpansionContext
40+
) throws -> ExprSyntax {
41+
return "1"
42+
}
43+
}
44+
45+
46+
//--- MacroLib.swift
47+
@freestanding(expression) public macro `public`() -> Int = #externalMacro(module: "MacroDefinition", type: "OneMacro")
48+
@freestanding(expression) public macro `escaped`() -> Int = #externalMacro(module: "MacroDefinition", type: "OneMacro")
49+
@freestanding(expression) public macro normal() -> Int = #externalMacro(module: "MacroDefinition", type: "OneMacro")
50+
51+
//--- test.swift
52+
import MacroLib
53+
@freestanding(expression) public macro `class`() -> Int = #externalMacro(module: "MacroDefinition", type: "OneMacro")
54+
55+
func test() {
56+
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}}
57+
let _: Int = #`public`()
58+
let _: Int = #escaped()
59+
let _: Int = #`class`()
60+
let _: Int = #normal()
61+
}

test/Parse/macro_decl.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,3 +46,11 @@ func test() {
4646
@CustomAttr
4747
isolated #someFunc
4848
}
49+
50+
public # someFunc // expected-error {{extraneous whitespace between '#' and macro name is not permitted}} {{9-10=}}
51+
52+
struct S {
53+
# someFunc // expected-error {{extraneous whitespace between '#' and macro name is not permitted}} {{4-5=}}
54+
#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`}}
55+
# struct Inner {} // expected-error {{expected a macro identifier for a pound literal declaration}} expected-error {{consecutive declarations on a line}}
56+
}

test/Parse/macro_expr.swift

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,3 +35,14 @@ _ = #another {
3535

3636
// expected-error @+1 {{expected a macro identifier for a pound literal expression}}
3737
_ = #()
38+
39+
do {
40+
_ = # // expected-error {{expected a macro identifier for a pound literal expression}}
41+
name()
42+
}
43+
do {
44+
_ = # macro() // expected-error {{extraneous whitespace between '#' and macro name is not permitted}} {{8-9=}}
45+
}
46+
do {
47+
_ = #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`}}
48+
}

0 commit comments

Comments
 (0)