Skip to content

Commit 95eea2f

Browse files
committed
[Parse] Implement "missing 'func' keyword" diagnostic with a fix-it
- When parsing a type or extension declaration, attempt to parse a function or property declaration when meeting an identifier, an operator or a paren (for tuple declarations). - Produce the diagnostic with a fix-it suggesting to insert the needed keyword - Recover parsing as if the declaration with the missing keyword is a function/property declaration Resolves https://bugs.swift.org/browse/SR-10477
1 parent 06fd9c4 commit 95eea2f

File tree

12 files changed

+271
-65
lines changed

12 files changed

+271
-65
lines changed

include/swift/AST/DiagnosticsParse.def

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,8 @@ ERROR(expected_decl,none,
216216
"expected declaration", ())
217217
ERROR(expected_identifier_in_decl,none,
218218
"expected identifier in %0 declaration", (StringRef))
219+
ERROR(expected_keyword_in_decl,none,
220+
"expected '%0' keyword in %1 declaration", (StringRef, StringRef))
219221
ERROR(number_cant_start_decl_name,none,
220222
"%0 name can only start with a letter or underscore, not a number",
221223
(StringRef))

include/swift/Parse/Parser.h

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -869,6 +869,7 @@ class Parser {
869869
}
870870

871871
ParserResult<Decl> parseDecl(ParseDeclOptions Flags,
872+
bool IsAtStartOfLineOrPreviousHadSemi,
872873
llvm::function_ref<void(Decl*)> Handler);
873874

874875
void parseDeclDelayed();
@@ -994,7 +995,8 @@ class Parser {
994995
SmallVectorImpl<Decl *> &Decls,
995996
SourceLoc StaticLoc,
996997
StaticSpellingKind StaticSpelling,
997-
SourceLoc TryLoc);
998+
SourceLoc TryLoc,
999+
bool HasLetOrVarKeyword = true);
9981000

