forked from OSchip/llvm-project
[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 llvm-svn: 299950
This commit is contained in:
parent
59a2d7b909
commit
f127821140
|
@ -436,6 +436,13 @@ def err_declaration_does_not_declare_param : Error<
|
|||
"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">;
|
||||
|
|
|
@ -280,7 +280,11 @@ public:
|
|||
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.
|
||||
|
|
|
@ -800,6 +800,14 @@ private:
|
|||
/// \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
|
||||
|
|
|
@ -244,7 +244,7 @@ static KeywordStatus getTokenKwStatus(const LangOptions &LangOpts,
|
|||
|
||||
/// \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:
|
||||
|
@ -254,6 +254,19 @@ bool IdentifierInfo::isKeyword(const LangOptions &LangOpts) {
|
|||
}
|
||||
}
|
||||
|
||||
/// \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
|
||||
|
|
|
@ -5580,6 +5580,21 @@ void Parser::ParseDirectDeclarator(Declarator &D) {
|
|||
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()
|
||||
|
|
|
@ -137,8 +137,7 @@ Parser::ParseObjCAtClassDeclaration(SourceLocation atLoc) {
|
|||
|
||||
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);
|
||||
}
|
||||
|
@ -229,11 +228,8 @@ Decl *Parser::ParseObjCAtInterfaceDeclaration(SourceLocation AtLoc,
|
|||
|
||||
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();
|
||||
|
@ -319,11 +315,8 @@ Decl *Parser::ParseObjCAtInterfaceDeclaration(SourceLocation AtLoc,
|
|||
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();
|
||||
|
||||
|
@ -1438,12 +1431,9 @@ Decl *Parser::ParseObjCMethodDecl(SourceLocation mLoc,
|
|||
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();
|
||||
|
@ -1552,8 +1542,7 @@ ParseObjCProtocolReferences(SmallVectorImpl<Decl *> &Protocols,
|
|||
return true;
|
||||
}
|
||||
|
||||
if (Tok.isNot(tok::identifier)) {
|
||||
Diag(Tok, diag::err_expected) << tok::identifier;
|
||||
if (expectIdentifier()) {
|
||||
SkipUntil(tok::greater, StopAtSemi);
|
||||
return true;
|
||||
}
|
||||
|
@ -2035,10 +2024,8 @@ Parser::ParseObjCAtProtocolDeclaration(SourceLocation AtLoc,
|
|||
|
||||
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();
|
||||
|
@ -2058,8 +2045,7 @@ Parser::ParseObjCAtProtocolDeclaration(SourceLocation AtLoc,
|
|||
// 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;
|
||||
}
|
||||
|
@ -2126,11 +2112,8 @@ Parser::ParseObjCAtImplementationDeclaration(SourceLocation AtLoc) {
|
|||
|
||||
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
|
||||
|
@ -2200,11 +2183,8 @@ Parser::ParseObjCAtImplementationDeclaration(SourceLocation AtLoc) {
|
|||
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
|
||||
}
|
||||
|
@ -2304,16 +2284,12 @@ Decl *Parser::ParseObjCAtAliasDeclaration(SourceLocation atLoc) {
|
|||
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");
|
||||
|
@ -2361,11 +2337,9 @@ Decl *Parser::ParseObjCPropertySynthesize(SourceLocation atLoc) {
|
|||
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
|
||||
}
|
||||
|
@ -2423,9 +2397,8 @@ Decl *Parser::ParseObjCPropertyDynamic(SourceLocation atLoc) {
|
|||
cutOffParsing();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (Tok.isNot(tok::identifier)) {
|
||||
Diag(Tok, diag::err_expected) << tok::identifier;
|
||||
|
||||
if (expectIdentifier()) {
|
||||
SkipUntil(tok::semi);
|
||||
return nullptr;
|
||||
}
|
||||
|
@ -3561,8 +3534,8 @@ Parser::ParseObjCProtocolExpression(SourceLocation AtLoc) {
|
|||
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();
|
||||
|
|
|
@ -211,6 +211,21 @@ void Parser::ConsumeExtraSemi(ExtraSemiKind Kind, unsigned TST) {
|
|||
<< 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.
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
|
|
@ -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++}}
|
||||
}
|
Loading…
Reference in New Issue