Skip to content

Commit a67f4bd

Browse files
authored
Merge pull request #6948 from matthewcarroll/SR-3599-Better-Recovery-For-Decl-Names
[DiagnosticsQoI] SR-3599: Better recovery for decls with consecutive identifiers
2 parents dec7f9f + 6285579 commit a67f4bd

File tree

6 files changed

+149
-28
lines changed

6 files changed

+149
-28
lines changed

include/swift/Parse/Parser.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -463,6 +463,9 @@ class Parser {
463463

464464
/// Parse an #endif.
465465
bool parseEndIfDirective(SourceLoc &Loc);
466+
467+
/// True if Tok is the second consecutive identifier in a variable decl.
468+
bool isSecondVarIdentifier();
466469

467470
public:
468471
InFlightDiagnostic diagnose(SourceLoc Loc, Diagnostic Diag) {
@@ -490,6 +493,10 @@ class Parser {
490493
}
491494

492495
void diagnoseRedefinition(ValueDecl *Prev, ValueDecl *New);
496+
497+
/// Add a fix-it to remove the space in consecutive identifiers.
498+
/// Add a camel-cased option if it is different than the first option.
499+
void diagnoseConsecutiveIDs(Token First, Token Second);
493500

494501
/// \brief Check whether the current token starts with '<'.
495502
bool startsWithLess(Token Tok) {

lib/Parse/ParseDecl.cpp

Lines changed: 55 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2705,6 +2705,22 @@ parseIdentifierDeclName(Parser &P, Identifier &Result, SourceLoc &L,
27052705
ResyncP1, Diagnostic(ID, Args...));
27062706
}
27072707

2708+
/// Add a fix-it to remove the space in consecutive identifiers.
2709+
/// Add a camel-cased option if it is different than the first option.
2710+
void Parser::diagnoseConsecutiveIDs(Token First, Token Second) {
2711+
auto Joined = (First.getText() + Second.getText()).str();
2712+
SourceRange Range(First.getLoc(), Second.getLoc());
2713+
diagnose(Second.getLoc(), diag::join_identifiers).fixItReplace(Range, Joined);
2714+
2715+
SmallString<8> Scratch;
2716+
auto SentenceCased = camel_case::toSentencecase(Tok.getText(), Scratch);
2717+
auto CamelCased = (First.getText() + SentenceCased).str();
2718+
if (Joined != CamelCased) {
2719+
diagnose(Second.getLoc(), diag::join_identifiers_camel_case)
2720+
.fixItReplace(Range, CamelCased);
2721+
}
2722+
}
2723+
27082724
/// Parse a Decl item in decl list.
27092725
static ParserStatus parseDeclItem(Parser &P,
27102726
bool &PreviousHadSemi,
@@ -2722,9 +2738,20 @@ static ParserStatus parseDeclItem(Parser &P,
27222738
// If the previous declaration didn't have a semicolon and this new
27232739
// declaration doesn't start a line, complain.
27242740
if (!PreviousHadSemi && !P.Tok.isAtStartOfLine() && !P.Tok.is(tok::unknown)) {
2725-
auto endOfPrevious = P.getEndOfPreviousLoc();
2726-
P.diagnose(endOfPrevious, diag::declaration_same_line_without_semi)
2727-
.fixItInsert(endOfPrevious, ";");
2741+
// Add a fix-it to remove the space in consecutive identifiers
2742+
// in a property or enum case decl.
2743+
auto PreviousTok = P.L->getTokenAt(P.PreviousLoc);
2744+
if (P.Tok.is(tok::identifier) && PreviousTok.is(tok::identifier)) {
2745+
auto enumContext = P.CurDeclContext->getAsEnumOrEnumExtensionContext();
2746+
auto TypeName = enumContext ? "enum case" : "property";
2747+
P.diagnose(P.Tok.getLoc(), diag::repeated_identifier, TypeName);
2748+
P.diagnoseConsecutiveIDs(PreviousTok, P.Tok);
2749+
P.skipUntilDeclRBrace(tok::semi, tok::pound_endif);
2750+
} else {
2751+
auto endOfPrevious = P.getEndOfPreviousLoc();
2752+
P.diagnose(endOfPrevious, diag::declaration_same_line_without_semi)
2753+
.fixItInsert(endOfPrevious, ";");
2754+
}
27282755
}
27292756

27302757
auto Result = P.parseDecl(Options, handler);
@@ -4661,26 +4688,7 @@ Parser::parseDeclFunc(SourceLoc StaticLoc, StaticSpellingKind StaticSpelling,
46614688
// space or newline accidentally.
46624689
if (Tok.isIdentifierOrUnderscore() && SimpleName.str().back() != '<') {
46634690
diagnose(Tok.getLoc(), diag::repeated_identifier, "function");
4664-
4665-
SourceRange DoubleIdentifierRange(NameLoc, Tok.getLoc());
4666-
4667-
// Provide two fix-its: a direct concatenation of the two identifiers
4668-
// and a camel-cased version.
4669-
4670-
auto DirectConcatenation = NameTok.getText().str() + Tok.getText().str();
4671-
4672-
diagnose(Tok.getLoc(), diag::join_identifiers)
4673-
.fixItReplace(DoubleIdentifierRange, DirectConcatenation);
4674-
4675-
SmallString<8> CapitalizedScratch;
4676-
auto Capitalized = camel_case::toSentencecase(Tok.getText(),
4677-
CapitalizedScratch);
4678-
auto CamelCaseConcatenation = NameTok.getText().str() + Capitalized.str();
4679-
4680-
if (DirectConcatenation != CamelCaseConcatenation)
4681-
diagnose(Tok.getLoc(), diag::join_identifiers_camel_case)
4682-
.fixItReplace(DoubleIdentifierRange, CamelCaseConcatenation);
4683-
4691+
diagnoseConsecutiveIDs(NameTok, Tok);
46844692
consumeToken();
46854693
}
46864694
}
@@ -4941,6 +4949,14 @@ ParserResult<EnumDecl> Parser::parseDeclEnum(ParseDeclOptions Flags,
49414949
ED->setGenericParams(GenericParams);
49424950
}
49434951

4952+
// Add a fix-it to remove the space in consecutive identifiers
4953+
// in an enum decl.
4954+
if (Tok.is(tok::identifier)) {
4955+
diagnose(Tok.getLoc(), diag::repeated_identifier, "enum");
4956+
diagnoseConsecutiveIDs(L->getTokenAt(EnumNameLoc), Tok);
4957+
consumeToken();
4958+
}
4959+
49444960
SourceLoc LBLoc, RBLoc;
49454961
if (parseToken(tok::l_brace, LBLoc, diag::expected_lbrace_enum)) {
49464962
LBLoc = PreviousLoc;
@@ -5196,6 +5212,14 @@ ParserResult<StructDecl> Parser::parseDeclStruct(ParseDeclOptions Flags,
51965212
SD->setGenericParams(GenericParams);
51975213
}
51985214

5215+
// Add a fix-it to remove the space in consecutive identifiers
5216+
// in a struct decl.
5217+
if (Tok.is(tok::identifier)) {
5218+
diagnose(Tok.getLoc(), diag::repeated_identifier, "struct");
5219+
diagnoseConsecutiveIDs(L->getTokenAt(StructNameLoc), Tok);
5220+
consumeToken();
5221+
}
5222+
51995223
SourceLoc LBLoc, RBLoc;
52005224
if (parseToken(tok::l_brace, LBLoc, diag::expected_lbrace_struct)) {
52015225
LBLoc = PreviousLoc;
@@ -5279,6 +5303,14 @@ ParserResult<ClassDecl> Parser::parseDeclClass(SourceLoc ClassLoc,
52795303
CD->setGenericParams(GenericParams);
52805304
}
52815305

5306+
// Add a fix-it to remove the space in consecutive identifiers
5307+
// in a class decl.
5308+
if (Tok.is(tok::identifier)) {
5309+
diagnose(Tok.getLoc(), diag::repeated_identifier, "class");
5310+
diagnoseConsecutiveIDs(L->getTokenAt(ClassNameLoc), Tok);
5311+
consumeToken();
5312+
}
5313+
52825314
SourceLoc LBLoc, RBLoc;
52835315
if (parseToken(tok::l_brace, LBLoc, diag::expected_lbrace_class)) {
52845316
LBLoc = PreviousLoc;

lib/Parse/ParseStmt.cpp

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -283,10 +283,19 @@ ParserStatus Parser::parseBraceItems(SmallVectorImpl<ASTNode> &Entries,
283283
// If the previous statement didn't have a semicolon and this new
284284
// statement doesn't start a line, complain.
285285
if (!PreviousHadSemi && !Tok.isAtStartOfLine()) {
286-
SourceLoc EndOfPreviousLoc = getEndOfPreviousLoc();
287-
diagnose(EndOfPreviousLoc, diag::statement_same_line_without_semi)
288-
.fixItInsert(EndOfPreviousLoc, ";");
289-
// FIXME: Add semicolon to the AST?
286+
// Add a fix-it to remove the space in consecutive identifiers
287+
// in a variable decl.
288+
if (isSecondVarIdentifier()) {
289+
diagnose(Tok.getLoc(), diag::repeated_identifier, "variable");
290+
auto Previous = L->getTokenAt(PreviousLoc);
291+
diagnoseConsecutiveIDs(Previous, Tok);
292+
skipUntilDeclRBrace(tok::semi, tok::pound_endif);
293+
} else {
294+
SourceLoc EndOfPreviousLoc = getEndOfPreviousLoc();
295+
diagnose(EndOfPreviousLoc, diag::statement_same_line_without_semi)
296+
.fixItInsert(EndOfPreviousLoc, ";");
297+
// FIXME: Add semicolon to the AST?
298+
}
290299
}
291300

292301
ParserPosition BeginParserPosition;

lib/Parse/Parser.cpp

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -737,6 +737,27 @@ void Parser::diagnoseRedefinition(ValueDecl *Prev, ValueDecl *New) {
737737
Prev->getName());
738738
}
739739

740+
/// True if Tok is the second consecutive identifier in a variable decl.
741+
bool Parser::isSecondVarIdentifier() {
742+
auto PreviousTok = L->getTokenAt(PreviousLoc);
743+
if (Tok.isNot(tok::identifier) || PreviousTok.isNot(tok::identifier)) {
744+
return false;
745+
}
746+
auto LineStart = L->getLocForStartOfLine(SourceMgr, Tok.getLoc());
747+
auto FirstTok = L->getTokenAt(LineStart);
748+
Lexer::State FirstTokState = L->getStateForBeginningOfToken(FirstTok);
749+
auto StartPos = ParserPosition(FirstTokState, LineStart);
750+
auto RestorePos = getParserPosition();
751+
backtrackToPosition(StartPos);
752+
while (Tok.isNot(tok::kw_let) && Tok.isNot(tok::kw_var)
753+
&& Tok.getLoc() != PreviousTok.getLoc()) {
754+
consumeToken();
755+
}
756+
bool IsConsecutiveLoc = peekToken().getLoc() == PreviousTok.getLoc();
757+
restoreParserPosition(RestorePos);
758+
return IsConsecutiveLoc;
759+
}
760+
740761
struct ParserUnit::Implementation {
741762
LangOptions LangOpts;
742763
SearchPathOptions SearchPathOpts;

test/Parse/recovery.swift

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,58 @@ class NoBracesClass2 // expected-error {{expected '{' in class}}
242242
protocol NoBracesProtocol2 // expected-error {{expected '{' in protocol type}}
243243
extension NoBracesStruct2 // expected-error {{expected '{' in extension}}
244244

245+
//===--- Recovery for multiple identifiers in decls
246+
247+
enum EE EE {}
248+
// expected-error @-1 {{found an unexpected second identifier in enum declaration; is there an accidental break?}}
249+
// expected-note @-2 {{join the identifiers together}} {{6-11=EEEE}}
250+
251+
struct SS SS {}
252+
// expected-error @-1 {{found an unexpected second identifier in struct declaration; is there an accidental break?}}
253+
// expected-note @-2 {{join the identifiers together}} {{8-13=SSSS}}
254+
255+
class CC CC {}
256+
// expected-error @-1 {{found an unexpected second identifier in class declaration; is there an accidental break?}}
257+
// expected-note @-2 {{join the identifiers together}} {{7-12=CCCC}}
258+
// expected-note @-3 {{did you mean 'CC'}}
259+
260+
enum EEE {
261+
262+
case a a
263+
// expected-error @-1 {{found an unexpected second identifier in enum case declaration; is there an accidental break?}}
264+
// expected-note @-2 {{join the identifiers together}} {{8-11=aa}}
265+
// expected-note @-3 {{join the identifiers together with camel-case}} {{8-11=aA}}
266+
267+
case b
268+
}
269+
270+
struct AA {
271+
272+
private var a b = 0
273+
// expected-error @-1 {{found an unexpected second identifier in property declaration; is there an accidental break?}}
274+
// expected-note @-2 {{join the identifiers together}} {{15-18=ab}}
275+
// expected-note @-3 {{join the identifiers together with camel-case}} {{15-18=aB}}
276+
// expected-error @-4 {{type annotation missing in pattern}}
277+
278+
func f() {
279+
var c d = 5
280+
// expected-error @-1 {{found an unexpected second identifier in variable declaration; is there an accidental break?}}
281+
// expected-note @-2 {{join the identifiers together}} {{9-12=cd}}
282+
// expected-note @-3 {{join the identifiers together with camel-case}} {{9-12=cD}}
283+
// expected-error @-4 {{type annotation missing in pattern}}
284+
285+
let _ = 0
286+
}
287+
}
288+
289+
let e f = 5
290+
// expected-error @-1 {{found an unexpected second identifier in variable declaration; is there an accidental break?}}
291+
// expected-note @-2 {{join the identifiers together}} {{5-8=ef}}
292+
// expected-note @-3 {{join the identifiers together with camel-case}} {{5-8=eF}}
293+
// expected-error @-4 {{type annotation missing in pattern}}
294+
// expected-note @-5 * {{did you mean 'e'?}}
295+
296+
245297
//===--- Recovery for parse errors in types.
246298

247299
struct ErrorTypeInVarDecl1 {

validation-test/compiler_crashers/28619-basety-islvaluetype-basety-is-anymetatypetype.swift renamed to validation-test/compiler_crashers_fixed/28619-basety-islvaluetype-basety-is-anymetatypetype.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
// See https://swift.org/LICENSE.txt for license information
66
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
77

8-
// RUN: not --crash %target-swift-frontend %s -emit-ir
8+
// RUN: not %target-swift-frontend %s -emit-ir
99
// REQUIRES: asserts
1010
protocol a:RangeReplaceableCollection
1111
class a

0 commit comments

Comments
 (0)