From: Chris Lattner Date: Sun, 12 Apr 2009 21:49:30 +0000 (+0000) Subject: Diagnose invalid uses of tagged types with a missing tag. For example, in: X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=4c97d762d8c5a84f6554e5bfb31d28c90df64158;p=clang Diagnose invalid uses of tagged types with a missing tag. For example, in: struct xyz { int y; }; enum abc { ZZZ }; static xyz b; abc c; we used to produce: t2.c:4:8: error: unknown type name 'xyz' static xyz b; ^ t2.c:5:1: error: unknown type name 'abc' abc c; ^ we now produce: t2.c:4:8: error: use of tagged type 'xyz' without 'struct' tag static xyz b; ^ struct t2.c:5:1: error: use of tagged type 'abc' without 'enum' tag abc c; ^ enum GCC produces the normal: t2.c:4: error: expected ‘=’, ‘,’, ‘;’, ‘asm’ or ‘__attribute__’ before ‘b’ t2.c:5: error: expected ‘=’, ‘,’, ‘;’, ‘asm’ or ‘__attribute__’ before ‘c’ rdar://6783347 git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@68914 91177308-0d34-0410-b5e6-96231b3b80d8 --- diff --git a/include/clang/Basic/DiagnosticParseKinds.td b/include/clang/Basic/DiagnosticParseKinds.td index 66a48cffb0..5e97faa96d 100644 --- a/include/clang/Basic/DiagnosticParseKinds.td +++ b/include/clang/Basic/DiagnosticParseKinds.td @@ -145,6 +145,8 @@ def err_invalid_decl_spec_combination : Error< "cannot combine with previous '%0' declaration specifier">; def err_unknown_typename : Error< "unknown type name %0">; +def err_use_of_tag_name_without_tag : Error< + "use of tagged type %0 without '%1' tag">; /// Objective-C parser diagnostics diff --git a/include/clang/Parse/Action.h b/include/clang/Parse/Action.h index 699d571146..df54da46f6 100644 --- a/include/clang/Parse/Action.h +++ b/include/clang/Parse/Action.h @@ -139,6 +139,15 @@ public: virtual TypeTy *getTypeName(IdentifierInfo &II, SourceLocation NameLoc, Scope *S, const CXXScopeSpec *SS = 0) = 0; + /// isTagName() - This method is called *for error recovery purposes only* + /// to determine if the specified name is a valid tag name ("struct foo"). If + /// so, this returns the TST for the tag corresponding to it (TST_enum, + /// TST_union, TST_struct, TST_class). This is used to diagnose cases in C + /// where the user forgot to specify the tag. + virtual DeclSpec::TST isTagName(IdentifierInfo &II, Scope *S) { + return DeclSpec::TST_unspecified; + } + /// isCurrentClassName - Return true if the specified name is the /// name of the innermost C++ class type currently being defined. virtual bool isCurrentClassName(const IdentifierInfo &II, Scope *S, diff --git a/include/clang/Parse/Parser.h b/include/clang/Parse/Parser.h index 3e09a2cc53..f5a00065df 100644 --- a/include/clang/Parse/Parser.h +++ b/include/clang/Parse/Parser.h @@ -825,7 +825,8 @@ private: void ParseObjCTypeQualifierList(ObjCDeclSpec &DS); - void ParseEnumSpecifier(DeclSpec &DS, AccessSpecifier AS = AS_none); + void ParseEnumSpecifier(SourceLocation TagLoc, DeclSpec &DS, + AccessSpecifier AS = AS_none); void ParseEnumBody(SourceLocation StartLoc, DeclPtrTy TagDecl); void ParseStructUnionBody(SourceLocation StartLoc, unsigned TagType, DeclPtrTy TagDecl); @@ -1012,7 +1013,8 @@ private: // C++ 9: classes [class] and C structs/unions. TypeResult ParseClassName(SourceLocation &EndLocation, const CXXScopeSpec *SS = 0); - void ParseClassSpecifier(DeclSpec &DS, + void ParseClassSpecifier(tok::TokenKind TagTokKind, SourceLocation TagLoc, + DeclSpec &DS, TemplateParameterLists *TemplateParams = 0, AccessSpecifier AS = AS_none); void ParseCXXMemberSpecification(SourceLocation StartLoc, unsigned TagType, diff --git a/lib/Parse/ParseDecl.cpp b/lib/Parse/ParseDecl.cpp index 76bdd2f487..ae00ada311 100644 --- a/lib/Parse/ParseDecl.cpp +++ b/lib/Parse/ParseDecl.cpp @@ -654,19 +654,39 @@ void Parser::ParseDeclarationSpecifiers(DeclSpec &DS, } // Otherwise, if we don't consume this token, we are going to emit an - // error anyway. Since this is almost certainly an invalid type name, - // emit a diagnostic that says it, eat the token, and pretend we saw an - // 'int'. + // error anyway. Try to recover from various common problems. Check + // to see if this was a reference to a tag name without a tag specified. + // This is a common problem in C (saying 'foo' insteat of 'struct foo'). + const char *TagName = 0; + tok::TokenKind TagKind = tok::unknown; + + switch (Actions.isTagName(*Tok.getIdentifierInfo(), CurScope)) { + default: break; + case DeclSpec::TST_enum: TagName="enum" ;TagKind=tok::kw_enum ;break; + case DeclSpec::TST_union: TagName="union" ;TagKind=tok::kw_union ;break; + case DeclSpec::TST_struct:TagName="struct";TagKind=tok::kw_struct;break; + case DeclSpec::TST_class: TagName="class" ;TagKind=tok::kw_class ;break; + } + if (TagName) { + Diag(Loc, diag::err_use_of_tag_name_without_tag) + << Tok.getIdentifierInfo() << TagName + << CodeModificationHint::CreateInsertion(Tok.getLocation(),TagName); + + // Parse this as a tag as if the missing tag were present. + if (TagKind == tok::kw_enum) + ParseEnumSpecifier(Loc, DS, AS); + else + ParseClassSpecifier(TagKind, Loc, DS, TemplateParams, AS); + continue; + } + + // Since this is almost certainly an invalid type name, emit a + // diagnostic that says it, eat the token, and pretend we saw an 'int'. Diag(Loc, diag::err_unknown_typename) << Tok.getIdentifierInfo(); DS.SetTypeSpecType(DeclSpec::TST_int, Loc, PrevSpec); DS.SetRangeEnd(Tok.getLocation()); ConsumeToken(); - // TODO: in C, we could redo the lookup in the tag namespace to catch - // things like "foo x" where the user meant "struct foo x" etc, this - // would be much nicer for both error recovery, diagnostics, and we - // could even emit a fixit hint. - // TODO: Could inject an invalid typedef decl in an enclosing scope to // avoid rippling error messages on subsequent uses of the same type, // could be useful if #include was forgotten. @@ -780,8 +800,6 @@ void Parser::ParseDeclarationSpecifiers(DeclSpec &DS, case tok::kw___thread: isInvalid = DS.SetStorageClassSpecThread(Loc, PrevSpec)*2; break; - - continue; // function-specifier case tok::kw_inline: @@ -851,13 +869,17 @@ void Parser::ParseDeclarationSpecifiers(DeclSpec &DS, // class-specifier: case tok::kw_class: case tok::kw_struct: - case tok::kw_union: - ParseClassSpecifier(DS, TemplateParams, AS); + case tok::kw_union: { + tok::TokenKind Kind = Tok.getKind(); + ConsumeToken(); + ParseClassSpecifier(Kind, Loc, DS, TemplateParams, AS); continue; + } // enum-specifier: case tok::kw_enum: - ParseEnumSpecifier(DS, AS); + ConsumeToken(); + ParseEnumSpecifier(Loc, DS, AS); continue; // cv-qualifier: @@ -1069,13 +1091,17 @@ bool Parser::ParseOptionalTypeSpecifier(DeclSpec &DS, int& isInvalid, // class-specifier: case tok::kw_class: case tok::kw_struct: - case tok::kw_union: - ParseClassSpecifier(DS, TemplateParams); + case tok::kw_union: { + tok::TokenKind Kind = Tok.getKind(); + ConsumeToken(); + ParseClassSpecifier(Kind, Loc, DS, TemplateParams); return true; + } // enum-specifier: case tok::kw_enum: - ParseEnumSpecifier(DS); + ConsumeToken(); + ParseEnumSpecifier(Loc, DS); return true; // cv-qualifier: @@ -1325,10 +1351,8 @@ void Parser::ParseStructUnionBody(SourceLocation RecordLoc, /// [C++] elaborated-type-specifier: /// [C++] 'enum' '::'[opt] nested-name-specifier[opt] identifier /// -void Parser::ParseEnumSpecifier(DeclSpec &DS, AccessSpecifier AS) { - assert(Tok.is(tok::kw_enum) && "Not an enum specifier"); - SourceLocation StartLoc = ConsumeToken(); - +void Parser::ParseEnumSpecifier(SourceLocation StartLoc, DeclSpec &DS, + AccessSpecifier AS) { // Parse the tag portion of this. AttributeList *Attr = 0; diff --git a/lib/Parse/ParseDeclCXX.cpp b/lib/Parse/ParseDeclCXX.cpp index 91c05669b2..d4310e0cbf 100644 --- a/lib/Parse/ParseDeclCXX.cpp +++ b/lib/Parse/ParseDeclCXX.cpp @@ -390,19 +390,19 @@ Parser::TypeResult Parser::ParseClassName(SourceLocation &EndLocation, /// struct-or-union: /// 'struct' /// 'union' -void Parser::ParseClassSpecifier(DeclSpec &DS, +void Parser::ParseClassSpecifier(tok::TokenKind TagTokKind, + SourceLocation StartLoc, DeclSpec &DS, TemplateParameterLists *TemplateParams, AccessSpecifier AS) { - assert((Tok.is(tok::kw_class) || - Tok.is(tok::kw_struct) || - Tok.is(tok::kw_union)) && - "Not a class specifier"); - DeclSpec::TST TagType = - Tok.is(tok::kw_class) ? DeclSpec::TST_class : - Tok.is(tok::kw_struct) ? DeclSpec::TST_struct : - DeclSpec::TST_union; - - SourceLocation StartLoc = ConsumeToken(); + DeclSpec::TST TagType; + if (TagTokKind == tok::kw_struct) + TagType = DeclSpec::TST_struct; + else if (TagTokKind == tok::kw_class) + TagType = DeclSpec::TST_class; + else { + assert(TagTokKind == tok::kw_union && "Not a class specifier"); + TagType = DeclSpec::TST_union; + } AttributeList *Attr = 0; // If attributes exist after tag, parse them. diff --git a/lib/Sema/Sema.h b/lib/Sema/Sema.h index e8eb960a3f..f48e75afb3 100644 --- a/lib/Sema/Sema.h +++ b/lib/Sema/Sema.h @@ -332,6 +332,8 @@ public: virtual TypeTy *getTypeName(IdentifierInfo &II, SourceLocation NameLoc, Scope *S, const CXXScopeSpec *SS); + virtual DeclSpec::TST isTagName(IdentifierInfo &II, Scope *S); + virtual DeclPtrTy ActOnDeclarator(Scope *S, Declarator &D) { return ActOnDeclarator(S, D, false); } diff --git a/lib/Sema/SemaDecl.cpp b/lib/Sema/SemaDecl.cpp index 603b8b31a2..8ce624b788 100644 --- a/lib/Sema/SemaDecl.cpp +++ b/lib/Sema/SemaDecl.cpp @@ -139,6 +139,29 @@ Sema::TypeTy *Sema::getTypeName(IdentifierInfo &II, SourceLocation NameLoc, return 0; } +/// isTagName() - This method is called *for error recovery purposes only* +/// to determine if the specified name is a valid tag name ("struct foo"). If +/// so, this returns the TST for the tag corresponding to it (TST_enum, +/// TST_union, TST_struct, TST_class). This is used to diagnose cases in C +/// where the user forgot to specify the tag. +DeclSpec::TST Sema::isTagName(IdentifierInfo &II, Scope *S) { + // Do a tag name lookup in this scope. + LookupResult R = LookupName(S, &II, LookupTagName, false, false); + if (R.getKind() == LookupResult::Found) + if (const TagDecl *TD = dyn_cast(R.getAsDecl())) { + switch (TD->getTagKind()) { + case TagDecl::TK_struct: return DeclSpec::TST_struct; + case TagDecl::TK_union: return DeclSpec::TST_union; + case TagDecl::TK_class: return DeclSpec::TST_class; + case TagDecl::TK_enum: return DeclSpec::TST_enum; + } + } + + return DeclSpec::TST_unspecified; +} + + + DeclContext *Sema::getContainingDC(DeclContext *DC) { if (CXXMethodDecl *MD = dyn_cast(DC)) { // A C++ out-of-line method will return to the file declaration context. diff --git a/test/Parser/declarators.c b/test/Parser/declarators.c index 09b43e5057..0b3f5cecd5 100644 --- a/test/Parser/declarators.c +++ b/test/Parser/declarators.c @@ -40,16 +40,26 @@ int (test5), ; // expected-error {{expected identifier or '('}} // PR3963 & rdar://6759604 - test error recovery for mistyped "typenames". -struct xyz { int y; }; - +foo_t *d; // expected-error {{unknown type name 'foo_t'}} foo_t a = 4; // expected-error {{unknown type name 'foo_t'}} -xyz b; // expected-error {{unknown type name 'xyz'}} +int test6() { return a; } // a should be declared. + +// Use of tagged type without tag. rdar://6783347 +struct xyz { int y; }; +enum myenum { ASDFAS }; +xyz b; // expected-error {{use of tagged type 'xyz' without 'struct' tag}} +myenum c; // expected-error {{use of tagged type 'myenum' without 'enum' tag}} + +float *test7() { + // We should recover 'b' by parsing it with a valid type of "struct xyz", which + // allows us to diagnose other bad things done with y, such as this. + return &b.y; // expected-warning {{incompatible pointer types returning 'int *', expected 'float *'}} +} -foo_t *d; // expected-error {{unknown type name 'foo_t'}} +// Verify that implicit int still works. static f; // expected-warning {{type specifier missing, defaults to 'int'}} static g = 4; // expected-warning {{type specifier missing, defaults to 'int'}} static h // expected-warning {{type specifier missing, defaults to 'int'}} __asm__("foo"); // expected-warning {{extension used}} -int bar() { return a; }