9991001
struct ParsedAccessors;
10001002
ParserStatus parseGetSet(ParseDeclOptions Flags,
@@ -1018,7 +1020,8 @@ class Parser {
10181020
ParserResult<FuncDecl> parseDeclFunc(SourceLoc StaticLoc,
10191021
StaticSpellingKind StaticSpelling,
10201022
ParseDeclOptions Flags,
1021-
DeclAttributes &Attributes);
1023+
DeclAttributes &Attributes,
1024+
bool HasFuncKeyword = true);
10221025
void parseAbstractFunctionBody(AbstractFunctionDecl *AFD);
10231026
bool parseAbstractFunctionBodyDelayed(AbstractFunctionDecl *AFD);
10241027
ParserResult<ProtocolDecl> parseDeclProtocol(ParseDeclOptions Flags,

lib/Parse/ParseDecl.cpp

Lines changed: 76 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
//
33
// This source file is part of the Swift.org open source project
44
//
5-
// Copyright (c) 2014 - 2018 Apple Inc. and the Swift project authors
5+
// Copyright (c) 2014 - 2019 Apple Inc. and the Swift project authors
66
// Licensed under Apache License v2.0 with Runtime Library Exception
77
//
88
// See https://swift.org/LICENSE.txt for license information
@@ -2766,6 +2766,7 @@ void Parser::delayParseFromBeginningToHere(ParserPosition BeginParserPosition,
27662766
/// \endverbatim
27672767
ParserResult<Decl>
27682768
Parser::parseDecl(ParseDeclOptions Flags,
2769+
bool IsAtStartOfLineOrPreviousHadSemi,
27692770
llvm::function_ref<void(Decl*)> Handler) {
27702771
ParserPosition BeginParserPosition;
27712772
if (isCodeCompletionFirstPass())
@@ -2854,6 +2855,30 @@ Parser::parseDecl(ParseDeclOptions Flags,
28542855
auto OrigTok = Tok;
28552856
bool MayNeedOverrideCompletion = false;
28562857

2858+
auto parseLetOrVar = [&](bool HasLetOrVarKeyword) {
2859+
// Collect all modifiers into a modifier list.
2860+
DeclParsingContext.setCreateSyntax(SyntaxKind::VariableDecl);
2861+
llvm::SmallVector<Decl *, 4> Entries;
2862+
DeclResult = parseDeclVar(Flags, Attributes, Entries, StaticLoc,
2863+
StaticSpelling, tryLoc, HasLetOrVarKeyword);
2864+
StaticLoc = SourceLoc(); // we handled static if present.
2865+
MayNeedOverrideCompletion = true;
2866+
if (DeclResult.hasCodeCompletion() && isCodeCompletionFirstPass())
2867+
return;
2868+
std::for_each(Entries.begin(), Entries.end(), Handler);
2869+
if (auto *D = DeclResult.getPtrOrNull())
2870+
markWasHandled(D);
2871+
};
2872+
2873+
auto parseFunc = [&](bool HasFuncKeyword) {
2874+
// Collect all modifiers into a modifier list.
2875+
DeclParsingContext.setCreateSyntax(SyntaxKind::FunctionDecl);
2876+
DeclResult = parseDeclFunc(StaticLoc, StaticSpelling, Flags, Attributes,
2877+
HasFuncKeyword);
2878+
StaticLoc = SourceLoc(); // we handled static if present.
2879+
MayNeedOverrideCompletion = true;
2880+
};
2881+
28572882
switch (Tok.getKind()) {
28582883
case tok::kw_import:
28592884
DeclParsingContext.setCreateSyntax(SyntaxKind::ImportDecl);
@@ -2865,18 +2890,7 @@ Parser::parseDecl(ParseDeclOptions Flags,
28652890
break;
28662891
case tok::kw_let:
28672892
case tok::kw_var: {
2868-
// Collect all modifiers into a modifier list.
2869-
DeclParsingContext.setCreateSyntax(SyntaxKind::VariableDecl);
2870-
llvm::SmallVector<Decl *, 4> Entries;
2871-
DeclResult = parseDeclVar(Flags, Attributes, Entries, StaticLoc,
2872-
StaticSpelling, tryLoc);
2873-
StaticLoc = SourceLoc(); // we handled static if present.
2874-
MayNeedOverrideCompletion = true;
2875-
if (DeclResult.hasCodeCompletion() && isCodeCompletionFirstPass())
2876-
break;
2877-
std::for_each(Entries.begin(), Entries.end(), Handler);
2878-
if (auto *D = DeclResult.getPtrOrNull())
2879-
markWasHandled(D);
2893+
parseLetOrVar(/*HasLetOrVarKeyword=*/true);
28802894
break;
28812895
}
28822896
case tok::kw_typealias:
@@ -2932,11 +2946,7 @@ Parser::parseDecl(ParseDeclOptions Flags,
29322946
DeclResult = parseDeclProtocol(Flags, Attributes);
29332947
break;
29342948
case tok::kw_func:
2935-
// Collect all modifiers into a modifier list.
2936-
DeclParsingContext.setCreateSyntax(SyntaxKind::FunctionDecl);
2937-
DeclResult = parseDeclFunc(StaticLoc, StaticSpelling, Flags, Attributes);
2938-
StaticLoc = SourceLoc(); // we handled static if present.
2939-
MayNeedOverrideCompletion = true;
2949+
parseFunc(/*HasFuncKeyword=*/true);
29402950
break;
29412951
case tok::kw_subscript: {
29422952
DeclParsingContext.setCreateSyntax(SyntaxKind::SubscriptDecl);
@@ -2982,6 +2992,33 @@ Parser::parseDecl(ParseDeclOptions Flags,
29822992

29832993
// Obvious nonsense.
29842994
default:
2995+
2996+
if (Flags.contains(PD_HasContainerType) &&
2997+
IsAtStartOfLineOrPreviousHadSemi) {
2998+
2999+
// Emit diagnostics if we meet an identifier/operator where a declaration
3000+
// is expected, perhaps the user forgot the 'func' or 'var' keyword.
3001+
//
3002+
// Must not confuse it with trailing closure syntax, so we only
3003+
// recover in contexts where there can be no statements.
3004+
3005+
if ((Tok.isIdentifierOrUnderscore() &&
3006+
peekToken().isAny(tok::colon, tok::equal, tok::comma)) ||
3007+
Tok.is(tok::l_paren)) {
3008+
diagnose(Tok.getLoc(), diag::expected_keyword_in_decl, "var",
3009+
"property")
3010+
.fixItInsert(Tok.getLoc(), "var ");
3011+
parseLetOrVar(/*HasLetOrVarKeyword=*/false);
3012+
break;
3013+
} else if (Tok.isIdentifierOrUnderscore() || Tok.isAnyOperator()) {
3014+
diagnose(Tok.getLoc(), diag::expected_keyword_in_decl, "func",
3015+
"function")
3016+
.fixItInsert(Tok.getLoc(), "func ");
3017+
parseFunc(/*HasFuncKeyword=*/false);
3018+
break;
3019+
}
3020+
}
3021+
29853022
diagnose(Tok, diag::expected_decl);
29863023

29873024
if (CurDeclContext) {
@@ -3166,7 +3203,9 @@ void Parser::parseDeclDelayed() {
31663203
Scope S(this, DelayedState->takeScope());
31673204
ContextChange CC(*this, DelayedState->ParentContext);
31683205

3169-
parseDecl(ParseDeclOptions(DelayedState->Flags), [&](Decl *D) {
3206+
parseDecl(ParseDeclOptions(DelayedState->Flags),
3207+
/*IsAtStartOfLineOrPreviousHadSemi=*/true,
3208+
[&](Decl *D) {
31703209
if (auto *parent = DelayedState->ParentContext) {
31713210
if (auto *NTD = dyn_cast<NominalTypeDecl>(parent)) {
31723211
NTD->addMember(D);
@@ -3517,7 +3556,9 @@ ParserStatus Parser::parseDeclItem(bool &PreviousHadSemi,
35173556

35183557
// If the previous declaration didn't have a semicolon and this new
35193558
// declaration doesn't start a line, complain.
3520-
if (!PreviousHadSemi && !Tok.isAtStartOfLine() && !Tok.is(tok::unknown)) {
3559+
const bool IsAtStartOfLineOrPreviousHadSemi =
3560+
PreviousHadSemi || Tok.isAtStartOfLine() || Tok.is(tok::unknown);
3561+
if (!IsAtStartOfLineOrPreviousHadSemi) {
35213562
auto endOfPrevious = getEndOfPreviousLoc();
35223563
diagnose(endOfPrevious, diag::declaration_same_line_without_semi)
35233564
.fixItInsert(endOfPrevious, ";");
@@ -3536,7 +3577,7 @@ ParserStatus Parser::parseDeclItem(bool &PreviousHadSemi,
35363577
if (loadCurrentSyntaxNodeFromCache()) {
35373578
return ParserStatus();
35383579
}
3539-
Result = parseDecl(Options, handler);
3580+
Result = parseDecl(Options, IsAtStartOfLineOrPreviousHadSemi, handler);
35403581
if (Result.isParseError())
35413582
skipUntilDeclRBrace(tok::semi, tok::pound_endif);
35423583
SourceLoc SemiLoc;
@@ -5134,7 +5175,8 @@ Parser::parseDeclVar(ParseDeclOptions Flags,
51345175
SmallVectorImpl<Decl *> &Decls,
51355176
SourceLoc StaticLoc,
51365177
StaticSpellingKind StaticSpelling,
5137-
SourceLoc TryLoc) {
5178+
SourceLoc TryLoc,
5179+
bool HasLetOrVarKeyword) {
51385180
assert(StaticLoc.isInvalid() || StaticSpelling != StaticSpellingKind::None);
51395181

51405182
if (StaticLoc.isValid()) {
@@ -5152,9 +5194,11 @@ Parser::parseDeclVar(ParseDeclOptions Flags,
51525194
}
51535195
}
51545196

5155-
bool isLet = Tok.is(tok::kw_let);
5156-
assert(Tok.getKind() == tok::kw_let || Tok.getKind() == tok::kw_var);
5157-
SourceLoc VarLoc = consumeToken();
5197+
bool isLet = HasLetOrVarKeyword && Tok.is(tok::kw_let);
5198+
assert(!HasLetOrVarKeyword || Tok.getKind() == tok::kw_let ||
5199+
Tok.getKind() == tok::kw_var);
5200+
5201+
SourceLoc VarLoc = HasLetOrVarKeyword ? consumeToken() : Tok.getLoc();
51585202

51595203
// If this is a var in the top-level of script/repl source file, wrap the
51605204
// PatternBindingDecl in a TopLevelCodeDecl, since it represents executable
@@ -5464,9 +5508,11 @@ void Parser::consumeAbstractFunctionBody(AbstractFunctionDecl *AFD,
54645508
/// \endverbatim
54655509
///
54665510
/// \note The caller of this method must ensure that the next token is 'func'.
5467-
ParserResult<FuncDecl>
5468-
Parser::parseDeclFunc(SourceLoc StaticLoc, StaticSpellingKind StaticSpelling,
5469-
ParseDeclOptions Flags, DeclAttributes &Attributes) {
5511+
ParserResult<FuncDecl> Parser::parseDeclFunc(SourceLoc StaticLoc,
5512+
StaticSpellingKind StaticSpelling,
5513+
ParseDeclOptions Flags,
5514+
DeclAttributes &Attributes,
5515+
bool HasFuncKeyword) {
54705516
assert(StaticLoc.isInvalid() || StaticSpelling != StaticSpellingKind::None);
54715517

54725518
if (StaticLoc.isValid()) {
@@ -5488,7 +5534,8 @@ Parser::parseDeclFunc(SourceLoc StaticLoc, StaticSpellingKind StaticSpelling,
54885534
}
54895535
}
54905536

5491-
SourceLoc FuncLoc = consumeToken(tok::kw_func);
5537+
SourceLoc FuncLoc =
5538+
HasFuncKeyword ? consumeToken(tok::kw_func) : Tok.getLoc();
54925539

54935540
// Parse function name.
54945541
Identifier SimpleName;

lib/Parse/ParseStmt.cpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -356,7 +356,9 @@ ParserStatus Parser::parseBraceItems(SmallVectorImpl<ASTNode> &Entries,
356356

357357
// If the previous statement didn't have a semicolon and this new
358358
// statement doesn't start a line, complain.
359-
if (!PreviousHadSemi && !Tok.isAtStartOfLine()) {
359+
const bool IsAtStartOfLineOrPreviousHadSemi =
360+
PreviousHadSemi || Tok.isAtStartOfLine();
361+
if (!IsAtStartOfLineOrPreviousHadSemi) {
360362
SourceLoc EndOfPreviousLoc = getEndOfPreviousLoc();
361363
diagnose(EndOfPreviousLoc, diag::statement_same_line_without_semi)
362364
.fixItInsert(EndOfPreviousLoc, ";");
@@ -410,6 +412,7 @@ ParserStatus Parser::parseBraceItems(SmallVectorImpl<ASTNode> &Entries,
410412
SmallVector<Decl*, 8> TmpDecls;
411413
ParserResult<Decl> DeclResult =
412414
parseDecl(IsTopLevel ? PD_AllowTopLevel : PD_Default,
415+
IsAtStartOfLineOrPreviousHadSemi,
413416
[&](Decl *D) {TmpDecls.push_back(D);});
414417
BraceItemsStatus |= DeclResult;
415418
if (DeclResult.isParseError()) {

test/Parse/ConditionalCompilation/stmt_in_type.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@
44

55
func foo() {}
66

7-
struct S1 { // expected-note 2 {{in declaration of 'S1'}}
7+
struct S1 { // expected-note {{in declaration of 'S1'}}
88
#if FOO
99
return 1; // expected-error {{expected declaration}}
1010
#elseif BAR
11-
foo(); // expected-error {{expected declaration}}
11+
foo(); // expected-error {{expected 'func' keyword in function declaration}}
1212
#endif
1313
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// RUN: %target-typecheck-verify-swift
2+
3+
// https://bugs.swift.org/browse/SR-10477
4+
5+
protocol Brew { // expected-note {{in declaration of 'Brew'}}
6+
tripel() -> Int // expected-error {{expected 'func' keyword in function declaration}} {{3-3=func }}
7+
8+
quadrupel: Int { get } // expected-error {{expected 'var' keyword in property declaration}} {{3-3=var }}
9+
10+
static + (lhs: Self, rhs: Self) -> Self // expected-error {{expected 'func' keyword in function declaration}} {{10-10=func }}
11+
12+
* (lhs: Self, rhs: Self) -> Self // expected-error {{expected 'func' keyword in function declaration}} {{3-3=func }}
13+
// expected-error @-1 {{operator '*' declared in protocol must be 'static'}} {{3-3=static }}
14+
15+
ipa: Int { get }; apa: Float { get }
16+
// expected-error @-1 {{expected 'var' keyword in property declaration}} {{3-3=var }}
17+
// expected-error @-2 {{expected 'var' keyword in property declaration}} {{21-21=var }}
18+
19+
stout: Int { get } porter: Float { get }
20+
// expected-error @-1 {{expected 'var' keyword in property declaration}} {{3-3=var }}
21+
// expected-error @-2 {{expected declaration}}
22+
// expected-error @-3 {{consecutive declarations on a line must be separated by ';'}}
23+
}
24+
25+
infix operator %%
26+
27+
struct Bar {
28+
fisr = 0x5F3759DF // expected-error {{expected 'var' keyword in property declaration}} {{3-3=var }}
29+
30+
%%<T: Brew> (lhs: T, rhs: T) -> T { // expected-error {{expected 'func' keyword in function declaration}} {{3-3=func }}
31+
// expected-error @-1 {{operator '%%' declared in type 'Bar' must be 'static'}}
32+
// expected-error @-2 {{member operator '%%' must have at least one argument of type 'Bar'}}
33+
lhs + lhs + rhs + rhs
34+
}
35+
36+
_: Int = 42 // expected-error {{expected 'var' keyword in property declaration}} {{3-3=var }}
37+
// expected-error @-1 {{property declaration does not bind any variables}}
38+
39+
((x, y), z) = ((1, 2), 3) // expected-error {{expected 'var' keyword in property declaration}}
40+
41+
a, b: Int // expected-error {{expected 'var' keyword in property declaration}} {{3-3=var }}
42+
}

test/Parse/line-directive.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,13 @@ x x // expected-error{{consecutive statements}} {{2-2=;}}
2424

2525
// rdar://19582475
2626
public struct S { // expected-note{{in declaration of 'S'}}
27+
// expected-error@+8{{expected 'func' keyword in function declaration}}
28+
// expected-error@+7{{operator '/' declared in type 'S' must be 'static'}}
29+
// expected-error@+6{{expected '(' in argument list of function declaration}}
30+
// expected-error@+5{{operators must have one or two arguments}}
31+
// expected-error@+4{{member operator '/()' must have at least one argument of type 'S'}}
32+
// expected-error@+3{{expected '{' in body of function declaration}}
33+
// expected-error@+2{{consecutive declarations on a line must be separated by ';}}
2734
// expected-error@+1{{expected declaration}}
2835
/ ###line 25 "line-directive.swift"
2936
}

test/Parse/recovery.swift

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -540,19 +540,25 @@ func use_BracesInsideNominalDecl1() {
540540
var _ : BracesInsideNominalDecl1.A // no-error
541541
}
542542

543-
class SR771 { // expected-note {{in declaration of 'SR771'}}
544-
print("No one else was in the room where it happened") // expected-error {{expected declaration}}
543+
class SR771 {
544+
print("No one else was in the room where it happened") // expected-note {{'print()' previously declared here}}
545+
// expected-error @-1 {{expected 'func' keyword in function declaration}}
546+
// expected-error @-2 {{expected '{' in body of function declaration}}
547+
// expected-error @-3 {{expected parameter name followed by ':'}}
545548
}
546549

547-
extension SR771 { // expected-note {{in extension of 'SR771'}}
548-
print("The room where it happened, the room where it happened") // expected-error {{expected declaration}}
550+
extension SR771 {
551+
print("The room where it happened, the room where it happened")
552+
// expected-error @-1 {{expected 'func' keyword in function declaration}}
553+
// expected-error @-2 {{invalid redeclaration of 'print()'}}
554+
// expected-error @-3 {{expected parameter name followed by ':'}}
549555
}
550556

551557

552558
//===--- Recovery for wrong decl introducer keyword.
553559

554-
class WrongDeclIntroducerKeyword1 { // expected-note{{in declaration of 'WrongDeclIntroducerKeyword1'}}
555-
notAKeyword() {} // expected-error {{expected declaration}}
560+
class WrongDeclIntroducerKeyword1 {
561+
notAKeyword() {} // expected-error {{expected 'func' keyword in function declaration}}
556562
func foo() {}
557563
class func bar() {}
558564
}

test/Sema/immutability.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -508,8 +508,10 @@ struct StructWithDelegatingInit {
508508
}
509509

510510
// <rdar://problem/16792027> compiler infinite loops on a really really mutating function
511-
struct F { // expected-note 1 {{in declaration of 'F'}}
512-
mutating mutating mutating f() { // expected-error 2 {{duplicate modifier}} expected-note 2 {{modifier already specified here}} expected-error {{expected declaration}}
511+
struct F {
512+
mutating mutating mutating f() { // expected-error 2 {{duplicate modifier}}
513+
// expected-note@-1 2 {{modifier already specified here}}
514+
// expected-error@-2 {{expected 'func' keyword in function declaration}}
513515
}
514516

515517
mutating nonmutating func g() {} // expected-error {{method must not be declared both 'mutating' and 'nonmutating'}}

0 commit comments

Comments
 (0)