Skip to content

Commit 2fbc8b3

Browse files
authored
Merge pull request #74559 from hamishknight/pound-if-skip
[Parse] Handle `#if` in brace skipping logic
2 parents c686c30 + eeced06 commit 2fbc8b3

File tree

9 files changed

+153
-43
lines changed

9 files changed

+153
-43
lines changed

include/swift/AST/DiagnosticsParse.def

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ ERROR(extra_tokens_conditional_compilation_directive,none,
5151
"extra tokens following conditional compilation directive", ())
5252
ERROR(unexpected_rbrace_in_conditional_compilation_block,none,
5353
"unexpected '}' in conditional compilation block", ())
54+
ERROR(ifconfig_unexpectedtoken,none,
55+
"unexpected tokens in '#if' body", ())
5456
ERROR(unexpected_if_following_else_compilation_directive,none,
5557
"unexpected 'if' keyword following '#else' conditional compilation "
5658
"directive; did you mean '#elseif'?",

include/swift/Parse/Parser.h

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,15 @@ enum class IfConfigElementsRole {
105105
Skipped
106106
};
107107

108+
/// Describes the context in which the '#if' is being parsed.
109+
enum class IfConfigContext {
110+
BraceItems,
111+
DeclItems,
112+
SwitchStmt,
113+
PostfixExpr,
114+
DeclAttrs
115+
};
116+
108117
/// The main class used for parsing a source file (.swift or .sil).
109118
///
110119
/// Rather than instantiating a Parser yourself, use one of the parsing APIs
@@ -993,8 +1002,9 @@ class Parser {
9931002
/// them however they wish. The parsing function will be provided with the
9941003
/// location of the clause token (`#if`, `#else`, etc.), the condition,
9951004
/// whether this is the active clause, and the role of the elements.
996-
template<typename Result>
1005+
template <typename Result>
9971006
Result parseIfConfigRaw(
1007+
IfConfigContext ifConfigContext,
9981008
llvm::function_ref<void(SourceLoc clauseLoc, Expr *condition,
9991009
bool isActive, IfConfigElementsRole role)>
10001010
parseElements,
@@ -1003,7 +1013,8 @@ class Parser {
10031013
/// Parse a #if ... #endif directive.
10041014
/// Delegate callback function to parse elements in the blocks.
10051015
ParserResult<IfConfigDecl> parseIfConfig(
1006-
llvm::function_ref<void(SmallVectorImpl<ASTNode> &, bool)> parseElements);
1016+
IfConfigContext ifConfigContext,
1017+
llvm::function_ref<void(SmallVectorImpl<ASTNode> &, bool)> parseElements);
10071018

10081019
/// Parse an #if ... #endif containing only attributes.
10091020
ParserStatus parseIfConfigDeclAttributes(

lib/Parse/ParseDecl.cpp

Lines changed: 29 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5659,10 +5659,11 @@ static unsigned skipUntilMatchingRBrace(Parser &P,
56595659
HasPotentialRegexLiteral = false;
56605660

56615661
unsigned OpenBraces = 1;
5662+
unsigned OpenPoundIf = 0;
56625663

56635664
bool LastTokenWasFunc = false;
56645665

5665-
while (OpenBraces != 0 && P.Tok.isNot(tok::eof)) {
5666+
while ((OpenBraces != 0 || OpenPoundIf != 0) && P.Tok.isNot(tok::eof)) {
56665667
// Detect 'func' followed by an operator identifier.
56675668
if (LastTokenWasFunc) {
56685669
LastTokenWasFunc = false;
@@ -5693,6 +5694,24 @@ static unsigned skipUntilMatchingRBrace(Parser &P,
56935694
return OpenBraces;
56945695
}
56955696

5697+
// Match opening `#if` with closing `#endif` to match what the parser does,
5698+
// `#if` + `#endif` are considered stronger delimiters than `{` + `}`.
5699+
if (P.consumeIf(tok::pound_if)) {
5700+
OpenPoundIf += 1;
5701+
continue;
5702+
}
5703+
if (OpenPoundIf != 0) {
5704+
// We're in a `#if`, check to see if we've reached the end.
5705+
if (P.consumeIf(tok::pound_endif)) {
5706+
OpenPoundIf -= 1;
5707+
continue;
5708+
}
5709+
// Consume the next token and continue iterating. We can swallow any
5710+
// amount of '{' and '}' while in the `#if`.
5711+
P.consumeToken();
5712+
continue;
5713+
}
5714+
56965715
if (P.consumeIf(tok::l_brace)) {
56975716
++OpenBraces;
56985717
continue;
@@ -6192,23 +6211,16 @@ ParserResult<Decl> Parser::parseDecl(bool IsAtStartOfLineOrPreviousHadSemi,
61926211

61936212
if (Tok.is(tok::pound_if) && !ifConfigContainsOnlyAttributes()) {
61946213
auto IfConfigResult = parseIfConfig(
6195-
[&](SmallVectorImpl<ASTNode> &Decls, bool IsActive) {
6196-
ParserStatus Status;
6197-
bool PreviousHadSemi = true;
6198-
while (Tok.isNot(tok::pound_else, tok::pound_endif, tok::pound_elseif,
6199-
tok::eof)) {
6200-
if (Tok.is(tok::r_brace)) {
6201-
diagnose(Tok.getLoc(),
6202-
diag::unexpected_rbrace_in_conditional_compilation_block);
6203-
// If we see '}', following declarations don't look like belong to
6204-
// the current decl context; skip them.
6205-
skipUntilConditionalBlockClose();
6206-
break;
6214+
IfConfigContext::DeclItems,
6215+
[&](SmallVectorImpl<ASTNode> &Decls, bool IsActive) {
6216+
ParserStatus Status;
6217+
bool PreviousHadSemi = true;
6218+
while (Tok.isNot(tok::pound_else, tok::pound_endif, tok::pound_elseif,
6219+
tok::r_brace, tok::eof)) {
6220+
Status |= parseDeclItem(PreviousHadSemi,
6221+
[&](Decl *D) { Decls.emplace_back(D); });
62076222
}
6208-
Status |= parseDeclItem(PreviousHadSemi,
6209-
[&](Decl *D) { Decls.emplace_back(D); });
6210-
}
6211-
});
6223+
});
62126224
if (IfConfigResult.hasCodeCompletion() && isIDEInspectionFirstPass()) {
62136225
consumeDecl(BeginParserPosition, CurDeclContext->isModuleScopeContext());
62146226
return makeParserError();

lib/Parse/ParseExpr.cpp

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1479,8 +1479,9 @@ Parser::parseExprPostfixSuffix(ParserResult<Expr> Result, bool isExprBasic,
14791479
}
14801480

14811481
llvm::SmallPtrSet<Expr *, 4> exprsWithBindOptional;
1482-
auto ICD =
1483-
parseIfConfig([&](SmallVectorImpl<ASTNode> &elements, bool isActive) {
1482+
auto ICD = parseIfConfig(
1483+
IfConfigContext::PostfixExpr,
1484+
[&](SmallVectorImpl<ASTNode> &elements, bool isActive) {
14841485
// Although we know the '#if' body starts with period,
14851486
// '#elseif'/'#else' bodies might start with invalid tokens.
14861487
if (isAtStartOfPostfixExprSuffix() || Tok.is(tok::pound_if)) {
@@ -1492,13 +1493,6 @@ Parser::parseExprPostfixSuffix(ParserResult<Expr> Result, bool isExprBasic,
14921493
exprsWithBindOptional.insert(expr.get());
14931494
elements.push_back(expr.get());
14941495
}
1495-
1496-
// Don't allow any character other than the postfix expression.
1497-
if (!Tok.isAny(tok::pound_elseif, tok::pound_else, tok::pound_endif,
1498-
tok::eof)) {
1499-
diagnose(Tok, diag::expr_postfix_ifconfig_unexpectedtoken);
1500-
skipUntilConditionalBlockClose();
1501-
}
15021496
});
15031497
if (ICD.isNull())
15041498
break;

lib/Parse/ParseIfConfig.cpp

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -769,11 +769,12 @@ static Expr *findAnyLikelySimulatorEnvironmentTest(Expr *Condition) {
769769

770770
/// Parse and populate a #if ... #endif directive.
771771
/// Delegate callback function to parse elements in the blocks.
772-
template<typename Result>
772+
template <typename Result>
773773
Result Parser::parseIfConfigRaw(
774-
llvm::function_ref<void(SourceLoc clauseLoc, Expr *condition,
775-
bool isActive, IfConfigElementsRole role)>
776-
parseElements,
774+
IfConfigContext ifConfigContext,
775+
llvm::function_ref<void(SourceLoc clauseLoc, Expr *condition, bool isActive,
776+
IfConfigElementsRole role)>
777+
parseElements,
777778
llvm::function_ref<Result(SourceLoc endLoc, bool hadMissingEnd)> finish) {
778779
assert(Tok.is(tok::pound_if));
779780

@@ -897,6 +898,24 @@ Result Parser::parseIfConfigRaw(
897898
ClauseLoc, Condition, isActive, IfConfigElementsRole::Skipped);
898899
}
899900

901+
// We ought to be at the end of the clause, diagnose if not and skip to
902+
// the closing token. `#if` + `#endif` are considered stronger delimiters
903+
// than `{` + `}`, so we can skip over those too.
904+
if (Tok.isNot(tok::pound_elseif, tok::pound_else, tok::pound_endif,
905+
tok::eof)) {
906+
if (Tok.is(tok::r_brace)) {
907+
diagnose(Tok, diag::unexpected_rbrace_in_conditional_compilation_block);
908+
} else if (ifConfigContext == IfConfigContext::PostfixExpr) {
909+
diagnose(Tok, diag::expr_postfix_ifconfig_unexpectedtoken);
910+
} else {
911+
// We ought to never hit this case in practice, but fall back to a
912+
// generic 'unexpected tokens' diagnostic if we weren't able to produce
913+
// a better diagnostic during the parsing of the clause.
914+
diagnose(Tok, diag::ifconfig_unexpectedtoken);
915+
}
916+
skipUntilConditionalBlockClose();
917+
}
918+
900919
// Record the clause range info in SourceFile.
901920
if (shouldEvaluate) {
902921
auto kind = isActive ? IfConfigClauseRangeInfo::ActiveClause
@@ -927,9 +946,11 @@ Result Parser::parseIfConfigRaw(
927946
/// Parse and populate a #if ... #endif directive.
928947
/// Delegate callback function to parse elements in the blocks.
929948
ParserResult<IfConfigDecl> Parser::parseIfConfig(
949+
IfConfigContext ifConfigContext,
930950
llvm::function_ref<void(SmallVectorImpl<ASTNode> &, bool)> parseElements) {
931951
SmallVector<IfConfigClause, 4> clauses;
932952
return parseIfConfigRaw<ParserResult<IfConfigDecl>>(
953+
ifConfigContext,
933954
[&](SourceLoc clauseLoc, Expr *condition, bool isActive,
934955
IfConfigElementsRole role) {
935956
SmallVector<ASTNode, 16> elements;
@@ -940,7 +961,8 @@ ParserResult<IfConfigDecl> Parser::parseIfConfig(
940961

941962
clauses.emplace_back(
942963
clauseLoc, condition, Context.AllocateCopy(elements), isActive);
943-
}, [&](SourceLoc endLoc, bool hadMissingEnd) {
964+
},
965+
[&](SourceLoc endLoc, bool hadMissingEnd) {
944966
auto *ICD = new (Context) IfConfigDecl(CurDeclContext,
945967
Context.AllocateCopy(clauses),
946968
endLoc, hadMissingEnd);
@@ -953,6 +975,7 @@ ParserStatus Parser::parseIfConfigDeclAttributes(
953975
PatternBindingInitializer *initContext) {
954976
ParserStatus status = makeParserSuccess();
955977
return parseIfConfigRaw<ParserStatus>(
978+
IfConfigContext::DeclAttrs,
956979
[&](SourceLoc clauseLoc, Expr *condition, bool isActive,
957980
IfConfigElementsRole role) {
958981
if (isActive) {

lib/Parse/ParseStmt.cpp

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -370,12 +370,14 @@ ParserStatus Parser::parseBraceItems(SmallVectorImpl<ASTNode> &Entries,
370370
PreviousHadSemi = false;
371371
if (Tok.is(tok::pound_if) && !isStartOfSwiftDecl()) {
372372
auto IfConfigResult = parseIfConfig(
373-
[&](SmallVectorImpl<ASTNode> &Elements, bool IsActive) {
374-
parseBraceItems(Elements, Kind, IsActive
375-
? BraceItemListKind::ActiveConditionalBlock
376-
: BraceItemListKind::InactiveConditionalBlock,
377-
IsFollowingGuard);
378-
});
373+
IfConfigContext::BraceItems,
374+
[&](SmallVectorImpl<ASTNode> &Elements, bool IsActive) {
375+
parseBraceItems(Elements, Kind,
376+
IsActive
377+
? BraceItemListKind::ActiveConditionalBlock
378+
: BraceItemListKind::InactiveConditionalBlock,
379+
IsFollowingGuard);
380+
});
379381
if (IfConfigResult.hasCodeCompletion() && isIDEInspectionFirstPass()) {
380382
consumeDecl(BeginParserPosition, IsTopLevel);
381383
return IfConfigResult;
@@ -2573,10 +2575,11 @@ Parser::parseStmtCases(SmallVectorImpl<ASTNode> &cases, bool IsActive) {
25732575
} else if (Tok.is(tok::pound_if)) {
25742576
// '#if' in 'case' position can enclose one or more 'case' or 'default'
25752577
// clauses.
2576-
auto IfConfigResult = parseIfConfig(
2577-
[&](SmallVectorImpl<ASTNode> &Elements, bool IsActive) {
2578-
parseStmtCases(Elements, IsActive);
2579-
});
2578+
auto IfConfigResult =
2579+
parseIfConfig(IfConfigContext::SwitchStmt,
2580+
[&](SmallVectorImpl<ASTNode> &Elements, bool IsActive) {
2581+
parseStmtCases(Elements, IsActive);
2582+
});
25802583
Status |= IfConfigResult;
25812584
if (auto ICD = IfConfigResult.getPtrOrNull()) {
25822585
cases.emplace_back(ICD);
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// RUN: %target-typecheck-verify-swift
2+
3+
// Make sure we can recover by ignoring the '}' in the #if.
4+
struct S {
5+
func foo() {
6+
#if true
7+
} // expected-error {{unexpected '}' in conditional compilation block}}
8+
#endif
9+
}
10+
func bar() {
11+
#if false
12+
} // expected-error {{unexpected '}' in conditional compilation block}}
13+
#endif
14+
}
15+
func baz() {}
16+
}

test/Parse/rdar129195380.swift

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// RUN: not %target-swift-frontend -experimental-skip-all-function-bodies -dump-parse %s | %FileCheck %s
2+
3+
// rdar://129195380 - Make sure the skipping logic can handle #if.
4+
struct S {
5+
// CHECK: func_decl{{.*}}:[[@LINE+1]]:3 - line:[[@LINE+11]]:3{{.*}}"foo()"
6+
func foo() {
7+
#if true
8+
}
9+
#if true
10+
func bar() {
11+
#else
12+
}
13+
#endif
14+
}
15+
#endif
16+
}
17+
// CHECK: func_decl{{.*}}:[[@LINE+1]]:3 - line:[[@LINE+1]]:15{{.*}}"baz()"
18+
func baz() {}
19+
}
20+
21+
// The '#if' is unterminated here, so swallows the rest of the file.
22+
// CHECK: struct_decl{{.*}}:[[@LINE+1]]:1 - line:[[@LINE+14]]:14{{.*}}"R"
23+
struct R {
24+
#if false
25+
}
26+
#if true
27+
}
28+
#endif
29+
}
30+
#else
31+
}
32+
// CHECK-NOT: qux
33+
func qux() {}
34+
}
35+
36+
func flim() {}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// rdar://129195380 - Make sure we correctly handle '#if' when skipping function
2+
// bodies.
3+
class C {
4+
func test1() {
5+
#if FOOBAR
6+
// RUN: %sourcekitd-test -req=cursor -pos=%(line + 2):5 %s -- %s -DFOOBAR
7+
// RUN: %sourcekitd-test -req=cursor -pos=%(line + 1):5 %s -- %s
8+
abc
9+
}
10+
11+
func test2() {
12+
}
13+
}

0 commit comments

Comments
 (0)