Skip to content

Commit a85ca13

Browse files
committed
[Parse] Unify recovery for invalid tokens following a #if body
Previously we would only diagnose and recover for invalid tokens following a `#if` body for the decl and postfix expression case. Sink this logic into `parseIfConfigRaw`, ensuring that we do this for all `#if` cases. This requires propagating the context we're parsing in to customize the diagnostic.
1 parent cd9d202 commit a85ca13

File tree

7 files changed

+84
-42
lines changed

7 files changed

+84
-42
lines changed

include/swift/AST/DiagnosticsParse.def

+2
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

+13-2
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

+9-16
Original file line numberDiff line numberDiff line change
@@ -6191,23 +6191,16 @@ ParserResult<Decl> Parser::parseDecl(bool IsAtStartOfLineOrPreviousHadSemi,
61916191

61926192
if (Tok.is(tok::pound_if) && !ifConfigContainsOnlyAttributes()) {
61936193
auto IfConfigResult = parseIfConfig(
6194-
[&](SmallVectorImpl<ASTNode> &Decls, bool IsActive) {
6195-
ParserStatus Status;
6196-
bool PreviousHadSemi = true;
6197-
while (Tok.isNot(tok::pound_else, tok::pound_endif, tok::pound_elseif,
6198-
tok::eof)) {
6199-
if (Tok.is(tok::r_brace)) {
6200-
diagnose(Tok.getLoc(),
6201-
diag::unexpected_rbrace_in_conditional_compilation_block);
6202-
// If we see '}', following declarations don't look like belong to
6203-
// the current decl context; skip them.
6204-
skipUntilConditionalBlockClose();
6205-
break;
6194+
IfConfigContext::DeclItems,
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::r_brace, tok::eof)) {
6200+
Status |= parseDeclItem(PreviousHadSemi,
6201+
[&](Decl *D) { Decls.emplace_back(D); });
62066202
}
6207-
Status |= parseDeclItem(PreviousHadSemi,
6208-
[&](Decl *D) { Decls.emplace_back(D); });
6209-
}
6210-
});
6203+
});
62116204
if (IfConfigResult.hasCodeCompletion() && isIDEInspectionFirstPass()) {
62126205
consumeDecl(BeginParserPosition, CurDeclContext->isModuleScopeContext());
62136206
return makeParserError();

lib/Parse/ParseExpr.cpp

+3-9
Original file line numberDiff line numberDiff line change
@@ -1461,8 +1461,9 @@ Parser::parseExprPostfixSuffix(ParserResult<Expr> Result, bool isExprBasic,
14611461
}
14621462

14631463
llvm::SmallPtrSet<Expr *, 4> exprsWithBindOptional;
1464-
auto ICD =
1465-
parseIfConfig([&](SmallVectorImpl<ASTNode> &elements, bool isActive) {
1464+
auto ICD = parseIfConfig(
1465+
IfConfigContext::PostfixExpr,
1466+
[&](SmallVectorImpl<ASTNode> &elements, bool isActive) {
14661467
// Although we know the '#if' body starts with period,
14671468
// '#elseif'/'#else' bodies might start with invalid tokens.
14681469
if (isAtStartOfPostfixExprSuffix() || Tok.is(tok::pound_if)) {
@@ -1474,13 +1475,6 @@ Parser::parseExprPostfixSuffix(ParserResult<Expr> Result, bool isExprBasic,
14741475
exprsWithBindOptional.insert(expr.get());
14751476
elements.push_back(expr.get());
14761477
}
1477-
1478-
// Don't allow any character other than the postfix expression.
1479-
if (!Tok.isAny(tok::pound_elseif, tok::pound_else, tok::pound_endif,
1480-
tok::eof)) {
1481-
diagnose(Tok, diag::expr_postfix_ifconfig_unexpectedtoken);
1482-
skipUntilConditionalBlockClose();
1483-
}
14841478
});
14851479
if (ICD.isNull())
14861480
break;

lib/Parse/ParseIfConfig.cpp

+28-5
Original file line numberDiff line numberDiff line change
@@ -768,11 +768,12 @@ static Expr *findAnyLikelySimulatorEnvironmentTest(Expr *Condition) {
768768

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

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

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

940961
clauses.emplace_back(
941962
clauseLoc, condition, Context.AllocateCopy(elements), isActive);
942-
}, [&](SourceLoc endLoc, bool hadMissingEnd) {
963+
},
964+
[&](SourceLoc endLoc, bool hadMissingEnd) {
943965
auto *ICD = new (Context) IfConfigDecl(CurDeclContext,
944966
Context.AllocateCopy(clauses),
945967
endLoc, hadMissingEnd);
@@ -952,6 +974,7 @@ ParserStatus Parser::parseIfConfigDeclAttributes(
952974
PatternBindingInitializer *initContext) {
953975
ParserStatus status = makeParserSuccess();
954976
return parseIfConfigRaw<ParserStatus>(
977+
IfConfigContext::DeclAttrs,
955978
[&](SourceLoc clauseLoc, Expr *condition, bool isActive,
956979
IfConfigElementsRole role) {
957980
if (isActive) {

lib/Parse/ParseStmt.cpp

+13-10
Original file line numberDiff line numberDiff line change
@@ -369,12 +369,14 @@ ParserStatus Parser::parseBraceItems(SmallVectorImpl<ASTNode> &Entries,
369369
PreviousHadSemi = false;
370370
if (Tok.is(tok::pound_if) && !isStartOfSwiftDecl()) {
371371
auto IfConfigResult = parseIfConfig(
372-
[&](SmallVectorImpl<ASTNode> &Elements, bool IsActive) {
373-
parseBraceItems(Elements, Kind, IsActive
374-
? BraceItemListKind::ActiveConditionalBlock
375-
: BraceItemListKind::InactiveConditionalBlock,
376-
IsFollowingGuard);
377-
});
372+
IfConfigContext::BraceItems,
373+
[&](SmallVectorImpl<ASTNode> &Elements, bool IsActive) {
374+
parseBraceItems(Elements, Kind,
375+
IsActive
376+
? BraceItemListKind::ActiveConditionalBlock
377+
: BraceItemListKind::InactiveConditionalBlock,
378+
IsFollowingGuard);
379+
});
378380
if (IfConfigResult.hasCodeCompletion() && isIDEInspectionFirstPass()) {
379381
consumeDecl(BeginParserPosition, IsTopLevel);
380382
return IfConfigResult;
@@ -2572,10 +2574,11 @@ Parser::parseStmtCases(SmallVectorImpl<ASTNode> &cases, bool IsActive) {
25722574
} else if (Tok.is(tok::pound_if)) {
25732575
// '#if' in 'case' position can enclose one or more 'case' or 'default'
25742576
// clauses.
2575-
auto IfConfigResult = parseIfConfig(
2576-
[&](SmallVectorImpl<ASTNode> &Elements, bool IsActive) {
2577-
parseStmtCases(Elements, IsActive);
2578-
});
2577+
auto IfConfigResult =
2578+
parseIfConfig(IfConfigContext::SwitchStmt,
2579+
[&](SmallVectorImpl<ASTNode> &Elements, bool IsActive) {
2580+
parseStmtCases(Elements, IsActive);
2581+
});
25792582
Status |= IfConfigResult;
25802583
if (auto ICD = IfConfigResult.getPtrOrNull()) {
25812584
cases.emplace_back(ICD);
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+
}

0 commit comments

Comments
 (0)