[Parser][ObjC++] Improve diagnostics and recovery when C++ keywords are used
as identifiers in Objective-C++
This commit improves the 'expected identifier' errors that are presented when a
C++ keyword is used as an identifier in Objective-C++ by mentioning that this is
a C++ keyword in the diagnostic message. It also improves the error recovery:
the parser will now treat the C++ keywords as identifiers to prevent unrelated
parsing errors.
rdar://20626062
Differential Revision: https://reviews.llvm.org/D26503
git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@299950 91177308-0d34-0410-b5e6-96231b3b80d8
(cherry picked from commit b257ac069f4eef39612addf09d671911f3174787)
diff --git a/include/clang/Basic/DiagnosticParseKinds.td b/include/clang/Basic/DiagnosticParseKinds.td
index d6ebdb1..9efb1e3 100644
--- a/include/clang/Basic/DiagnosticParseKinds.td
+++ b/include/clang/Basic/DiagnosticParseKinds.td
@@ -433,6 +433,13 @@
"declaration does not declare a parameter">;
def err_no_matching_param : Error<"parameter named %0 is missing">;
+/// Objective-C++ parser diagnostics
+def err_expected_token_instead_of_objcxx_keyword : Error<
+ "expected %0; %1 is a keyword in Objective-C++">;
+def err_expected_member_name_or_semi_objcxx_keyword : Error<
+ "expected member name or ';' after declaration specifiers; "
+ "%0 is a keyword in Objective-C++">;
+
/// C++ parser diagnostics
def err_invalid_operator_on_type : Error<
"cannot use %select{dot|arrow}0 operator on a type">;
diff --git a/include/clang/Basic/IdentifierTable.h b/include/clang/Basic/IdentifierTable.h
index 52238f8..7d10f14 100644
--- a/include/clang/Basic/IdentifierTable.h
+++ b/include/clang/Basic/IdentifierTable.h
@@ -277,7 +277,11 @@
bool isCPlusPlusOperatorKeyword() const { return IsCPPOperatorKeyword; }
/// \brief Return true if this token is a keyword in the specified language.
- bool isKeyword(const LangOptions &LangOpts);
+ bool isKeyword(const LangOptions &LangOpts) const;
+
+ /// \brief Return true if this token is a C++ keyword in the specified
+ /// language.
+ bool isCPlusPlusKeyword(const LangOptions &LangOpts) const;
/// getFETokenInfo/setFETokenInfo - The language front-end is allowed to
/// associate arbitrary metadata with this token.
diff --git a/include/clang/Parse/Parser.h b/include/clang/Parse/Parser.h
index a3c7b7b..0f5cd45 100644
--- a/include/clang/Parse/Parser.h
+++ b/include/clang/Parse/Parser.h
@@ -800,6 +800,14 @@
/// \brief Consume any extra semi-colons until the end of the line.
void ConsumeExtraSemi(ExtraSemiKind Kind, unsigned TST = TST_unspecified);
+ /// Return false if the next token is an identifier. An 'expected identifier'
+ /// error is emitted otherwise.
+ ///
+ /// The parser tries to recover from the error by checking if the next token
+ /// is a C++ keyword when parsing Objective-C++. Return false if the recovery
+ /// was successful.
+ bool expectIdentifier();
+
public:
//===--------------------------------------------------------------------===//
// Scope manipulation
diff --git a/lib/Basic/IdentifierTable.cpp b/lib/Basic/IdentifierTable.cpp
index 8c67f4e..6f67081 100644
--- a/lib/Basic/IdentifierTable.cpp
+++ b/lib/Basic/IdentifierTable.cpp
@@ -243,7 +243,7 @@
/// \brief Returns true if the identifier represents a keyword in the
/// specified language.
-bool IdentifierInfo::isKeyword(const LangOptions &LangOpts) {
+bool IdentifierInfo::isKeyword(const LangOptions &LangOpts) const {
switch (getTokenKwStatus(LangOpts, getTokenID())) {
case KS_Enabled:
case KS_Extension:
@@ -253,6 +253,19 @@
}
}
+/// \brief Returns true if the identifier represents a C++ keyword in the
+/// specified language.
+bool IdentifierInfo::isCPlusPlusKeyword(const LangOptions &LangOpts) const {
+ if (!LangOpts.CPlusPlus || !isKeyword(LangOpts))
+ return false;
+ // This is a C++ keyword if this identifier is not a keyword when checked
+ // using LangOptions without C++ support.
+ LangOptions LangOptsNoCPP = LangOpts;
+ LangOptsNoCPP.CPlusPlus = false;
+ LangOptsNoCPP.CPlusPlus11 = false;
+ return !isKeyword(LangOptsNoCPP);
+}
+
tok::PPKeywordKind IdentifierInfo::getPPKeywordID() const {
// We use a perfect hash function here involving the length of the keyword,
// the first and third character. For preprocessor ID's there are no
diff --git a/lib/Parse/ParseDecl.cpp b/lib/Parse/ParseDecl.cpp
index 423225f..b986026 100644
--- a/lib/Parse/ParseDecl.cpp
+++ b/lib/Parse/ParseDecl.cpp
@@ -5600,6 +5600,21 @@
if (Tok.is(tok::l_square))
return ParseMisplacedBracketDeclarator(D);
if (D.getContext() == Declarator::MemberContext) {
+ // Objective-C++: Detect C++ keywords and try to prevent further errors by
+ // treating these keyword as valid member names.
+ if (getLangOpts().ObjC1 && getLangOpts().CPlusPlus &&
+ Tok.getIdentifierInfo() &&
+ Tok.getIdentifierInfo()->isCPlusPlusKeyword(getLangOpts())) {
+ Diag(getMissingDeclaratorIdLoc(D, Tok.getLocation()),
+ diag::err_expected_member_name_or_semi_objcxx_keyword)
+ << Tok.getIdentifierInfo()
+ << (D.getDeclSpec().isEmpty() ? SourceRange()
+ : D.getDeclSpec().getSourceRange());
+ D.SetIdentifier(Tok.getIdentifierInfo(), Tok.getLocation());
+ D.SetRangeEnd(Tok.getLocation());
+ ConsumeToken();
+ goto PastIdentifier;
+ }
Diag(getMissingDeclaratorIdLoc(D, Tok.getLocation()),
diag::err_expected_member_name_or_semi)
<< (D.getDeclSpec().isEmpty() ? SourceRange()
diff --git a/lib/Parse/ParseObjc.cpp b/lib/Parse/ParseObjc.cpp
index fb4b945..99cc00f7 100644
--- a/lib/Parse/ParseObjc.cpp
+++ b/lib/Parse/ParseObjc.cpp
@@ -137,8 +137,7 @@
while (1) {
MaybeSkipAttributes(tok::objc_class);
- if (Tok.isNot(tok::identifier)) {
- Diag(Tok, diag::err_expected) << tok::identifier;
+ if (expectIdentifier()) {
SkipUntil(tok::semi);
return Actions.ConvertDeclToDeclGroup(nullptr);
}
@@ -231,11 +230,8 @@
MaybeSkipAttributes(tok::objc_interface);
- if (Tok.isNot(tok::identifier)) {
- Diag(Tok, diag::err_expected)
- << tok::identifier; // missing class or category name.
- return nullptr;
- }
+ if (expectIdentifier())
+ return nullptr; // missing class or category name.
// We have a class or category name - consume it.
IdentifierInfo *nameId = Tok.getIdentifierInfo();
@@ -321,11 +317,8 @@
return nullptr;
}
- if (Tok.isNot(tok::identifier)) {
- Diag(Tok, diag::err_expected)
- << tok::identifier; // missing super class name.
- return nullptr;
- }
+ if (expectIdentifier())
+ return nullptr; // missing super class name.
superClassId = Tok.getIdentifierInfo();
superClassLoc = ConsumeToken();
@@ -1440,12 +1433,9 @@
cutOffParsing();
return nullptr;
}
-
- if (Tok.isNot(tok::identifier)) {
- Diag(Tok, diag::err_expected)
- << tok::identifier; // missing argument name.
- break;
- }
+
+ if (expectIdentifier())
+ break; // missing argument name.
ArgInfo.Name = Tok.getIdentifierInfo();
ArgInfo.NameLoc = Tok.getLocation();
@@ -1554,8 +1544,7 @@
return true;
}
- if (Tok.isNot(tok::identifier)) {
- Diag(Tok, diag::err_expected) << tok::identifier;
+ if (expectIdentifier()) {
SkipUntil(tok::greater, StopAtSemi);
return true;
}
@@ -2037,10 +2026,8 @@
MaybeSkipAttributes(tok::objc_protocol);
- if (Tok.isNot(tok::identifier)) {
- Diag(Tok, diag::err_expected) << tok::identifier; // missing protocol name.
- return nullptr;
- }
+ if (expectIdentifier())
+ return nullptr; // missing protocol name.
// Save the protocol name, then consume it.
IdentifierInfo *protocolName = Tok.getIdentifierInfo();
SourceLocation nameLoc = ConsumeToken();
@@ -2060,8 +2047,7 @@
// Parse the list of forward declarations.
while (1) {
ConsumeToken(); // the ','
- if (Tok.isNot(tok::identifier)) {
- Diag(Tok, diag::err_expected) << tok::identifier;
+ if (expectIdentifier()) {
SkipUntil(tok::semi);
return nullptr;
}
@@ -2128,11 +2114,8 @@
MaybeSkipAttributes(tok::objc_implementation);
- if (Tok.isNot(tok::identifier)) {
- Diag(Tok, diag::err_expected)
- << tok::identifier; // missing class or category name.
- return nullptr;
- }
+ if (expectIdentifier())
+ return nullptr; // missing class or category name.
// We have a class or category name - consume it.
IdentifierInfo *nameId = Tok.getIdentifierInfo();
SourceLocation nameLoc = ConsumeToken(); // consume class or category name
@@ -2202,11 +2185,8 @@
IdentifierInfo *superClassId = nullptr;
if (TryConsumeToken(tok::colon)) {
// We have a super class
- if (Tok.isNot(tok::identifier)) {
- Diag(Tok, diag::err_expected)
- << tok::identifier; // missing super class name.
- return nullptr;
- }
+ if (expectIdentifier())
+ return nullptr; // missing super class name.
superClassId = Tok.getIdentifierInfo();
superClassLoc = ConsumeToken(); // Consume super class name
}
@@ -2306,16 +2286,12 @@
assert(Tok.isObjCAtKeyword(tok::objc_compatibility_alias) &&
"ParseObjCAtAliasDeclaration(): Expected @compatibility_alias");
ConsumeToken(); // consume compatibility_alias
- if (Tok.isNot(tok::identifier)) {
- Diag(Tok, diag::err_expected) << tok::identifier;
+ if (expectIdentifier())
return nullptr;
- }
IdentifierInfo *aliasId = Tok.getIdentifierInfo();
SourceLocation aliasLoc = ConsumeToken(); // consume alias-name
- if (Tok.isNot(tok::identifier)) {
- Diag(Tok, diag::err_expected) << tok::identifier;
+ if (expectIdentifier())
return nullptr;
- }
IdentifierInfo *classId = Tok.getIdentifierInfo();
SourceLocation classLoc = ConsumeToken(); // consume class-name;
ExpectAndConsume(tok::semi, diag::err_expected_after, "@compatibility_alias");
@@ -2363,11 +2339,9 @@
cutOffParsing();
return nullptr;
}
-
- if (Tok.isNot(tok::identifier)) {
- Diag(Tok, diag::err_expected) << tok::identifier;
+
+ if (expectIdentifier())
break;
- }
propertyIvar = Tok.getIdentifierInfo();
propertyIvarLoc = ConsumeToken(); // consume ivar-name
}
@@ -2425,9 +2399,8 @@
cutOffParsing();
return nullptr;
}
-
- if (Tok.isNot(tok::identifier)) {
- Diag(Tok, diag::err_expected) << tok::identifier;
+
+ if (expectIdentifier()) {
SkipUntil(tok::semi);
return nullptr;
}
@@ -3563,8 +3536,8 @@
BalancedDelimiterTracker T(*this, tok::l_paren);
T.consumeOpen();
- if (Tok.isNot(tok::identifier))
- return ExprError(Diag(Tok, diag::err_expected) << tok::identifier);
+ if (expectIdentifier())
+ return ExprError();
IdentifierInfo *protocolId = Tok.getIdentifierInfo();
SourceLocation ProtoIdLoc = ConsumeToken();
diff --git a/lib/Parse/Parser.cpp b/lib/Parse/Parser.cpp
index bb45366..573f879 100644
--- a/lib/Parse/Parser.cpp
+++ b/lib/Parse/Parser.cpp
@@ -236,6 +236,21 @@
<< FixItHint::CreateRemoval(SourceRange(StartLoc, EndLoc));
}
+bool Parser::expectIdentifier() {
+ if (Tok.is(tok::identifier))
+ return false;
+ if (const auto *II = Tok.getIdentifierInfo()) {
+ if (II->isCPlusPlusKeyword(getLangOpts())) {
+ Diag(Tok, diag::err_expected_token_instead_of_objcxx_keyword)
+ << tok::identifier << Tok.getIdentifierInfo();
+ // Objective-C++: Recover by treating this keyword as a valid identifier.
+ return false;
+ }
+ }
+ Diag(Tok, diag::err_expected) << tok::identifier;
+ return true;
+}
+
//===----------------------------------------------------------------------===//
// Error recovery.
//===----------------------------------------------------------------------===//
diff --git a/test/Parser/objc-cxx-keyword-identifiers.mm b/test/Parser/objc-cxx-keyword-identifiers.mm
new file mode 100644
index 0000000..6791f0d
--- /dev/null
+++ b/test/Parser/objc-cxx-keyword-identifiers.mm
@@ -0,0 +1,62 @@
+// RUN: %clang_cc1 -fsyntax-only -std=c++11 -Wno-objc-root-class -Wno-incomplete-implementation -triple x86_64-apple-macosx10.10.0 -verify %s
+
+// rdar://20626062
+
+struct S {
+ int throw; // expected-error {{expected member name or ';' after declaration specifiers; 'throw' is a keyword in Objective-C++}}
+};
+
+@interface class // expected-error {{expected identifier; 'class' is a keyword in Objective-C++}}
+@end
+
+@interface Bar: class // expected-error {{expected identifier; 'class' is a keyword in Objective-C++}}
+@end
+
+@protocol P // ok
+@end
+
+@protocol new // expected-error {{expected identifier; 'new' is a keyword in Objective-C++}}
+@end
+
+@protocol P2, delete; // expected-error {{expected identifier; 'delete' is a keyword in Objective-C++}}
+
+@class Foo, try; // expected-error {{expected identifier; 'try' is a keyword in Objective-C++}}
+
+@interface Foo
+
+@property (readwrite, nonatomic) int a, b, throw; // expected-error {{expected member name or ';' after declaration specifiers; 'throw' is a keyword in Objective-C++}}
+
+-foo:(int)class; // expected-error {{expected identifier; 'class' is a keyword in Objective-C++}}
++foo:(int)constexpr; // expected-error {{expected identifier; 'constexpr' is a keyword in Objective-C++}}
+
+@end
+
+@interface Foo () <P, new> // expected-error {{expected identifier; 'new' is a keyword in Objective-C++}}
+@end
+
+@implementation Foo
+
+@synthesize a = _a; // ok
+@synthesize b = virtual; // expected-error {{expected identifier; 'virtual' is a keyword in Objective-C++}}
+
+@dynamic throw; // expected-error {{expected identifier; 'throw' is a keyword in Objective-C++}}
+
+-foo:(int)class { // expected-error {{expected identifier; 'class' is a keyword in Objective-C++}}
+}
+
+@end
+
+@implementation class // expected-error {{expected identifier; 'class' is a keyword in Objective-C++}}
+@end
+
+@implementation Bar: class // expected-error {{expected identifier; 'class' is a keyword in Objective-C++}}
+@end
+
+@compatibility_alias C Foo; // ok
+@compatibility_alias const_cast Bar; // expected-error {{expected identifier; 'const_cast' is a keyword in Objective-C++}}
+@compatibility_alias C2 class; // expected-error {{expected identifier; 'class' is a keyword in Objective-C++}}
+
+void func() {
+ (void)@protocol(P); // ok
+ (void)@protocol(delete); // expected-error {{expected identifier; 'delete' is a keyword in Objective-C++}}
+}