Skip to content

Commit ab415e6

Browse files
robinkundejrose-apple
authored andcommitted
[Parse] Improve parser diagnostics for keyword-as-identifer errors (#6045)
Instead of the simple "expected identifier in declaration", the error will now read "keyword '%' cannot be used as an identifier here", and will be accompanied by a note suggesting escaping the keyword with backticks, as well as a fixit. https://bugs.swift.org/browse/SR-3167
1 parent ca0fa31 commit ab415e6

File tree

7 files changed

+37
-10
lines changed

7 files changed

+37
-10
lines changed

lib/Parse/ParseDecl.cpp

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2520,8 +2520,15 @@ static ParserStatus parseIdentifierDeclName(Parser &P, Identifier &Result,
25202520

25212521
default:
25222522
P.checkForInputIncomplete();
2523-
if (!D.is(diag::invalid_diagnostic))
2524-
P.diagnose(P.Tok, D);
2523+
if (!D.is(diag::invalid_diagnostic)) {
2524+
if (P.Tok.isKeyword()) {
2525+
P.diagnose(P.Tok, diag::keyword_cant_be_identifier, P.Tok.getText());
2526+
P.diagnose(P.Tok, diag::backticks_to_escape)
2527+
.fixItReplace(P.Tok.getLoc(), "`" + P.Tok.getText().str() + "`");
2528+
} else {
2529+
P.diagnose(P.Tok, D);
2530+
}
2531+
}
25252532
if (P.Tok.isKeyword() &&
25262533
(P.peekToken().is(ResyncT1) || P.peekToken().is(ResyncT2) ||
25272534
P.peekToken().is(ResyncT3) || P.peekToken().is(ResyncT4) ||
@@ -4797,6 +4804,9 @@ ParserStatus Parser::parseDeclEnumCase(ParseDeclOptions Flags,
47974804
consumeIf(tok::period_prefix, DotLoc);
47984805

47994806
const bool NameIsNotIdentifier = Tok.isNot(tok::identifier);
4807+
const bool NameIsKeyword = Tok.isKeyword();
4808+
StringRef TokText = Tok.getText();
4809+
SourceLoc TokLoc = Tok.getLoc();
48004810
if (parseIdentifierDeclName(*this, Name, NameLoc, tok::l_paren,
48014811
tok::kw_case, tok::colon, tok::r_brace,
48024812
diag::invalid_diagnostic).isError()) {
@@ -4826,7 +4836,13 @@ ParserStatus Parser::parseDeclEnumCase(ParseDeclOptions Flags,
48264836
Status.setIsParseError();
48274837
return Status;
48284838
}
4829-
diagnose(CaseLoc, diag::expected_identifier_in_decl, "enum 'case'");
4839+
if (NameIsKeyword) {
4840+
diagnose(TokLoc, diag::keyword_cant_be_identifier, TokText);
4841+
diagnose(TokLoc, diag::backticks_to_escape)
4842+
.fixItReplace(TokLoc, "`" + TokText.str() + "`");
4843+
} else {
4844+
diagnose(CaseLoc, diag::expected_identifier_in_decl, "enum 'case'");
4845+
}
48304846
} else if (DotLoc.isValid()) {
48314847
diagnose(DotLoc, diag::enum_case_dot_prefix)
48324848
.fixItRemove(DotLoc);

test/Parse/enum.swift

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ enum Recovery3 {
113113
case UE2(): // expected-error {{'case' label can only appear inside a 'switch' statement}}
114114
}
115115
enum Recovery4 { // expected-note {{in declaration of 'Recovery4'}}
116-
case Self Self // expected-error {{expected identifier in enum 'case' declaration}} expected-error {{consecutive declarations on a line must be separated by ';'}} {{12-12=;}} expected-error {{expected declaration}}
116+
case Self Self // expected-error {{keyword 'Self' cannot be used as an identifier here}} expected-note {{if this name is unavoidable, use backticks to escape it}} {{8-12=`Self`}} expected-error {{consecutive declarations on a line must be separated by ';'}} {{12-12=;}} expected-error {{expected declaration}}
117117
}
118118
enum Recovery5 {
119119
case .UE3 // expected-error {{extraneous '.' in enum 'case' declaration}} {{8-9=}}
@@ -123,7 +123,7 @@ enum Recovery5 {
123123
}
124124
enum Recovery6 {
125125
case Snout, _; // expected-error {{expected identifier after comma in enum 'case' declaration}}
126-
case _; // expected-error {{expected identifier in enum 'case' declaration}}
126+
case _; // expected-error {{keyword '_' cannot be used as an identifier here}} expected-note {{if this name is unavoidable, use backticks to escape it}} {{8-9=`_`}}
127127
case Tusk, // expected-error {{expected pattern}}
128128
} // expected-error {{expected identifier after comma in enum 'case' declaration}}
129129

@@ -535,3 +535,4 @@ enum SE0036_Generic<T> {
535535
}
536536
}
537537

538+
enum switch {} // expected-error {{keyword 'switch' cannot be used as an identifier here}} expected-note {{if this name is unavoidable, use backticks to escape it}} {{6-12=`switch`}}

test/Parse/escaped_identifiers.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,7 @@ var applyGet: Int {
1919
`get` { }
2020
return 0
2121
}
22+
23+
enum `switch` {}
24+
25+
typealias `Self` = Int

test/Parse/identifiers.swift

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,12 @@ func s̈pin̈al_tap̈() {}
2727

2828
// Placeholders are recognized as identifiers but with error.
2929
func <#some name#>() {} // expected-error 2 {{editor placeholder in source file}}
30+
31+
// Keywords as identifiers
32+
class switch {} // expected-error {{keyword 'switch' cannot be used as an identifier here}} expected-note {{if this name is unavoidable, use backticks to escape it}} {{7-13=`switch`}}
33+
struct Self {} // expected-error {{keyword 'Self' cannot be used as an identifier here}} expected-note {{if this name is unavoidable, use backticks to escape it}} {{8-12=`Self`}}
34+
protocol enum {} // expected-error {{keyword 'enum' cannot be used as an identifier here}} expected-note {{if this name is unavoidable, use backticks to escape it}} {{10-14=`enum`}}
35+
protocol test { // expected-note{{in declaration of 'test'}}
36+
associatedtype public // expected-error {{keyword 'public' cannot be used as an identifier here}} expected-note {{if this name is unavoidable, use backticks to escape it}} {{18-24=`public`}} expected-error {{consecutive declarations on a line must be separated by ';'}}
37+
} // expected-error{{expected declaration}}
38+
func _(_ x: Int) {} // expected-error {{keyword '_' cannot be used as an identifier here}} // expected-note {{if this name is unavoidable, use backticks to escape it}} {{6-7=`_`}}

test/Parse/typealias.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,4 @@ typealias Recovery5 : Int, Float // expected-error {{expected '=' in typealias d
3131

3232
typealias Recovery6 = = // expected-error {{expected type in typealias declaration}}
3333

34+
typealias switch = Int // expected-error {{keyword 'switch' cannot be used as an identifier here}} expected-note {{if this name is unavoidable, use backticks to escape it}} {{11-17=`switch`}}

test/decl/var/variables.swift

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,6 @@ var bfx : Int, bfy : Int
1414

1515
_ = 10
1616

17-
func _(_ x: Int) {} // expected-error {{keyword '_' cannot be used as an identifier here}}
18-
// expected-note @-1 {{if this name is unavoidable, use backticks to escape it}}
19-
20-
2117
var self1 = self1 // expected-error {{variable used within its own initial value}}
2218
var self2 : Int = self2 // expected-error {{variable used within its own initial value}}
2319
var (self3) : Int = self3 // expected-error {{variable used within its own initial value}}

test/type/protocol_types.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ func testHasMoreAssoc(_ x: Any) {
9090
}
9191

9292
struct Outer {
93-
typealias Any = Int // expected-error {{expected identifier in typealias declaration}}
93+
typealias Any = Int // expected-error {{keyword 'Any' cannot be used as an identifier here}} expected-note {{if this name is unavoidable, use backticks to escape it}} {{13-16=`Any`}}
9494
typealias `Any` = Int
9595
static func aa(a: `Any`) -> Int { return a }
9696
}

0 commit comments

Comments
 (0)