sepavloff updated this revision to Diff 53547. sepavloff added a comment. Updated patch according to review notes.
The patch adds diagnostics for undefined function templates, in a similar way as for template variables. These two warnings are classified into different groups. Warnings on undefined variable templates are enabled by default. This is more error-prone case, existence of PR24425 may be considered as an evidence. Warnings on undefined function templates are off by default. If it is enabled, about 80 tests require update. References to template declarations can now be printed in two ways. The usual way is to print only names, such as 'C1::C2', it is compact and convenient when the reference occurs inside template definition. The patch adds new format, in which template parameters are also printed, like 'C1<T>::C2<T1,N1>', it can be more convenient if the template is referenced from outside. http://reviews.llvm.org/D16396 Files: include/clang/AST/ASTContext.h include/clang/AST/PrettyPrinter.h include/clang/Basic/DiagnosticGroups.td include/clang/Basic/DiagnosticSemaKinds.td include/clang/Sema/Sema.h lib/AST/ASTDiagnostic.cpp lib/AST/Decl.cpp lib/Sema/SemaTemplateInstantiateDecl.cpp test/CXX/temp/temp.decls/temp.mem/p1.cpp test/OpenMP/parallel_ast_print.cpp test/OpenMP/parallel_sections_ast_print.cpp test/OpenMP/target_parallel_ast_print.cpp test/OpenMP/task_ast_print.cpp test/OpenMP/teams_ast_print.cpp test/OpenMP/threadprivate_ast_print.cpp test/SemaCXX/PR10177.cpp test/SemaCXX/undefined-internal.cpp test/SemaTemplate/undefined-template.cpp
Index: test/SemaTemplate/undefined-template.cpp =================================================================== --- /dev/null +++ test/SemaTemplate/undefined-template.cpp @@ -0,0 +1,139 @@ +// RUN: %clang_cc1 -fsyntax-only -verify -std=c++14 -Wundefined-func-template %s + +template <class T> struct C1 { + static char s_var_1; + static char s_var_2; + static void s_func_1(); + static void s_func_2(); + void meth_1(); + void meth_2(); + template <class T1> static char s_tvar_2; + template <class T1> static void s_tfunc_2(); + template<typename T1> struct C2 { + static char s_var_2; + static void s_func_2(); + void meth_2(); + template <class T2> static char s_tvar_2; + template <class T2> void tmeth_2(); + }; +}; + +extern template char C1<int>::s_var_2; +extern template void C1<int>::s_func_2(); +extern template void C1<int>::meth_2(); +extern template char C1<int>::s_tvar_2<char>; +extern template void C1<int>::s_tfunc_2<char>(); +extern template void C1<int>::C2<long>::s_var_2; +extern template void C1<int>::C2<long>::s_func_2(); +extern template void C1<int>::C2<long>::meth_2(); +extern template char C1<int>::C2<long>::s_tvar_2<char>; +extern template void C1<int>::C2<long>::tmeth_2<char>(); + +char func_01() { + return C1<int>::s_var_2; +} + +char func_02() { + return C1<int>::s_var_1; // expected-warning{{instantiation of variable 'C1<int>::s_var_1' required here, but 'C1<T>::s_var_1' is not defined}} + // expected-note@-1{{add an explicit instantiation declaration to suppress this warning if 'C1<int>::s_var_1' is explicitly instantiated in another translation unit}} +} + +char func_03() { + return C1<char>::s_var_2; // expected-warning{{instantiation of variable 'C1<char>::s_var_2' required here, but 'C1<T>::s_var_2' is not defined}} + // expected-note@-1{{add an explicit instantiation declaration to suppress this warning if 'C1<char>::s_var_2' is explicitly instantiated in another translation unit}} +} + +void func_04() { + C1<int>::s_func_1(); // expected-warning{{instantiation of function 'C1<int>::s_func_1' required here, but 'C1<T>::s_func_1' is not defined}} + // expected-note@-1{{add an explicit instantiation declaration to suppress this warning if 'C1<int>::s_func_1' is explicitly instantiated in another translation unit}} +} + +void func_05() { + C1<int>::s_func_2(); +} + +void func_06() { + C1<char>::s_func_2(); // expected-warning{{instantiation of function 'C1<char>::s_func_2' required here, but 'C1<T>::s_func_2' is not defined}} + // expected-note@-1{{add an explicit instantiation declaration to suppress this warning if 'C1<char>::s_func_2' is explicitly instantiated in another translation unit}} +} + +void func_07(C1<int> *x) { + x->meth_1(); // expected-warning{{instantiation of function 'C1<int>::meth_1' required here, but 'C1<T>::meth_1' is not defined}} + // expected-note@-1{{add an explicit instantiation declaration to suppress this warning if 'C1<int>::meth_1' is explicitly instantiated in another translation unit}} +} + +void func_08(C1<int> *x) { + x->meth_2(); +} + +void func_09(C1<char> *x) { + x->meth_1(); // expected-warning{{instantiation of function 'C1<char>::meth_1' required here, but 'C1<T>::meth_1' is not defined}} + // expected-note@-1{{add an explicit instantiation declaration to suppress this warning if 'C1<char>::meth_1' is explicitly instantiated in another translation unit}} +} + +char func_10() { + return C1<int>::s_tvar_2<char>; +} + +char func_11() { + return C1<int>::s_tvar_2<long>; // expected-warning{{instantiation of variable 'C1<int>::s_tvar_2<long>' required here, but 'C1<T>::s_tvar_2<T1>' is not defined}} + // expected-note@-1{{add an explicit instantiation declaration to suppress this warning if 'C1<int>::s_tvar_2<long>' is explicitly instantiated in another translation unit}} +} + +void func_12() { + C1<int>::s_tfunc_2<char>(); +} + +void func_13() { + C1<int>::s_tfunc_2<long>(); // expected-warning{{instantiation of function 'C1<int>::s_tfunc_2<long>' required here, but 'C1<T>::s_tfunc_2<T1>' is not defined}} + // expected-note@-1{{add an explicit instantiation declaration to suppress this warning if 'C1<int>::s_tfunc_2<long>' is explicitly instantiated in another translation unit}} +} + +char func_14() { + return C1<int>::C2<long>::s_var_2; +} + +char func_15() { + return C1<int>::C2<char>::s_var_2; //expected-warning {{instantiation of variable 'C1<int>::C2<char>::s_var_2' required here, but 'C1<T>::C2<T1>::s_var_2' is not defined}} + // expected-note@-1{{add an explicit instantiation declaration to suppress this warning if 'C1<int>::C2<char>::s_var_2' is explicitly instantiated in another translation unit}} +} + +void func_16() { + C1<int>::C2<long>::s_func_2(); +} + +void func_17() { + C1<int>::C2<char>::s_func_2(); // expected-warning{{instantiation of function 'C1<int>::C2<char>::s_func_2' required here, but 'C1<T>::C2<T1>::s_func_2' is not defined}} + // expected-note@-1{{add an explicit instantiation declaration to suppress this warning if 'C1<int>::C2<char>::s_func_2' is explicitly instantiated in another translation unit}} +} + +void func_18(C1<int>::C2<long> *x) { + x->meth_2(); +} + +void func_19(C1<int>::C2<char> *x) { + x->meth_2(); // expected-warning{{instantiation of function 'C1<int>::C2<char>::meth_2' required here, but 'C1<T>::C2<T1>::meth_2' is not defined}} + // expected-note@-1{{add an explicit instantiation declaration to suppress this warning if 'C1<int>::C2<char>::meth_2' is explicitly instantiated in another translation unit}} +} + +char func_20() { + return C1<int>::C2<long>::s_tvar_2<char>; +} + +char func_21() { + return C1<int>::C2<long>::s_tvar_2<long>; // expected-warning{{instantiation of variable 'C1<int>::C2<long>::s_tvar_2<long>' required here, but 'C1<T>::C2<T1>::s_tvar_2<T2>' is not defined}} + // expected-note@-1{{add an explicit instantiation declaration to suppress this warning if 'C1<int>::C2<long>::s_tvar_2<long>' is explicitly instantiated in another translation unit}} +} + +void func_22(C1<int>::C2<long> *x) { + x->tmeth_2<char>(); +} + +void func_23(C1<int>::C2<long> *x) { + x->tmeth_2<int>(); // expected-warning{{instantiation of function 'C1<int>::C2<long>::tmeth_2<int>' required here, but 'C1<T>::C2<T1>::tmeth_2<T2>' is not defined}} + // expected-note@-1{{add an explicit instantiation declaration to suppress this warning if 'C1<int>::C2<long>::tmeth_2<int>' is explicitly instantiated in another translation unit}} +} + +int main() { + return 0; +} Index: test/SemaCXX/undefined-internal.cpp =================================================================== --- test/SemaCXX/undefined-internal.cpp +++ test/SemaCXX/undefined-internal.cpp @@ -82,6 +82,7 @@ static int var; // expected-warning {{variable 'test5::B<test5::(anonymous namespace)::A>::var' has internal linkage but is not defined}} static void foo(); // expected-warning {{function 'test5::B<test5::(anonymous namespace)::A>::foo' has internal linkage but is not defined}} }; + extern template int B<A>::var; void test() { B<A>::var = 0; // expected-note {{used here}} Index: test/SemaCXX/PR10177.cpp =================================================================== --- test/SemaCXX/PR10177.cpp +++ test/SemaCXX/PR10177.cpp @@ -54,6 +54,7 @@ namespace { template<typename> extern int n; } template<typename T> int g() { return n<int>; } +namespace { extern template int n<int>; } #endif Index: test/OpenMP/threadprivate_ast_print.cpp =================================================================== --- test/OpenMP/threadprivate_ast_print.cpp +++ test/OpenMP/threadprivate_ast_print.cpp @@ -69,4 +69,5 @@ return (foo<int>()); } +extern template int ST<int>::m; #endif Index: test/OpenMP/teams_ast_print.cpp =================================================================== --- test/OpenMP/teams_ast_print.cpp +++ test/OpenMP/teams_ast_print.cpp @@ -109,4 +109,6 @@ return tmain<int, 5>(b, &b) + tmain<long, 1>(x, &x); } +extern template int S<int>::TS; +extern template long S<long>::TS; #endif Index: test/OpenMP/task_ast_print.cpp =================================================================== --- test/OpenMP/task_ast_print.cpp +++ test/OpenMP/task_ast_print.cpp @@ -149,4 +149,7 @@ return tmain<int, 5>(b, &b) + tmain<long, 1>(x, &x); } +extern template int S<int>::TS; +extern template long S<long>::TS; + #endif Index: test/OpenMP/target_parallel_ast_print.cpp =================================================================== --- test/OpenMP/target_parallel_ast_print.cpp +++ test/OpenMP/target_parallel_ast_print.cpp @@ -227,4 +227,7 @@ return tmain<int, 5>(argc, &argc) + tmain<char, 1>(argv[0][0], argv[0]); } +extern template int S<int>::TS; +extern template char S<char>::TS; + #endif Index: test/OpenMP/parallel_sections_ast_print.cpp =================================================================== --- test/OpenMP/parallel_sections_ast_print.cpp +++ test/OpenMP/parallel_sections_ast_print.cpp @@ -141,4 +141,7 @@ return tmain<int, 5>(b, &b) + tmain<long, 1>(x, &x); } +template<typename T> +T S<T>::TS = 0; + #endif Index: test/OpenMP/parallel_ast_print.cpp =================================================================== --- test/OpenMP/parallel_ast_print.cpp +++ test/OpenMP/parallel_ast_print.cpp @@ -227,4 +227,7 @@ } } +template<typename T> +T S<T>::TS = 0; + #endif Index: test/CXX/temp/temp.decls/temp.mem/p1.cpp =================================================================== --- test/CXX/temp/temp.decls/temp.mem/p1.cpp +++ test/CXX/temp/temp.decls/temp.mem/p1.cpp @@ -10,6 +10,7 @@ } }; }; +extern template bool A<bool>::cond; int foo() { A<bool>::cond = true; Index: lib/Sema/SemaTemplateInstantiateDecl.cpp =================================================================== --- lib/Sema/SemaTemplateInstantiateDecl.cpp +++ lib/Sema/SemaTemplateInstantiateDecl.cpp @@ -3530,7 +3530,8 @@ void Sema::InstantiateFunctionDefinition(SourceLocation PointOfInstantiation, FunctionDecl *Function, bool Recursive, - bool DefinitionRequired) { + bool DefinitionRequired, + bool AtEndOfTU) { if (Function->isInvalidDecl() || Function->isDefined()) return; @@ -3604,6 +3605,15 @@ assert(!Recursive); PendingInstantiations.push_back( std::make_pair(Function, PointOfInstantiation)); + } else if (Function->getTemplateSpecializationKind() + == TSK_ImplicitInstantiation) { + if (AtEndOfTU && !getDiagnostics().hasErrorOccurred()) { + Diag(PointOfInstantiation, diag::warn_func_template_missing) + << Function << PatternDecl; + if (getLangOpts().CPlusPlus11) + Diag(PointOfInstantiation, diag::note_inst_declaration_hint) + << Function; + } } return; @@ -3951,7 +3961,7 @@ void Sema::InstantiateVariableDefinition(SourceLocation PointOfInstantiation, VarDecl *Var, bool Recursive, - bool DefinitionRequired) { + bool DefinitionRequired, bool AtEndOfTU) { if (Var->isInvalidDecl()) return; @@ -4083,6 +4093,15 @@ == TSK_ExplicitInstantiationDefinition) { PendingInstantiations.push_back( std::make_pair(Var, PointOfInstantiation)); + } else if (Var->getTemplateSpecializationKind() + == TSK_ImplicitInstantiation) { + // Warn about missing definition at the end of translation unit. + if (AtEndOfTU && !getDiagnostics().hasErrorOccurred()) { + Diag(PointOfInstantiation, diag::warn_var_template_missing) + << Var << PatternDecl; + if (getLangOpts().CPlusPlus11) + Diag(PointOfInstantiation, diag::note_inst_declaration_hint) << Var; + } } return; @@ -4852,7 +4871,7 @@ bool DefinitionRequired = Function->getTemplateSpecializationKind() == TSK_ExplicitInstantiationDefinition; InstantiateFunctionDefinition(/*FIXME:*/Inst.second, Function, true, - DefinitionRequired); + DefinitionRequired, true); continue; } @@ -4893,7 +4912,7 @@ // Instantiate static data member definitions or variable template // specializations. InstantiateVariableDefinition(/*FIXME:*/ Inst.second, Var, true, - DefinitionRequired); + DefinitionRequired, true); } } Index: lib/AST/Decl.cpp =================================================================== --- lib/AST/Decl.cpp +++ lib/AST/Decl.cpp @@ -1404,6 +1404,73 @@ printQualifiedName(OS, getASTContext().getPrintingPolicy()); } +namespace { + +/// \brief Class for printing template parameters in diagnostic messages. +/// +/// Allows a bit more readable reference to templates in the case of several +/// template levels. For instance, a reference to the template function \c meth +/// in the code: +/// +/// \code +/// template <class T, int Val> struct C1 { +/// template<typename T1, template<typename TT> class TC> struct C2 { +/// template <class T2> static void meth(); +/// }; +/// }; +/// \endcode +/// +/// is printed as C1<T,N>::C2<T1,TT<>>::meth<T2> rather than C1::C2::meth. +/// +class TemplParamPrinter { + unsigned TypePNo; ///< Sequential number of type parameter. + unsigned NonTypePNo; ///< Sequential number of non-type parameter. + unsigned TemplPNo; ///< Sequential number of template parameter. + bool Enabled; ///< If true, parameters are printed. + +public: + TemplParamPrinter(bool E) + : TypePNo(0), NonTypePNo(0), TemplPNo(0), Enabled(E) {} + + void printTemplateParameters(raw_ostream &OS, const TemplateDecl *Templ) { + if (!Enabled) + return; + OS << "<"; + TemplateParameterList *TPL = Templ->getTemplateParameters(); + for (unsigned i = 0, e = TPL->size(); i != e; ++i) { + if (i != 0) + OS << ","; + const Decl *Param = TPL->getParam(i); + if (auto *TP = dyn_cast<TemplateTypeParmDecl>(Param)) { + if (TP->isParameterPack()) + OS << "..."; + OS << 'T'; + if (TypePNo) + OS << TypePNo; + TypePNo++; + } else if (auto *NT = dyn_cast<NonTypeTemplateParmDecl>(Param)) { + if (NT->isParameterPack()) + OS << "..."; + OS << 'N'; + if (NonTypePNo) + OS << NonTypePNo; + NonTypePNo++; + } else if (auto *TT = dyn_cast<TemplateTemplateParmDecl>(Param)) { + if (TT->isParameterPack()) + OS << "..."; + OS << "TT"; + if (TemplPNo) + OS << TemplPNo; + OS << "<>"; + TemplPNo++; + } + } + OS << ">"; + } +}; + +} + void NamedDecl::printQualifiedName(raw_ostream &OS, const PrintingPolicy &P) const { const DeclContext *Ctx = getDeclContext(); @@ -1422,6 +1489,7 @@ Ctx = Ctx->getParent(); } + TemplParamPrinter TPPrinter(P.PrintTemplateParameters); for (ContextsTy::reverse_iterator I = Contexts.rbegin(), E = Contexts.rend(); I != E; ++I) { if (const auto *Spec = dyn_cast<ClassTemplateSpecializationDecl>(*I)) { @@ -1444,8 +1512,12 @@ } else if (const auto *RD = dyn_cast<RecordDecl>(*I)) { if (!RD->getIdentifier()) OS << "(anonymous " << RD->getKindName() << ')'; - else + else { OS << *RD; + if (auto *CXXRD = dyn_cast<CXXRecordDecl>(RD)) + if (ClassTemplateDecl *Templ = CXXRD->getDescribedClassTemplate()) + TPPrinter.printTemplateParameters(OS, Templ); + } } else if (const auto *FD = dyn_cast<FunctionDecl>(*I)) { const FunctionProtoType *FT = nullptr; if (FD->hasWrittenPrototype()) @@ -1482,9 +1554,16 @@ OS << "::"; } - if (getDeclName()) - OS << *this; - else + if (getDeclName()) { + printName(OS); + if (auto FD = dyn_cast<FunctionDecl>(this)) { + if (const FunctionTemplateDecl *FTD = FD->getDescribedFunctionTemplate()) + TPPrinter.printTemplateParameters(OS, FTD); + } else if (auto VD = dyn_cast<VarDecl>(this)) { + if (const VarTemplateDecl *VTD = VD->getDescribedVarTemplate()) + TPPrinter.printTemplateParameters(OS, VTD); + } + } else OS << "(anonymous)"; } Index: lib/AST/ASTDiagnostic.cpp =================================================================== --- lib/AST/ASTDiagnostic.cpp +++ lib/AST/ASTDiagnostic.cpp @@ -318,6 +318,18 @@ bool PrintFromType, bool ElideType, bool ShowColors, raw_ostream &OS); +namespace { +/// \brief Helper class to restore PrintingPolicy::PrintTemplateParameters, when +/// it temporary changes. +struct SavedPrintTemplParamFlag { + PrintingPolicy &PPolicy; + bool SavedValue; + SavedPrintTemplParamFlag(PrintingPolicy &P) : PPolicy(P), + SavedValue(P.PrintTemplateParameters) {} + ~SavedPrintTemplParamFlag() { PPolicy.PrintTemplateParameters = SavedValue; } +}; +} + void clang::FormatASTNodeDiagnosticArgument( DiagnosticsEngine::ArgumentKind Kind, intptr_t Val, @@ -385,9 +397,13 @@ } case DiagnosticsEngine::ak_nameddecl: { bool Qualified; + SavedPrintTemplParamFlag PrintFlag(Context.getPrintingPolicy()); if (Modifier == "q" && Argument.empty()) Qualified = true; - else { + else if (Modifier == "qt" && Argument.empty()) { + Qualified = true; + Context.getPrintingPolicy().PrintTemplateParameters = true; + } else { assert(Modifier.empty() && Argument.empty() && "Invalid modifier for NamedDecl* argument"); Qualified = false; Index: include/clang/Sema/Sema.h =================================================================== --- include/clang/Sema/Sema.h +++ include/clang/Sema/Sema.h @@ -7171,7 +7171,8 @@ void InstantiateFunctionDefinition(SourceLocation PointOfInstantiation, FunctionDecl *Function, bool Recursive = false, - bool DefinitionRequired = false); + bool DefinitionRequired = false, + bool AtEndOfTU = false); VarTemplateSpecializationDecl *BuildVarTemplateInstantiation( VarTemplateDecl *VarTemplate, VarDecl *FromVar, const TemplateArgumentList &TemplateArgList, @@ -7195,7 +7196,8 @@ const MultiLevelTemplateArgumentList &TemplateArgs); void InstantiateVariableDefinition(SourceLocation PointOfInstantiation, VarDecl *Var, bool Recursive = false, - bool DefinitionRequired = false); + bool DefinitionRequired = false, + bool AtEndOfTU = false); void InstantiateStaticDataMemberDefinition( SourceLocation PointOfInstantiation, VarDecl *Var, Index: include/clang/Basic/DiagnosticSemaKinds.td =================================================================== --- include/clang/Basic/DiagnosticSemaKinds.td +++ include/clang/Basic/DiagnosticSemaKinds.td @@ -3883,7 +3883,15 @@ "in instantiation of template type alias %0 requested here">; def note_template_exception_spec_instantiation_here : Note< "in instantiation of exception specification for %0 requested here">; - +def warn_var_template_missing : Warning<"instantiation of variable %q0 " + "required here, but %qt1 is not defined">, InGroup<UndefinedVarTemplate>; +def warn_func_template_missing : Warning<"instantiation of function %q0 " + "required here, but %qt1 is not defined">, InGroup<UndefinedFuncTemplate>, + DefaultIgnore; +def note_inst_declaration_hint : Note<"add an explicit instantiation " + "declaration to suppress this warning if %q0 is explicitly instantiated in " + "another translation unit">; + def note_default_arg_instantiation_here : Note< "in instantiation of default argument for '%0' required here">; def note_default_function_arg_instantiation_here : Note< Index: include/clang/Basic/DiagnosticGroups.td =================================================================== --- include/clang/Basic/DiagnosticGroups.td +++ include/clang/Basic/DiagnosticGroups.td @@ -75,6 +75,8 @@ def GNUDesignator : DiagGroup<"gnu-designator">; def GNUStringLiteralOperatorTemplate : DiagGroup<"gnu-string-literal-operator-template">; +def UndefinedVarTemplate : DiagGroup<"undefined-var-template">; +def UndefinedFuncTemplate : DiagGroup<"undefined-func-template">; def DeleteIncomplete : DiagGroup<"delete-incomplete">; def DeleteNonVirtualDtor : DiagGroup<"delete-non-virtual-dtor">; Index: include/clang/AST/PrettyPrinter.h =================================================================== --- include/clang/AST/PrettyPrinter.h +++ include/clang/AST/PrettyPrinter.h @@ -42,6 +42,7 @@ ConstantArraySizeAsWritten(false), AnonymousTagLocations(true), SuppressStrongLifetime(false), SuppressLifetimeQualifiers(false), SuppressTemplateArgsInCXXConstructors(false), + PrintTemplateParameters(false), Bool(LO.Bool), TerseOutput(false), PolishForDeclaration(false), Half(LO.Half), MSWChar(LO.MicrosoftExt && !LO.WChar), IncludeNewlines(true), MSVCFormatting(false) { } @@ -143,6 +144,10 @@ /// constructors. unsigned SuppressTemplateArgsInCXXConstructors : 1; + /// \brief When true, a template declaration name is printed with parameters, + /// e.g. 'C1<T>::C2<T1,N1>' instead of 'C1::C2'. + bool PrintTemplateParameters : 1; + /// \brief Whether we can use 'bool' rather than '_Bool', even if the language /// doesn't actually have 'bool' (because, e.g., it is defined as a macro). unsigned Bool : 1; Index: include/clang/AST/ASTContext.h =================================================================== --- include/clang/AST/ASTContext.h +++ include/clang/AST/ASTContext.h @@ -549,6 +549,7 @@ const clang::PrintingPolicy &getPrintingPolicy() const { return PrintingPolicy; } + clang::PrintingPolicy &getPrintingPolicy() { return PrintingPolicy; } void setPrintingPolicy(const clang::PrintingPolicy &Policy) { PrintingPolicy = Policy;
_______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits