https://github.com/diohabara created https://github.com/llvm/llvm-project/pull/187896
## Background When `-Wundefined-var-template` or `-Wundefined-func-template` fires, users want to know the concrete syntax to add. However, the current diagnostic only says "add an explicit instantiation declaration" without showing what to write, and the syntax is hard to guess. This new diagnostic would be especially helpful for users migrating from GCC or MSVC, where the explicit instantiation syntax differs. Fixes: https://github.com/llvm/llvm-project/issues/62121 ## Changes - [x] extern template declaration (`extern template int S<int>::value;`) - [ ] explicit specialization (`template <> int S<int>::value;`) - [ ] generic out-of-class definition (`template <typename T> int S<T>::value;`) ## Before/After Before: ``` # TODO ``` After: ``` # TODO ``` From 3349dabfc52cd6f440c3bc64ed5e453476d4dd47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8D=8Ddiohabara=E5=8D=8D?= <[email protected]> Date: Sat, 21 Mar 2026 18:52:57 +0900 Subject: [PATCH] [clang][diagnostics] suggest extern template syntax for -Wundefined-var-template and -Wundefined-func-template --- .../clang/Basic/DiagnosticSemaKinds.td | 2 + .../lib/Sema/SemaTemplateInstantiateDecl.cpp | 68 ++++++++++++++++++- ...antiation.diagnose_on_undefined_entity.cpp | 4 ++ clang/test/SemaCXX/cxx1z-ast-print.cpp | 4 +- .../instantiate-pure-virtual-function.cpp | 6 +- .../test/SemaTemplate/undefined-template.cpp | 13 ++++ 6 files changed, 93 insertions(+), 4 deletions(-) diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index d4d09a8ecef36..d8647f6d426c9 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -5954,6 +5954,8 @@ def note_unreachable_template_decl 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_inst_declaration_example : Note< + "e.g., '%0'">; def note_evaluating_exception_spec_here : Note< "in evaluation of exception specification for %q0 needed here">; diff --git a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp index cc24e03e77c07..f221b180e2bdd 100644 --- a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp +++ b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp @@ -5636,9 +5636,58 @@ void Sema::InstantiateFunctionDefinition(SourceLocation PointOfInstantiation, diag::note_unreachable_template_decl); } else { Diag(PatternDecl->getLocation(), diag::note_forward_template_decl); - if (getLangOpts().CPlusPlus11) + if (getLangOpts().CPlusPlus11) { Diag(PointOfInstantiation, diag::note_inst_declaration_hint) << Function; + // Suggest the exact extern template declaration syntax. + // Note: for functions with complex declarator return types + // (e.g., function pointers), the suggestion may not be + // perfectly formatted, but these cases are rare in practice. + std::string Suggestion; + { + llvm::raw_string_ostream OS(Suggestion); + OS << "extern template "; + // Constructors, destructors, and conversion operators have no + // separate return type in the declaration syntax. + if (!isa<CXXConstructorDecl>(Function) && + !isa<CXXDestructorDecl>(Function) && + !isa<CXXConversionDecl>(Function)) { + Function->getReturnType().print(OS, getPrintingPolicy()); + OS << " "; + } + Function->getNameForDiagnostic(OS, getPrintingPolicy(), + /*Qualified=*/true); + OS << "("; + for (unsigned I = 0, N = Function->getNumParams(); I != N; ++I) { + if (I > 0) + OS << ", "; + Function->getParamDecl(I)->getType().print( + OS, getPrintingPolicy()); + } + if (Function->isVariadic()) { + if (Function->getNumParams() > 0) + OS << ", "; + OS << "..."; + } + OS << ")"; + if (const auto *MD = dyn_cast<CXXMethodDecl>(Function)) { + if (MD->getMethodQualifiers().hasConst()) + OS << " const"; + if (MD->getMethodQualifiers().hasVolatile()) + OS << " volatile"; + if (MD->getRefQualifier() == RQ_LValue) + OS << " &"; + else if (MD->getRefQualifier() == RQ_RValue) + OS << " &&"; + } + if (auto EST = Function->getExceptionSpecType(); + EST == EST_BasicNoexcept) + OS << " noexcept"; + OS << ";"; + } + Diag(PointOfInstantiation, diag::note_inst_declaration_example) + << Suggestion; + } } } } @@ -6379,8 +6428,23 @@ void Sema::InstantiateVariableDefinition(SourceLocation PointOfInstantiation, Diag(PointOfInstantiation, diag::warn_var_template_missing) << Var; Diag(PatternDecl->getLocation(), diag::note_forward_template_decl); - if (getLangOpts().CPlusPlus11) + if (getLangOpts().CPlusPlus11) { Diag(PointOfInstantiation, diag::note_inst_declaration_hint) << Var; + // Suggest the exact extern template declaration syntax. + std::string Suggestion; + { + llvm::raw_string_ostream OS(Suggestion); + OS << "extern template "; + std::string QualName; + llvm::raw_string_ostream NameOS(QualName); + Var->getNameForDiagnostic(NameOS, getPrintingPolicy(), + /*Qualified=*/true); + Var->getType().print(OS, getPrintingPolicy(), QualName); + OS << ";"; + } + Diag(PointOfInstantiation, diag::note_inst_declaration_example) + << Suggestion; + } } return; } diff --git a/clang/test/SemaCXX/attr-exclude_from_explicit_instantiation.diagnose_on_undefined_entity.cpp b/clang/test/SemaCXX/attr-exclude_from_explicit_instantiation.diagnose_on_undefined_entity.cpp index 24e24221939fa..b79e5983fe6be 100644 --- a/clang/test/SemaCXX/attr-exclude_from_explicit_instantiation.diagnose_on_undefined_entity.cpp +++ b/clang/test/SemaCXX/attr-exclude_from_explicit_instantiation.diagnose_on_undefined_entity.cpp @@ -24,13 +24,17 @@ void use() { foo.non_static_member_function(); // expected-warning{{instantiation of function 'Foo<int>::non_static_member_function' required here, but no definition is available}} // expected-note@-1 {{add an explicit instantiation}} + // expected-note@-2 {{e.g., 'extern template}} Foo<int>::static_member_function(); // expected-warning{{instantiation of function 'Foo<int>::static_member_function' required here, but no definition is available}} // expected-note@-1 {{add an explicit instantiation}} + // expected-note@-2 {{e.g., 'extern template}} (void)Foo<int>::static_data_member; // expected-warning{{instantiation of variable 'Foo<int>::static_data_member' required here, but no definition is available}} // expected-note@-1 {{add an explicit instantiation}} + // expected-note@-2 {{e.g., 'extern template}} Foo<int>::nested::static_member_function(); // expected-warning{{instantiation of function 'Foo<int>::nested::static_member_function' required here, but no definition is available}} // expected-note@-1 {{add an explicit instantiation}} + // expected-note@-2 {{e.g., 'extern template}} } diff --git a/clang/test/SemaCXX/cxx1z-ast-print.cpp b/clang/test/SemaCXX/cxx1z-ast-print.cpp index a6ba149e974ef..642b7ceff935e 100644 --- a/clang/test/SemaCXX/cxx1z-ast-print.cpp +++ b/clang/test/SemaCXX/cxx1z-ast-print.cpp @@ -7,5 +7,7 @@ struct TypeSuffix { // CHECK: int k = TypeSuffix().x<0L> + TypeSuffix().y<0L>; int k = TypeSuffix().x<0L> + TypeSuffix().y<0L>; // expected-warning {{instantiation of variable 'TypeSuffix::x<0>' required here, but no definition is available}} \ // expected-note {{add an explicit instantiation declaration to suppress this warning if 'TypeSuffix::x<0>' is explicitly instantiated in another translation unit}} \ + // expected-note {{e.g., 'extern template}} \ // expected-warning {{instantiation of variable 'TypeSuffix::y<0L>' required here, but no definition is available}} \ - // expected-note {{add an explicit instantiation declaration to suppress this warning if 'TypeSuffix::y<0L>' is explicitly instantiated in another translation unit}} + // expected-note {{add an explicit instantiation declaration to suppress this warning if 'TypeSuffix::y<0L>' is explicitly instantiated in another translation unit}} \ + // expected-note {{e.g., 'extern template}} diff --git a/clang/test/SemaTemplate/instantiate-pure-virtual-function.cpp b/clang/test/SemaTemplate/instantiate-pure-virtual-function.cpp index caec42b6b77f9..548f7662b0af5 100644 --- a/clang/test/SemaTemplate/instantiate-pure-virtual-function.cpp +++ b/clang/test/SemaTemplate/instantiate-pure-virtual-function.cpp @@ -23,6 +23,7 @@ namespace call_pure_virtual_function_from_virtual { public: const void foo(const T &) { B::bar(1); } // expected-warning {{instantiation of function 'call_pure_virtual_function_from_virtual::B<int>::bar' required here, but no definition is available}} // expected-note@-1 {{add an explicit instantiation declaration to suppress this warning if 'call_pure_virtual_function_from_virtual::B<int>::bar' is explicitly instantiated in another translation unit}} + // expected-note@-2 {{e.g., 'extern template}} virtual const void bar(unsigned int) = 0; // expected-note {{forward declaration of template entity is here}} }; @@ -54,7 +55,10 @@ namespace non_pure_virtual_function { // expected-note@-3 {{add an explicit instantiation declaration to suppress this warning if 'non_pure_virtual_function::B<int>::bar' is explicitly instantiated in another translation unit}} // expected-note@-4 {{add an explicit instantiation declaration to suppress this warning if 'non_pure_virtual_function::B<int>::bar' is explicitly instantiated in another translation unit}} // expected-note@-5 {{add an explicit instantiation declaration to suppress this warning if 'non_pure_virtual_function::B<int>::bar' is explicitly instantiated in another translation unit}} -// expected-note@-6 {{used here}} +// expected-note@-6 {{e.g., 'extern template}} +// expected-note@-7 {{e.g., 'extern template}} +// expected-note@-8 {{e.g., 'extern template}} +// expected-note@-9 {{used here}} public: constexpr void bar(unsigned int) override { } diff --git a/clang/test/SemaTemplate/undefined-template.cpp b/clang/test/SemaTemplate/undefined-template.cpp index 52530e2e3909a..c26ab6c8ff03a 100644 --- a/clang/test/SemaTemplate/undefined-template.cpp +++ b/clang/test/SemaTemplate/undefined-template.cpp @@ -37,16 +37,19 @@ char func_01() { char func_02() { return C1<int>::s_var_1; // expected-warning{{instantiation of variable 'C1<int>::s_var_1' required here, but no definition is available}} // 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}} + // expected-note@-2{{e.g., 'extern template char C1<int>::s_var_1;'}} } char func_03() { return C1<char>::s_var_2; // expected-warning{{instantiation of variable 'C1<char>::s_var_2' required here, but no definition is available}} // 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}} + // expected-note@-2{{e.g., 'extern template char C1<char>::s_var_2;'}} } void func_04() { C1<int>::s_func_1(); // expected-warning{{instantiation of function 'C1<int>::s_func_1' required here, but no definition is available}} // 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}} + // expected-note@-2{{e.g., 'extern template void C1<int>::s_func_1();'}} } void func_05() { @@ -56,11 +59,13 @@ void func_05() { void func_06() { C1<char>::s_func_2(); // expected-warning{{instantiation of function 'C1<char>::s_func_2' required here, but no definition is available}} // 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}} + // expected-note@-2{{e.g., 'extern template void C1<char>::s_func_2();'}} } void func_07(C1<int> *x) { x->meth_1(); // expected-warning{{instantiation of function 'C1<int>::meth_1' required here, but no definition is available}} // expected-note@-1{{add an explicit instantiation declaration to suppress this warning if 'C1<int>::meth_1' is explicitly instantiated in another translation unit}} + // expected-note@-2{{e.g., 'extern template void C1<int>::meth_1();'}} } void func_08(C1<int> *x) { @@ -70,6 +75,7 @@ void func_08(C1<int> *x) { void func_09(C1<char> *x) { x->meth_1(); // expected-warning{{instantiation of function 'C1<char>::meth_1' required here, but no definition is available}} // expected-note@-1{{add an explicit instantiation declaration to suppress this warning if 'C1<char>::meth_1' is explicitly instantiated in another translation unit}} + // expected-note@-2{{e.g., 'extern template void C1<char>::meth_1();'}} } char func_10() { @@ -79,6 +85,7 @@ char func_10() { char func_11() { return C1<int>::s_tvar_2<long>; // expected-warning{{instantiation of variable 'C1<int>::s_tvar_2<long>' required here, but no definition is available}} // 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}} + // expected-note@-2{{e.g., 'extern template char C1<int>::s_tvar_2<long>;'}} } void func_12() { @@ -88,6 +95,7 @@ void func_12() { void func_13() { C1<int>::s_tfunc_2<long>(); // expected-warning{{instantiation of function 'C1<int>::s_tfunc_2<long>' required here, but no definition is available}} // 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}} + // expected-note@-2{{e.g., 'extern template void C1<int>::s_tfunc_2<long>();'}} } char func_14() { @@ -97,6 +105,7 @@ char func_14() { 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 no definition is available}} // 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}} + // expected-note@-2{{e.g., 'extern template char C1<int>::C2<char>::s_var_2;'}} } void func_16() { @@ -106,6 +115,7 @@ void func_16() { 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 no definition is available}} // 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}} + // expected-note@-2{{e.g., 'extern template void C1<int>::C2<char>::s_func_2();'}} } void func_18(C1<int>::C2<long> *x) { @@ -115,6 +125,7 @@ void func_18(C1<int>::C2<long> *x) { 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 no definition is available}} // 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}} + // expected-note@-2{{e.g., 'extern template void C1<int>::C2<char>::meth_2();'}} } char func_20() { @@ -124,6 +135,7 @@ char func_20() { 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 no definition is available}} // 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}} + // expected-note@-2{{e.g., 'extern template char C1<int>::C2<long>::s_tvar_2<long>;'}} } void func_22(C1<int>::C2<long> *x) { @@ -133,6 +145,7 @@ void func_22(C1<int>::C2<long> *x) { 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 no definition is available}} // 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}} + // expected-note@-2{{e.g., 'extern template void C1<int>::C2<long>::tmeth_2<int>();'}} } namespace test_24 { _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
