From: Douglas Gregor Date: Fri, 9 Jan 2009 22:42:13 +0000 (+0000) Subject: When we see a reference to a struct, class, or union like "struct X" X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=3218c4bb3b5d7250f12420de6db7ef3e3f805a75;p=clang When we see a reference to a struct, class, or union like "struct X" that is neither a definition nor a forward declaration and where X has not yet been declared as a tag, introduce a declaration into the appropriate scope (which is likely *not* to be the current scope). The rules for the placement of the declaration differ slightly in C and C++, so we implement both and test the various corner cases. This implementation isn't 100% correct due to some lingering issues with the function prototype scope (for a function parameter list) not being the same scope as the scope of the function definition. Testcase is FIXME'd; this probably isn't an important issue. Addresses . git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@62014 91177308-0d34-0410-b5e6-96231b3b80d8 --- diff --git a/include/clang/Basic/DiagnosticKinds.def b/include/clang/Basic/DiagnosticKinds.def index c8ebe066d1..bac6f242e0 100644 --- a/include/clang/Basic/DiagnosticKinds.def +++ b/include/clang/Basic/DiagnosticKinds.def @@ -726,6 +726,8 @@ DIAG(err_bad_variable_name, ERROR, "'%0' cannot be the name of a variable or data member") DIAG(err_parameter_name_omitted, ERROR, "parameter name omitted") +DIAG(warn_decl_in_param_list, WARNING, + "declaration of %0 will not be visible outside of this function") DIAG(err_declarator_need_ident, ERROR, "declarator requires an identifier") diff --git a/include/clang/Parse/Scope.h b/include/clang/Parse/Scope.h index 0180b47aac..5243ecceda 100644 --- a/include/clang/Parse/Scope.h +++ b/include/clang/Parse/Scope.h @@ -47,8 +47,8 @@ public: /// ControlScope - The controlling scope in a if/switch/while/for statement. ControlScope = 0x10, - /// CXXClassScope - The scope of a C++ struct/union/class definition. - CXXClassScope = 0x20, + /// ClassScope - The scope of a struct/union/class definition. + ClassScope = 0x20, /// BlockScope - This is a scope that corresponds to a block object. /// Blocks serve as top-level scopes for some objects like labels, they @@ -60,7 +60,11 @@ public: /// template parameters of a C++ template. Template parameter /// scope starts at the 'template' keyword and ends when the /// template declaration ends. - TemplateParamScope = 0x80 + TemplateParamScope = 0x80, + + /// FunctionPrototypeScope - This is a scope that corresponds to the + /// parameters within a function prototype. + FunctionPrototypeScope = 0x100 }; private: /// The parent scope for this scope. This is null for the translation-unit @@ -73,7 +77,7 @@ private: /// Flags - This contains a set of ScopeFlags, which indicates how the scope /// interrelates with other control flow statements. - unsigned Flags : 8; + unsigned Flags : 9; /// WithinElse - Whether this scope is part of the "else" branch in /// its parent ControlScope. @@ -197,9 +201,9 @@ public: void* getEntity() const { return Entity; } void setEntity(void *E) { Entity = E; } - /// isCXXClassScope - Return true if this scope is a C++ class scope. - bool isCXXClassScope() const { - return (getFlags() & Scope::CXXClassScope); + /// isClassScope - Return true if this scope is a class/struct/union scope. + bool isClassScope() const { + return (getFlags() & Scope::ClassScope); } /// isInCXXInlineMethodScope - Return true if this scope is a C++ inline @@ -207,7 +211,7 @@ public: bool isInCXXInlineMethodScope() const { if (const Scope *FnS = getFnParent()) { assert(FnS->getParent() && "TUScope not created?"); - return FnS->getParent()->isCXXClassScope(); + return FnS->getParent()->isClassScope(); } return false; } @@ -218,6 +222,12 @@ public: return getFlags() & Scope::TemplateParamScope; } + /// isFunctionPrototypeScope - Return true if this scope is a + /// function prototype scope. + bool isFunctionPrototypeScope() const { + return getFlags() & Scope::FunctionPrototypeScope; + } + /// isWithinElse - Whether we are within the "else" of the /// ControlParent (if any). bool isWithinElse() const { return WithinElse; } diff --git a/lib/Parse/ParseCXXInlineMethods.cpp b/lib/Parse/ParseCXXInlineMethods.cpp index a9712fe9ed..90d2f6fefc 100644 --- a/lib/Parse/ParseCXXInlineMethods.cpp +++ b/lib/Parse/ParseCXXInlineMethods.cpp @@ -75,7 +75,8 @@ void Parser::ParseLexedMethodDeclarations() { // Introduce the parameters into scope and parse their default // arguments. - ParseScope PrototypeScope(this, Scope::FnScope|Scope::DeclScope); + ParseScope PrototypeScope(this, + Scope::FunctionPrototypeScope|Scope::DeclScope); for (unsigned I = 0, N = LM.DefaultArgs.size(); I != N; ++I) { // Introduce the parameter into scope. Actions.ActOnDelayedCXXMethodParameter(CurScope, LM.DefaultArgs[I].Param); diff --git a/lib/Parse/ParseDecl.cpp b/lib/Parse/ParseDecl.cpp index 219295fa74..ac1a4be845 100644 --- a/lib/Parse/ParseDecl.cpp +++ b/lib/Parse/ParseDecl.cpp @@ -526,7 +526,7 @@ void Parser::ParseDeclarationSpecifiers(DeclSpec &DS, // constructor declaration. We're done with the decl-specifiers // and will treat this token as an identifier. if (getLang().CPlusPlus && - CurScope->isCXXClassScope() && + CurScope->isClassScope() && Actions.isCurrentClassName(*Tok.getIdentifierInfo(), CurScope) && NextToken().getKind() == tok::l_paren) goto DoneWithDeclSpec; @@ -948,7 +948,7 @@ void Parser::ParseStructUnionBody(SourceLocation RecordLoc, unsigned TagType, DeclTy *TagDecl) { SourceLocation LBraceLoc = ConsumeBrace(); - ParseScope StructScope(this, Scope::DeclScope); + ParseScope StructScope(this, Scope::ClassScope|Scope::DeclScope); Actions.ActOnTagStartDefinition(CurScope, TagDecl); // Empty structs are an extension in C (C99 6.7.2.1p7), but are allowed in @@ -1875,7 +1875,7 @@ void Parser::ParseFunctionDeclarator(SourceLocation LParenLoc, Declarator &D, // Enter function-declaration scope, limiting any declarators to the // function prototype scope, including parameter declarators. - ParseScope PrototypeScope(this, Scope::FnScope|Scope::DeclScope); + ParseScope PrototypeScope(this, Scope::FunctionPrototypeScope|Scope::DeclScope); bool IsVariadic = false; while (1) { diff --git a/lib/Parse/ParseDeclCXX.cpp b/lib/Parse/ParseDeclCXX.cpp index c6c2ae4cfe..26787aec12 100644 --- a/lib/Parse/ParseDeclCXX.cpp +++ b/lib/Parse/ParseDeclCXX.cpp @@ -720,7 +720,7 @@ void Parser::ParseCXXMemberSpecification(SourceLocation RecordLoc, SourceLocation LBraceLoc = ConsumeBrace(); - if (!CurScope->isCXXClassScope() && // Not about to define a nested class. + if (!CurScope->isClassScope() && // Not about to define a nested class. CurScope->isInCXXInlineMethodScope()) { // We will define a local class of an inline method. // Push a new LexedMethodsForTopClass for its inline methods. @@ -728,7 +728,7 @@ void Parser::ParseCXXMemberSpecification(SourceLocation RecordLoc, } // Enter a scope for the class. - ParseScope ClassScope(this, Scope::CXXClassScope|Scope::DeclScope); + ParseScope ClassScope(this, Scope::ClassScope|Scope::DeclScope); Actions.ActOnTagStartDefinition(CurScope, TagDecl); @@ -782,7 +782,7 @@ void Parser::ParseCXXMemberSpecification(SourceLocation RecordLoc, // // FIXME: Only function bodies and constructor ctor-initializers are // parsed correctly, fix the rest. - if (!CurScope->getParent()->isCXXClassScope()) { + if (!CurScope->getParent()->isClassScope()) { // We are not inside a nested class. This class and its nested classes // are complete and we can parse the delayed portions of method // declarations and the lexed inline method definitions. diff --git a/lib/Parse/Parser.cpp b/lib/Parse/Parser.cpp index d9b39099e0..799a2072f1 100644 --- a/lib/Parse/Parser.cpp +++ b/lib/Parse/Parser.cpp @@ -570,7 +570,7 @@ void Parser::ParseKNRParamDeclarations(Declarator &D) { // Enter function-declaration scope, limiting any declarators to the // function prototype scope, including parameter declarators. - ParseScope PrototypeScope(this, Scope::FnScope|Scope::DeclScope); + ParseScope PrototypeScope(this, Scope::FunctionPrototypeScope|Scope::DeclScope); // Read all the argument declarations. while (isDeclarationSpecifier()) { diff --git a/lib/Sema/SemaDecl.cpp b/lib/Sema/SemaDecl.cpp index ee72a98295..39b9c787cf 100644 --- a/lib/Sema/SemaDecl.cpp +++ b/lib/Sema/SemaDecl.cpp @@ -2859,6 +2859,7 @@ Sema::DeclTy *Sema::ActOnTag(Scope *S, unsigned TagType, TagKind TK, } DeclContext *DC = CurContext; + DeclContext *LexicalContext = CurContext; ScopedDecl *PrevDecl = 0; if (Name && SS.isNotEmpty()) { @@ -2971,6 +2972,35 @@ Sema::DeclTy *Sema::ActOnTag(Scope *S, unsigned TagType, TagKind TK, PrevDecl = 0; } } + } else if (TK == TK_Reference && SS.isEmpty() && Name && + (Kind != TagDecl::TK_enum)) { + // C++ [basic.scope.pdecl]p5: + // -- for an elaborated-type-specifier of the form + // + // class-key identifier + // + // if the elaborated-type-specifier is used in the + // decl-specifier-seq or parameter-declaration-clause of a + // function defined in namespace scope, the identifier is + // declared as a class-name in the namespace that contains + // the declaration; otherwise, except as a friend + // declaration, the identifier is declared in the smallest + // non-class, non-function-prototype scope that contains the + // declaration. + // + // C99 6.7.2.3p8 has a similar (but not identical!) provision for + // C structs and unions. + + // Find the context where we'll be declaring the tag. + while (DC->isRecord()) + DC = DC->getParent(); + LexicalContext = DC; + + // Find the scope where we'll be declaring the tag. + while (S->isClassScope() || + (getLangOptions().CPlusPlus && S->isFunctionPrototypeScope()) || + (S->getFlags() & Scope::DeclScope == 0)) + S = S->getParent(); } CreateNewDecl: @@ -3025,9 +3055,13 @@ CreateNewDecl: if (Attr) ProcessDeclAttributeList(New, Attr); + // If we're declaring or defining + if (Name && S->isFunctionPrototypeScope() && !getLangOptions().CPlusPlus) + Diag(Loc, diag::warn_decl_in_param_list) << Context.getTagDeclType(New); + // Set the lexical context. If the tag has a C++ scope specifier, the // lexical context will be different from the semantic context. - New->setLexicalDeclContext(CurContext); + New->setLexicalDeclContext(LexicalContext); // If this has an identifier, add it to the scope stack. if (Name) { @@ -3037,13 +3071,20 @@ CreateNewDecl: S = S->getParent(); // Add it to the decl chain. - PushOnScopeChains(New, S); + if (LexicalContext != CurContext) { + // FIXME: PushOnScopeChains should not rely on CurContext! + DeclContext *OldContext = CurContext; + CurContext = LexicalContext; + PushOnScopeChains(New, S); + CurContext = OldContext; + } else + PushOnScopeChains(New, S); } else if (getLangOptions().CPlusPlus) { // FIXME: We also want to do this for C, but if this tag is // defined within a structure CurContext will point to the context // enclosing the structure, and we would end up inserting the tag // type into the wrong place. - CurContext->addDecl(Context, New); + LexicalContext->addDecl(Context, New); } return New; diff --git a/test/Sema/type-spec-struct-union.c b/test/Sema/type-spec-struct-union.c new file mode 100644 index 0000000000..38e57f7c1b --- /dev/null +++ b/test/Sema/type-spec-struct-union.c @@ -0,0 +1,37 @@ +// RUN: clang -fsyntax-only -pedantic -verify %s + +/* This test checks the introduction of struct and union types based + on a type specifier of the form "struct-or-union identifier" when they + type has not yet been declared. See C99 6.7.2.3p8. */ + +typedef struct S1 { + union { + struct S2 *x; + struct S3 *y; + } u1; +} S1; + +int test_struct_scope(S1 *s1, struct S2 *s2, struct S3 *s3) { + if (s1->u1.x == s2) return 1; + if (s1->u1.y == s3) return 1; + return 0; +} + +int test_struct_scope_2(S1 *s1) { + struct S2 { int x; } *s2 = 0; + if (s1->u1.x == s2) return 1; /* expected-warning {{comparison of distinct pointer types ('struct S2 *' and 'struct S2 *')}} */ + return 0; +} + +// FIXME: We do not properly implement C99 6.2.1p4, which says that +// the type "struct S4" declared in the function parameter list has +// block scope within the function definition. The problem, in this +// case, is that the code is ill-formed but we warn about the two S4's +// being incompatible (we think they are two different types). +int test_struct_scope_3(struct S4 * s4) { // expected-warning{{declaration of 'struct S4' will not be visible outside of this function}} + struct S4 { int y; } *s4_2 = 0; + /* if (s4 == s4_2) return 1; */ + return 0; +} + +void f(struct S5 { int y; } s5); // expected-warning{{declaration of 'struct S5' will not be visible outside of this function}} diff --git a/test/SemaCXX/elaborated-type-specifier.cpp b/test/SemaCXX/elaborated-type-specifier.cpp new file mode 100644 index 0000000000..b9ba508789 --- /dev/null +++ b/test/SemaCXX/elaborated-type-specifier.cpp @@ -0,0 +1,48 @@ +// RUN: clang -fsyntax-only -verify %s + +// Test the use of elaborated-type-specifiers to inject the names of +// structs (or classes or unions) into an outer scope as described in +// C++ [basic.scope.pdecl]p5. +typedef struct S1 { + union { + struct S2 *x; + struct S3 *y; + }; +} S1; + +bool test_elab(S1 *s1, struct S2 *s2, struct S3 *s3) { + if (s1->x == s2) return true; + if (s1->y == s3) return true; + return false; +} + +namespace NS { + class X { + public: + void test_elab2(struct S4 *s4); + }; + + void X::test_elab2(S4 *s4) { } +} + +void test_X_elab(NS::X x) { + struct S4 *s4 = 0; + x.test_elab2(s4); // expected-error{{incompatible type passing 'struct S4 *', expected 'struct S4 *'}} +} + +namespace NS { + S4 *get_S4(); +} + +void test_S5_scope() { + S4 *s4; // expected-error{{use of undeclared identifier 'S4'}} +} + +// FIXME: the warning below should be an error! +int test_funcparam_scope(struct S5 * s5) { + struct S5 { int y; } *s5_2 = 0; + if (s5 == s5_2) return 1; // expected-warning {{comparison of distinct pointer types ('struct S5 *' and 'struct S5 *')}} + return 0; +} + +