Skip to content

Commit b3c5fde

Browse files
committed
Parse declaration attributes within #if...#endif blocks.
Introduce support for parsing declaration attributes that occur within example: #if hasAttribute(frozen) @Frozen #endif public struct X { ... } will apply to "frozen" attribute to the struct `X`, but only when the compiler supports the "frozen" attribute. Correctly determining whether a particular `#if` block contains attributes to be associated with the following declaration vs. starting a new declaration requires arbitrary lookahead. The parser will ensure that at least one of the branches of the `#if` contains an attribute, and that none of the branches contains something that does not fit the attribute grammar, before committing to parsing the `#if` clause as part of the declaration attributes. This lookahead does occur at the top level (e.g., in the parsing of top-level declarations and code), but should only need to scan past the first `#if` line to the following token in the common case. Unlike other `#if` when used to wrap statements or declarations, we make no attempt to record the `#if` not taken anywhere in the AST. This reflects a change in attitude in the design of the AST, because we have found that trying to represent this information there (e.g., via `IfConfigDecl`) complicates clients while providing little value. This information is best kept in the syntax tree, only.
1 parent 7a75dda commit b3c5fde

File tree

6 files changed

+308
-50
lines changed

6 files changed

+308
-50
lines changed

include/swift/Parse/Parser.h

Lines changed: 51 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,16 @@ class ConsumeTokenReceiver {
100100
virtual ~ConsumeTokenReceiver() = default;
101101
};
102102

