From: Douglas Gregor Date: Mon, 15 Dec 2008 23:53:10 +0000 (+0000) Subject: Diagnose erroneous uses of out-of-line member definitions and scope X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=584049d49d956add7bce5669e9823491f7d8de78;p=clang Diagnose erroneous uses of out-of-line member definitions and scope specifiers. Specifically: * Determine when an out-of-line function definition does not match any declaration within the class or namespace (including coping with overloaded functions). * Complain about typedefs and parameters that have scope specifiers. * Complain about out-of-line declarations that aren't also definitions. * Complain about non-static data members being declared out-of-line. * Allow cv-qualifiers on out-of-line member function definitions. git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@61058 91177308-0d34-0410-b5e6-96231b3b80d8 --- diff --git a/include/clang/Basic/DiagnosticKinds.def b/include/clang/Basic/DiagnosticKinds.def index fba04101cf..8b0bd27767 100644 --- a/include/clang/Basic/DiagnosticKinds.def +++ b/include/clang/Basic/DiagnosticKinds.def @@ -1169,7 +1169,20 @@ DIAG(err_typecheck_incomplete_tag, ERROR, "incomplete definition of type %0") DIAG(err_typecheck_no_member, ERROR, "no member named %0") -DIAG(err_member_redeclared, ERROR, "class member cannot be redeclared") +DIAG(err_member_redeclared, ERROR, + "class member cannot be redeclared") +DIAG(err_member_def_does_not_match, ERROR, + "out-of-line definition does not match any declaration in %0") +DIAG(err_nonstatic_member_out_of_line, ERROR, + "non-static data member defined out-of-line") +DIAG(err_qualified_typedef_declarator, ERROR, + "typedef declarator cannot be qualified") +DIAG(err_qualified_param_declarator, ERROR, + "parameter declarator cannot be qualified") +DIAG(err_out_of_line_declaration, ERROR, + "out-of-line declaration of a member must be a definition") +DIAG(note_member_def_close_match, NOTE, + "member declaration nearly matches") DIAG(err_typecheck_ivar_variable_size, ERROR, "instance variables must have a constant size") // FIXME: Improve with %select diff --git a/lib/Sema/Sema.h b/lib/Sema/Sema.h index 528611b3a9..bebd0cfe74 100644 --- a/lib/Sema/Sema.h +++ b/lib/Sema/Sema.h @@ -267,7 +267,11 @@ public: // virtual TypeTy *isTypeName(IdentifierInfo &II, Scope *S, const CXXScopeSpec *SS); - virtual DeclTy *ActOnDeclarator(Scope *S, Declarator &D, DeclTy *LastInGroup); + virtual DeclTy *ActOnDeclarator(Scope *S, Declarator &D, DeclTy *LastInGroup) { + return ActOnDeclarator(S, D, LastInGroup, false); + } + DeclTy *ActOnDeclarator(Scope *S, Declarator &D, DeclTy *LastInGroup, + bool IsFunctionDefinition); virtual DeclTy *ActOnParamDeclarator(Scope *S, Declarator &D); virtual void ActOnParamDefaultArgument(DeclTy *param, SourceLocation EqualLoc, diff --git a/lib/Sema/SemaDecl.cpp b/lib/Sema/SemaDecl.cpp index 1578a253e9..1be30715e5 100644 --- a/lib/Sema/SemaDecl.cpp +++ b/lib/Sema/SemaDecl.cpp @@ -910,8 +910,32 @@ DeclarationName Sema::GetNameForDeclarator(Declarator &D) { return DeclarationName(); } +/// isNearlyMatchingMemberFunction - Determine whether the C++ member +/// functions Declaration and Definition are "nearly" matching. This +/// heuristic is used to improve diagnostics in the case where an +/// out-of-line member function definition doesn't match any +/// declaration within the class. +static bool isNearlyMatchingMemberFunction(ASTContext &Context, + FunctionDecl *Declaration, + FunctionDecl *Definition) { + if (Declaration->param_size() != Definition->param_size()) + return false; + for (unsigned Idx = 0; Idx < Declaration->param_size(); ++Idx) { + QualType DeclParamTy = Declaration->getParamDecl(Idx)->getType(); + QualType DefParamTy = Definition->getParamDecl(Idx)->getType(); + + DeclParamTy = Context.getCanonicalType(DeclParamTy.getNonReferenceType()); + DefParamTy = Context.getCanonicalType(DefParamTy.getNonReferenceType()); + if (DeclParamTy.getUnqualifiedType() != DefParamTy.getUnqualifiedType()) + return false; + } + + return true; +} + Sema::DeclTy * -Sema::ActOnDeclarator(Scope *S, Declarator &D, DeclTy *lastDecl) { +Sema::ActOnDeclarator(Scope *S, Declarator &D, DeclTy *lastDecl, + bool IsFunctionDefinition) { ScopedDecl *LastDeclarator = dyn_cast_or_null((Decl *)lastDecl); DeclarationName Name = GetNameForDeclarator(D); @@ -952,21 +976,21 @@ Sema::ActOnDeclarator(Scope *S, Declarator &D, DeclTy *lastDecl) { // after the point of declaration in a namespace that encloses the // declarations namespace. // - // FIXME: We need to perform this check later, once we know that - // we've actually found a redeclaration. Otherwise, just the fact - // that there is some entity with the same name will suppress this - // diagnostic, e.g., we fail to diagnose: + // Note that we only check the context at this point. We don't yet + // have enough information to make sure that PrevDecl is actually + // the declaration we want to match. For example, given: + // // class X { // void f(); + // void f(float); // }; // - // void X::f(int) { } // ill-formed, but we don't complain. - if (PrevDecl == 0) { - // No previous declaration in the qualifying scope. - Diag(D.getIdentifierLoc(), diag::err_typecheck_no_member) - << Name << D.getCXXScopeSpec().getRange(); - InvalidDecl = true; - } else if (!CurContext->Encloses(DC)) { + // void X::f(int) { } // ill-formed + // + // In this case, PrevDecl will point to the overload set + // containing the two f's declared in X, but neither of them + // matches. + if (!CurContext->Encloses(DC)) { // The qualifying scope doesn't enclose the original declaration. // Emit diagnostic based on current scope. SourceLocation L = D.getIdentifierLoc(); @@ -999,6 +1023,15 @@ Sema::ActOnDeclarator(Scope *S, Declarator &D, DeclTy *lastDecl) { assert(!R.isNull() && "GetTypeForDeclarator() returned null type"); if (D.getDeclSpec().getStorageClassSpec() == DeclSpec::SCS_typedef) { + // Typedef declarators cannot be qualified (C++ [dcl.meaning]p1). + if (D.getCXXScopeSpec().isSet()) { + Diag(D.getIdentifierLoc(), diag::err_qualified_typedef_declarator) + << D.getCXXScopeSpec().getRange(); + InvalidDecl = true; + // Pretend we didn't see the scope specifier. + DC = 0; + } + // Check that there are no default arguments (C++ only). if (getLangOptions().CPlusPlus) CheckExtraCXXDefaultArguments(D); @@ -1116,6 +1149,7 @@ Sema::ActOnDeclarator(Scope *S, Declarator &D, DeclTy *lastDecl) { // FIXME: Move to DeclGroup... D.getDeclSpec().getSourceRange().getBegin()); } + // Handle attributes. ProcessDeclAttributes(NewFD, D); @@ -1288,17 +1322,83 @@ Sema::ActOnDeclarator(Scope *S, Declarator &D, DeclTy *lastDecl) { // Check default arguments now that we have merged decls. CheckCXXDefaultArguments(NewFD); + + // An out-of-line member function declaration must also be a + // definition (C++ [dcl.meaning]p1). + if (!IsFunctionDefinition && D.getCXXScopeSpec().isSet() && + !InvalidDecl) { + Diag(NewFD->getLocation(), diag::err_out_of_line_declaration) + << D.getCXXScopeSpec().getRange(); + NewFD->setInvalidDecl(); + } } return NewFD; } } + + if (!Redeclaration && D.getCXXScopeSpec().isSet()) { + // The user tried to provide an out-of-line definition for a + // member function, but there was no such member function + // declared (C++ [class.mfct]p2). For example: + // + // class X { + // void f() const; + // }; + // + // void X::f() { } // ill-formed + // + // Complain about this problem, and attempt to suggest close + // matches (e.g., those that differ only in cv-qualifiers and + // whether the parameter types are references). + Diag(D.getIdentifierLoc(), diag::err_member_def_does_not_match) + << cast(DC)->getDeclName() + << D.getCXXScopeSpec().getRange(); + InvalidDecl = true; + + PrevDecl = LookupDecl(Name, Decl::IDNS_Ordinary, S, DC); + if (!PrevDecl) { + // Nothing to suggest. + } else if (OverloadedFunctionDecl *Ovl + = dyn_cast(PrevDecl)) { + for (OverloadedFunctionDecl::function_iterator + Func = Ovl->function_begin(), + FuncEnd = Ovl->function_end(); + Func != FuncEnd; ++Func) { + if (isNearlyMatchingMemberFunction(Context, *Func, NewFD)) + Diag((*Func)->getLocation(), diag::note_member_def_close_match); + + } + } else if (CXXMethodDecl *Method = dyn_cast(PrevDecl)) { + // Suggest this no matter how mismatched it is; it's the only + // thing we have. + unsigned diag; + if (isNearlyMatchingMemberFunction(Context, Method, NewFD)) + diag = diag::note_member_def_close_match; + else if (Method->getBody()) + diag = diag::note_previous_definition; + else + diag = diag::note_previous_declaration; + Diag(Method->getLocation(), diag); + } + + PrevDecl = 0; + } } New = NewFD; - // In C++, check default arguments now that we have merged decls. - if (getLangOptions().CPlusPlus) + if (getLangOptions().CPlusPlus) { + // In C++, check default arguments now that we have merged decls. CheckCXXDefaultArguments(NewFD); + + // An out-of-line member function declaration must also be a + // definition (C++ [dcl.meaning]p1). + if (!IsFunctionDefinition && D.getCXXScopeSpec().isSet()) { + Diag(NewFD->getLocation(), diag::err_out_of_line_declaration) + << D.getCXXScopeSpec().getRange(); + InvalidDecl = true; + } + } } else { // Check that there are no default arguments (C++ only). if (getLangOptions().CPlusPlus) @@ -1337,7 +1437,6 @@ Sema::ActOnDeclarator(Scope *S, Declarator &D, DeclTy *lastDecl) { } if (DC->isCXXRecord()) { - assert(SC == VarDecl::Static && "Invalid storage class for member!"); // This is a static data member for a C++ class. NewVD = CXXClassVarDecl::Create(Context, cast(DC), D.getIdentifierLoc(), II, @@ -1380,8 +1479,24 @@ Sema::ActOnDeclarator(Scope *S, Declarator &D, DeclTy *lastDecl) { // Merge the decl with the existing one if appropriate. If the decl is // in an outer scope, it isn't the same thing. if (PrevDecl && isDeclInScope(PrevDecl, DC, S)) { + if (isa(PrevDecl) && D.getCXXScopeSpec().isSet()) { + // The user tried to define a non-static data member + // out-of-line (C++ [dcl.meaning]p1). + Diag(NewVD->getLocation(), diag::err_nonstatic_member_out_of_line) + << D.getCXXScopeSpec().getRange(); + NewVD->Destroy(Context); + return 0; + } + NewVD = MergeVarDecl(NewVD, PrevDecl); if (NewVD == 0) return 0; + + if (D.getCXXScopeSpec().isSet()) { + // No previous declaration in the qualifying scope. + Diag(D.getIdentifierLoc(), diag::err_typecheck_no_member) + << Name << D.getCXXScopeSpec().getRange(); + InvalidDecl = true; + } } New = NewVD; } @@ -2151,9 +2266,8 @@ Sema::DeclTy *Sema::FinalizeDeclaratorGroup(Scope *S, DeclTy *group) { /// to introduce parameters into function prototype scope. Sema::DeclTy * Sema::ActOnParamDeclarator(Scope *S, Declarator &D) { - // FIXME: disallow CXXScopeSpec for param declarators. const DeclSpec &DS = D.getDeclSpec(); - + // Verify C99 6.7.5.3p2: The only SCS allowed is 'register'. VarDecl::StorageClass StorageClass = VarDecl::None; if (DS.getStorageClassSpec() == DeclSpec::SCS_register) { @@ -2231,6 +2345,13 @@ Sema::ActOnParamDeclarator(Scope *S, Declarator &D) { if (D.getInvalidType()) New->setInvalidDecl(); + // Parameter declarators cannot be qualified (C++ [dcl.meaning]p1). + if (D.getCXXScopeSpec().isSet()) { + Diag(D.getIdentifierLoc(), diag::err_qualified_param_declarator) + << D.getCXXScopeSpec().getRange(); + New->setInvalidDecl(); + } + // Add the parameter declaration into this scope. S->AddDecl(New); if (II) @@ -2269,10 +2390,11 @@ Sema::DeclTy *Sema::ActOnStartOfFunctionDef(Scope *FnBodyScope, Declarator &D) { // FIXME: Diagnose arguments without names in C. } - Scope *GlobalScope = FnBodyScope->getParent(); + Scope *ParentScope = FnBodyScope->getParent(); return ActOnStartOfFunctionDef(FnBodyScope, - ActOnDeclarator(GlobalScope, D, 0)); + ActOnDeclarator(ParentScope, D, 0, + /*IsFunctionDefinition=*/true)); } Sema::DeclTy *Sema::ActOnStartOfFunctionDef(Scope *FnBodyScope, DeclTy *D) { diff --git a/lib/Sema/SemaType.cpp b/lib/Sema/SemaType.cpp index 2423e1271b..3f911f5b0c 100644 --- a/lib/Sema/SemaType.cpp +++ b/lib/Sema/SemaType.cpp @@ -533,9 +533,11 @@ QualType Sema::GetTypeForDeclarator(Declarator &D, Scope *S, unsigned Skip) { // declaration. if (FnTy->getTypeQuals() != 0 && D.getDeclSpec().getStorageClassSpec() != DeclSpec::SCS_typedef && - (D.getContext() != Declarator::MemberContext || + ((D.getContext() != Declarator::MemberContext && + (!D.getCXXScopeSpec().isSet() || + !static_cast(D.getCXXScopeSpec().getScopeRep()) + ->isCXXRecord())) || D.getDeclSpec().getStorageClassSpec() == DeclSpec::SCS_static)) { - if (D.isFunctionDeclarator()) Diag(D.getIdentifierLoc(), diag::err_invalid_qualified_function_type); else diff --git a/test/SemaCXX/nested-name-spec.cpp b/test/SemaCXX/nested-name-spec.cpp index b6032e6494..1d91c24b96 100644 --- a/test/SemaCXX/nested-name-spec.cpp +++ b/test/SemaCXX/nested-name-spec.cpp @@ -12,10 +12,19 @@ A:: ; // expected-error {{expected unqualified-id}} A::undef1::undef2 ex4; // expected-error {{no member named 'undef1'}} expected-error {{expected '=', ',', ';', 'asm', or '__attribute__' after declarator}} class C2 { - void m(); + void m(); // expected-note{{member declaration nearly matches}} + + void f(const int& parm); // expected-note{{member declaration nearly matches}} + void f(int) const; // expected-note{{member declaration nearly matches}} + void f(float); + int x; }; +void C2::m() const { } // expected-error{{out-of-line definition does not match any declaration in 'C2'}} + +void C2::f(int) { } // expected-error{{out-of-line definition does not match any declaration in 'C2'}} + void C2::m() { x = 0; } @@ -25,7 +34,7 @@ namespace B { } void f1() { - void A::Af(); // expected-error {{definition or redeclaration of 'Af' not allowed inside a function}} + void A::Af(); // expected-error {{definition or redeclaration of 'Af' not allowed inside a function}} } void f2() { @@ -73,3 +82,11 @@ void f3() { // make sure the following doesn't hit any asserts void f4(undef::C); // expected-error {{use of undeclared identifier 'undef'}} // expected-error {{expected ')'}} expected-note {{to match this '('}} // expected-error {{variable has incomplete type 'void'}} + +typedef void C2::f5(int); // expected-error{{typedef declarator cannot be qualified}} + +void f6(int A2::RC::x); // expected-error{{parameter declarator cannot be qualified}} + +int A2::RC::x; // expected-error{{non-static data member defined out-of-line}} + +void A2::CC::NC::m(); // expected-error{{out-of-line declaration of a member must be a definition}}