103+
/// The role of the elements in a given #if
104+
enum class IfConfigElementsRole {
105+
// Parse normally.
106+
Normal,
107+
// Parse, but only for syntax. Throw away the results.
108+
SyntaxOnly,
109+
// Already skipped; no need to parse anything.
110+
Skipped
111+
};
112+
103113
/// The main class used for parsing a source file (.swift or .sil).
104114
///
105115
/// Rather than instantiating a Parser yourself, use one of the parsing APIs
@@ -684,7 +694,8 @@ class Parser {
684694
while (Tok.isNot(K..., tok::eof, tok::r_brace, tok::pound_endif,
685695
tok::pound_else, tok::pound_elseif,
686696
tok::code_complete) &&
687-
!isStartOfStmt() && !isStartOfSwiftDecl()) {
697+
!isStartOfStmt() &&
698+
!isStartOfSwiftDecl(/*allowPoundIfAttributes=*/false)) {
688699
skipSingle();
689700
}
690701
}
@@ -945,7 +956,7 @@ class Parser {
945956
// Decl Parsing
946957

947958
/// Returns true if parser is at the start of a Swift decl or decl-import.
948-
bool isStartOfSwiftDecl();
959+
bool isStartOfSwiftDecl(bool allowPoundIfAttributes = true);
949960

950961
/// Returns true if the parser is at the start of a SIL decl.
951962
bool isStartOfSILDecl();
@@ -982,6 +993,7 @@ class Parser {
982993

983994
ParserResult<Decl> parseDecl(ParseDeclOptions Flags,
984995
bool IsAtStartOfLineOrPreviousHadSemi,
996+
bool IfConfigsAreDeclAttrs,
985997
llvm::function_ref<void(Decl*)> Handler);
986998

987999
std::pair<std::vector<Decl *>, Optional<Fingerprint>>
@@ -1004,12 +1016,29 @@ class Parser {
10041016

10051017
ParserResult<TypeDecl> parseDeclAssociatedType(ParseDeclOptions Flags,
10061018
DeclAttributes &Attributes);
1007-
1019+
1020+
/// Parse a #if ... #endif directive.
1021+
/// Delegate callback function to parse elements in the blocks and record
1022+
/// them however they wish. The parsing function will be provided with the
1023+
/// location of the clause token (`#if`, `#else`, etc.), the condition,
1024+
/// whether this is the active clause, and the role of the elements.
1025+
template<typename Result>
1026+
Result parseIfConfigRaw(
1027+
llvm::function_ref<void(SourceLoc clauseLoc, Expr *condition,
1028+
bool isActive, IfConfigElementsRole role)>
1029+
parseElements,
1030+
llvm::function_ref<Result(SourceLoc endLoc, bool hadMissingEnd)> finish);
1031+
10081032
/// Parse a #if ... #endif directive.
10091033
/// Delegate callback function to parse elements in the blocks.
10101034
ParserResult<IfConfigDecl> parseIfConfig(
10111035
llvm::function_ref<void(SmallVectorImpl<ASTNode> &, bool)> parseElements);
10121036

1037+
/// Parse an #if ... #endif containing only attributes.
1038+
ParserStatus parseIfConfigDeclAttributes(
1039+
DeclAttributes &attributes, bool ifConfigsAreDeclAttrs,
1040+
PatternBindingInitializer *initContext);
1041+
10131042
/// Parse a #error or #warning diagnostic.
10141043
ParserResult<PoundDiagnosticDecl> parseDeclPoundDiagnostic();
10151044

@@ -1020,8 +1049,26 @@ class Parser {
10201049
void setLocalDiscriminator(ValueDecl *D);
10211050
void setLocalDiscriminatorToParamList(ParameterList *PL);
10221051

1052+
/// Skip an `#if` configuration block containing only attributes.
1053+
///
1054+
/// \returns true if the skipping was successful, false otherwise.
1055+
bool skipIfConfigOfAttributes(bool &sawAnyAttributes);
1056+
1057+
/// Determine whether the `#if` at which the parser occurs only contains
1058+
/// attributes (in all branches), in which case it is treated as part of
1059+
/// an attribute list.
1060+
bool ifConfigContainsOnlyAttributes();
1061+
10231062
/// Parse the optional attributes before a declaration.
1024-
ParserStatus parseDeclAttributeList(DeclAttributes &Attributes);
1063+
ParserStatus parseDeclAttributeList(DeclAttributes &Attributes,
1064+
bool IfConfigsAreDeclAttrs = false);
1065+
1066+
/// Parse the optional attributes before a declaration.
1067+
///
1068+
/// This is the inner loop, which can be called recursively.
1069+
ParserStatus parseDeclAttributeList(DeclAttributes &Attributes,
1070+
bool IfConfigsAreDeclAttrs,
1071+
PatternBindingInitializer *initContext);
10251072

10261073
/// Parse the optional modifiers before a declaration.
10271074
bool parseDeclModifierList(DeclAttributes &Attributes, SourceLoc &StaticLoc,

lib/Parse/ParseDecl.cpp

Lines changed: 54 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -3793,26 +3793,41 @@ ParserStatus Parser::parseTypeAttribute(TypeAttributes &Attributes,
37933793
return makeParserSuccess();
37943794
}
37953795

3796+
ParserStatus Parser::parseDeclAttributeList(
3797+
DeclAttributes &Attributes, bool ifConfigsAreDeclAttrs,
3798+
PatternBindingInitializer *initContext) {
3799+
ParserStatus Status;
3800+
while (Tok.isAny(tok::at_sign, tok::pound_if)) {
3801+
if (Tok.is(tok::at_sign)) {
3802+
SyntaxParsingContext AttrCtx(SyntaxContext, SyntaxKind::Attribute);
3803+
SourceLoc AtLoc = consumeToken();
3804+
Status |= parseDeclAttribute(Attributes, AtLoc, initContext);
3805+
} else {
3806+
if (!ifConfigsAreDeclAttrs && !ifConfigContainsOnlyAttributes())
3807+
break;
3808+
3809+
Status |= parseIfConfigDeclAttributes(
3810+
Attributes, ifConfigsAreDeclAttrs, initContext);
3811+
}
3812+
}
3813+
return Status;
3814+
}
3815+
37963816
/// \verbatim
37973817
/// attribute-list:
37983818
/// /*empty*/
37993819
/// attribute-list-clause attribute-list
38003820
/// attribute-list-clause:
38013821
/// '@' attribute
38023822
/// \endverbatim
3803-
ParserStatus Parser::parseDeclAttributeList(DeclAttributes &Attributes) {
3804-
if (Tok.isNot(tok::at_sign))
3823+
ParserStatus Parser::parseDeclAttributeList(
3824+
DeclAttributes &Attributes, bool IfConfigsAreDeclAttrs) {
3825+
if (Tok.isNot(tok::at_sign, tok::pound_if))
38053826
return makeParserSuccess();
38063827

38073828
PatternBindingInitializer *initContext = nullptr;
3808-
ParserStatus Status;
38093829
SyntaxParsingContext AttrListCtx(SyntaxContext, SyntaxKind::AttributeList);
3810-
do {
3811-
SyntaxParsingContext AttrCtx(SyntaxContext, SyntaxKind::Attribute);
3812-
SourceLoc AtLoc = consumeToken();
3813-
Status |= parseDeclAttribute(Attributes, AtLoc, initContext);
3814-
} while (Tok.is(tok::at_sign));
3815-
return Status;
3830+
return parseDeclAttributeList(Attributes, IfConfigsAreDeclAttrs, initContext);
38163831
}
38173832

38183833
/// \verbatim
@@ -3902,7 +3917,8 @@ bool Parser::parseDeclModifierList(DeclAttributes &Attributes,
39023917
BacktrackingScope Scope(*this);
39033918

39043919
consumeToken(); // consume actor
3905-
isActorModifier = isStartOfSwiftDecl();
3920+
isActorModifier = isStartOfSwiftDecl(
3921+
/*allowPoundIfAttributes=*/false);
39063922
}
39073923

39083924
if (!isActorModifier)
@@ -4273,7 +4289,7 @@ static void skipAttribute(Parser &P) {
42734289
}
42744290
}
42754291

4276-
bool Parser::isStartOfSwiftDecl() {
4292+
bool Parser::isStartOfSwiftDecl(bool allowPoundIfAttributes) {
42774293
if (Tok.is(tok::at_sign) && peekToken().is(tok::kw_rethrows)) {
42784294
// @rethrows does not follow the general rule of @<identifier> so
42794295
// it is needed to short circuit this else there will be an infinite
@@ -4307,7 +4323,7 @@ bool Parser::isStartOfSwiftDecl() {
43074323
// check 'let' and 'var' right now.
43084324
if (Tok.is(tok::kw_try))
43094325
return peekToken().isAny(tok::kw_let, tok::kw_var);
4310-
4326+
43114327
// Skip an attribute, since it might be a type attribute. This can't
43124328
// happen at the top level of a scope, but we do use isStartOfSwiftDecl()
43134329
// in positions like generic argument lists.
@@ -4318,10 +4334,20 @@ bool Parser::isStartOfSwiftDecl() {
43184334

43194335
// If this attribute is the last element in the block,
43204336
// consider it is a start of incomplete decl.
4321-
if (Tok.isAny(tok::r_brace, tok::eof, tok::pound_endif))
4337+
if (Tok.isAny(tok::r_brace, tok::eof) ||
4338+
(Tok.is(tok::pound_endif) && !allowPoundIfAttributes))
43224339
return true;
43234340

4324-
return isStartOfSwiftDecl();
4341+
return isStartOfSwiftDecl(allowPoundIfAttributes);
4342+
}
4343+
4344+
// Skip a #if that contains only attributes in all branches. These will be
4345+
// parsed as attributes of a declaration, not as separate declarations.
4346+
if (Tok.is(tok::pound_if) && allowPoundIfAttributes) {
4347+
BacktrackingScope backtrack(*this);
4348+
bool sawAnyAttributes = false;
4349+
return skipIfConfigOfAttributes(sawAnyAttributes) && sawAnyAttributes &&
4350+
isStartOfSwiftDecl();
43254351
}
43264352

43274353
// If we have a decl modifying keyword, check if the next token is a valid
@@ -4343,13 +4369,13 @@ bool Parser::isStartOfSwiftDecl() {
43434369
// If we found the start of a decl while trying to skip over the
43444370
// paren, then we have something incomplete like 'private('. Return
43454371
// true for better recovery.
4346-
if (isStartOfSwiftDecl())
4372+
if (isStartOfSwiftDecl(/*allowPoundIfAttributes=*/false))
43474373
return true;
43484374

43494375
skipSingle();
43504376
}
43514377
}
4352-
return isStartOfSwiftDecl();
4378+
return isStartOfSwiftDecl(/*allowPoundIfAttributes=*/false);
43534379
}
43544380
}
43554381

@@ -4376,7 +4402,7 @@ bool Parser::isStartOfSwiftDecl() {
43764402
consumeToken(tok::l_paren);
43774403
consumeToken(tok::identifier);
43784404
consumeToken(tok::r_paren);
4379-
return isStartOfSwiftDecl();
4405+
return isStartOfSwiftDecl(/*allowPoundIfAttributes=*/false);
43804406
}
43814407

43824408
if (Tok.isContextualKeyword("actor")) {
@@ -4388,7 +4414,7 @@ bool Parser::isStartOfSwiftDecl() {
43884414
// it's an actor declaration, otherwise, it isn't.
43894415
do {
43904416
consumeToken();
4391-
} while (isStartOfSwiftDecl());
4417+
} while (isStartOfSwiftDecl(/*allowPoundIfAttributes=*/false));
43924418
return Tok.is(tok::identifier);
43934419
}
43944420

@@ -4399,7 +4425,7 @@ bool Parser::isStartOfSwiftDecl() {
43994425
// Otherwise, do a recursive parse.
44004426
Parser::BacktrackingScope Backtrack(*this);
44014427
consumeToken(tok::identifier);
4402-
return isStartOfSwiftDecl();
4428+
return isStartOfSwiftDecl(/*allowPoundIfAttributes=*/false);
44034429
}
44044430

44054431
bool Parser::isStartOfSILDecl() {
@@ -4510,12 +4536,13 @@ setOriginalDeclarationForDifferentiableAttributes(DeclAttributes attrs,
45104536
ParserResult<Decl>
45114537
Parser::parseDecl(ParseDeclOptions Flags,
45124538
bool IsAtStartOfLineOrPreviousHadSemi,
4539+
bool IfConfigsAreDeclAttrs,
45134540
llvm::function_ref<void(Decl*)> Handler) {
45144541
ParserPosition BeginParserPosition;
45154542
if (isCodeCompletionFirstPass())
45164543
BeginParserPosition = getParserPosition();
45174544

4518-
if (Tok.is(tok::pound_if)) {
4545+
if (Tok.is(tok::pound_if) && !ifConfigContainsOnlyAttributes()) {
45194546
auto IfConfigResult = parseIfConfig(
45204547
[&](SmallVectorImpl<ASTNode> &Decls, bool IsActive) {
45214548
ParserStatus Status;
@@ -4574,7 +4601,8 @@ Parser::parseDecl(ParseDeclOptions Flags,
45744601
DeclAttributes Attributes;
45754602
if (Tok.hasComment())
45764603
Attributes.add(new (Context) RawDocCommentAttr(Tok.getCommentRange()));
4577-
ParserStatus AttrStatus = parseDeclAttributeList(Attributes);
4604+
ParserStatus AttrStatus = parseDeclAttributeList(
4605+
Attributes, IfConfigsAreDeclAttrs);
45784606

45794607
// Parse modifiers.
45804608
// Keep track of where and whether we see a contextual keyword on the decl.
@@ -5356,7 +5384,9 @@ ParserStatus Parser::parseDeclItem(bool &PreviousHadSemi,
53565384
if (loadCurrentSyntaxNodeFromCache()) {
53575385
return ParserStatus();
53585386
}
5359-
Result = parseDecl(Options, IsAtStartOfLineOrPreviousHadSemi, handler);
5387+
Result = parseDecl(
5388+
Options, IsAtStartOfLineOrPreviousHadSemi,
5389+
/* IfConfigsAreDeclAttrs=*/false, handler);
53605390
if (Result.isParseErrorOrHasCompletion())
53615391
skipUntilDeclRBrace(tok::semi, tok::pound_endif);
53625392
SourceLoc SemiLoc;
@@ -6200,7 +6230,8 @@ void Parser::skipSILUntilSwiftDecl() {
62006230
// Tell the lexer we're about to start lexing SIL.
62016231
Lexer::SILBodyRAII sbr(*L);
62026232

6203-
while (!Tok.is(tok::eof) && !isStartOfSwiftDecl()) {
6233+
while (!Tok.is(tok::eof) &&
6234+
!isStartOfSwiftDecl(/*allowPoundIfAttributes=*/false)) {
62046235
// SIL pound dotted paths need to be skipped specially as they can contain
62056236
// decl keywords like 'subscript'.
62066237
if (consumeIf(tok::pound)) {

0 commit comments

Comments
 (0)