From 1704d3d2ea851d3406c42e9ebc24b6b9b5f8ecf3 Mon Sep 17 00:00:00 2001 From: jupblb Date: Fri, 19 Dec 2025 17:58:17 +0100 Subject: [PATCH] Fix missing symbols for template functions with enable_if Root cause: In getFunctionDisambiguator(), when handling template function instantiations, the code only handled two cases: 1. Non-templated member functions of class templates 2. Member function templates inside class templates It missed the case of member function templates in non-template classes (like Flashing::bind) and free function templates (like ToString). When getInstantiatedFromMemberTemplate() returned null, definingDecl was left pointing at the instantiated declaration, causing unstable/missing symbols. Fix: Added an else branch to use instantiatedTemplateDecl->getTemplatedDecl() directly when getInstantiatedFromMemberTemplate() returns null. --- indexer/SymbolFormatter.cc | 52 +++--- test/index/cuda/kernelcall.snapshot.cu | 2 +- test/index/functions/enable_if_templates.cc | 84 +++++++++ .../functions/enable_if_templates.snapshot.cc | 160 ++++++++++++++++++ test/index/types/types.snapshot.cc | 2 +- 5 files changed, 274 insertions(+), 26 deletions(-) create mode 100644 test/index/functions/enable_if_templates.cc create mode 100644 test/index/functions/enable_if_templates.snapshot.cc diff --git a/indexer/SymbolFormatter.cc b/indexer/SymbolFormatter.cc index 315472bb..6a0c6a5c 100644 --- a/indexer/SymbolFormatter.cc +++ b/indexer/SymbolFormatter.cc @@ -510,34 +510,38 @@ SymbolFormatter::getEnumSymbol(const clang::EnumDecl &enumDecl) { std::string_view SymbolFormatter::getFunctionDisambiguator( const clang::FunctionDecl &functionDecl, char buf[16]) { const clang::FunctionDecl *definingDecl = &functionDecl; - // clang-format off - if (functionDecl.isTemplateInstantiation()) { - // Handle non-templated member functions - if (auto *memberFnDecl = functionDecl.getInstantiatedFromMemberFunction()) { - definingDecl = memberFnDecl; - } else if (auto *templateInfo = functionDecl.getTemplateSpecializationInfo()) { - // Consider code like: - // template class C { template void f() {} }; - // void g() { C().f(); } - // ^ Emitting a reference - // - // The dance below gets to the original declaration in 3 steps: - // C.f (FunctionDecl) → C.f<$U> (FunctionTemplateDecl) - // ↓ - // C<$T>.f<$U> (FunctionDecl) ← C<$T>.f<$U> (FunctionTemplateDecl) - auto *instantiatedTemplateDecl = templateInfo->getTemplate(); - // For some reason, we end up on this code path for overloaded - // literal operators. In that case, uninstantiatedTemplateDecl - // can be null. - if (auto *uninstantiatedTemplateDecl = instantiatedTemplateDecl->getInstantiatedFromMemberTemplate()) { - definingDecl = uninstantiatedTemplateDecl->getTemplatedDecl(); - } + if (functionDecl.isTemplateInstantiation()) { + // Handle non-templated member functions of class templates + if (auto *memberFnDecl = functionDecl.getInstantiatedFromMemberFunction()) { + definingDecl = memberFnDecl; + } else if (auto *templateInfo = + functionDecl.getTemplateSpecializationInfo()) { + // Consider code like: + // template class C { template void f() {} }; + // void g() { C().f(); } + // ^ Emitting a reference + // + // The dance below gets to the original declaration in 3 steps: + // C.f (FunctionDecl) → C.f<$U> (FunctionTemplateDecl) + // ↓ + // C<$T>.f<$U> (FunctionDecl) ← C<$T>.f<$U> (FunctionTemplateDecl) + auto *instantiatedTemplateDecl = templateInfo->getTemplate(); + // For some reason, we end up on this code path for overloaded + // literal operators. In that case, uninstantiatedTemplateDecl + // can be null. + if (auto *uninstantiatedTemplateDecl = + instantiatedTemplateDecl->getInstantiatedFromMemberTemplate()) { + definingDecl = uninstantiatedTemplateDecl->getTemplatedDecl(); + } else { + // For free function templates or member function templates that are + // not themselves inside a class template, use the template's decl + // directly to get the uninstantiated type signature. + definingDecl = instantiatedTemplateDecl->getTemplatedDecl(); } } - // clang-format on + } // 64-bit hash in hex should take 16 characters at most. auto typeString = definingDecl->getType().getCanonicalType().getAsString(); - // char buf[16] = {0}; auto *end = fmt::format_to(buf, "{:x}", HashValue::forText(typeString)); return std::string_view{buf, end}; } diff --git a/test/index/cuda/kernelcall.snapshot.cu b/test/index/cuda/kernelcall.snapshot.cu index cb05b1a1..cfff9bcb 100644 --- a/test/index/cuda/kernelcall.snapshot.cu +++ b/test/index/cuda/kernelcall.snapshot.cu @@ -151,7 +151,7 @@ } void e() { d0(1); } // ^ definition [..] b#e(49f6e7a06ebc5aa8). -// ^^ reference [..] b#d0(d4f767463ce0a6b3). +// ^^ reference [..] b#d0(9b289cee16747614). }; namespace x { diff --git a/test/index/functions/enable_if_templates.cc b/test/index/functions/enable_if_templates.cc new file mode 100644 index 00000000..3e434cdc --- /dev/null +++ b/test/index/functions/enable_if_templates.cc @@ -0,0 +1,84 @@ +// Test for member function templates with enable_if in non-template classes, +// and free function templates with enable_if. These patterns were previously +// broken because getFunctionDisambiguator didn't handle the case where +// getInstantiatedFromMemberTemplate() returns null for such templates. + +namespace std { +template +struct enable_if {}; + +template +struct enable_if { + using type = T; +}; + +template +struct is_integral { + static constexpr bool value = false; +}; + +template <> +struct is_integral { + static constexpr bool value = true; +}; + +template +struct is_enum { + static constexpr bool value = false; +}; +} // namespace std + +// Issue 1: Member function template with enable_if in a non-template class +class Widget { +public: + template ::value, int>::type = 0> + void process(T value) { + (void)value; + } +}; + +// Issue 2: Free function template with enable_if +template ::value, bool>::type = false> +T convert(int x) { + return static_cast(x); +} + +// Issue 3: Member call through a pointer wrapper (simplified unique_ptr) +template +class Ptr { + T *p; + +public: + Ptr(T *ptr) : p(ptr) {} + T *operator->() const { return p; } +}; + +class ThreadLoop { +public: + bool isHealthy() const { return true; } +}; + +class ThreadPool { + Ptr mThread; + +public: + ThreadPool() : mThread(new ThreadLoop()) {} + + bool checkHealth() const { + return mThread->isHealthy(); + } +}; + +void test() { + Widget w; + w.process(42); + w.process(100); + + auto val = convert(5); + (void)val; + + ThreadPool pool; + (void)pool.checkHealth(); +} diff --git a/test/index/functions/enable_if_templates.snapshot.cc b/test/index/functions/enable_if_templates.snapshot.cc new file mode 100644 index 00000000..254e5264 --- /dev/null +++ b/test/index/functions/enable_if_templates.snapshot.cc @@ -0,0 +1,160 @@ + // Test for member function templates with enable_if in non-template classes, +//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ definition [..] `/enable_if_templates.cc`/ + // and free function templates with enable_if. These patterns were previously + // broken because getFunctionDisambiguator didn't handle the case where + // getInstantiatedFromMemberTemplate() returns null for such templates. + + namespace std { +// ^^^ definition [..] std/ + template +// ^ definition local 0 +// ^ definition local 1 + struct enable_if {}; +// ^^^^^^^^^ definition [..] std/enable_if# + + template +// ^ definition local 2 + struct enable_if { +// ^^^^^^^^^ definition [..] std/enable_if# +// ^ reference local 2 + using type = T; +// ^^^^ definition [..] std/enable_if#type# +// ^ reference local 2 + }; + + template +// ^ definition local 3 + struct is_integral { +// ^^^^^^^^^^^ definition [..] std/is_integral# + static constexpr bool value = false; +// ^^^^^ definition [..] std/is_integral#value. + }; + + template <> + struct is_integral { +// ^^^^^^^^^^^ reference [..] std/is_integral# +// ^^^^^^^^^^^ definition [..] std/is_integral# + static constexpr bool value = true; +// ^^^^^ definition [..] std/is_integral#value. + }; + + template +// ^ definition local 4 + struct is_enum { +// ^^^^^^^ definition [..] std/is_enum# + static constexpr bool value = false; +// ^^^^^ definition [..] std/is_enum#value. + }; + } // namespace std + + // Issue 1: Member function template with enable_if in a non-template class + class Widget { +// ^^^^^^ definition [..] Widget# + public: + template ::value, int>::type = 0> +// ^^^ reference [..] std/ +// ^^^^^^^^^ reference [..] std/enable_if# + void process(T value) { +// ^^^^^^^ definition [..] Widget#process(9b289cee16747614). +// ^ reference local 5 +// ^^^^^ definition local 6 + (void)value; +// ^^^^^ reference local 6 + } + }; + + // Issue 2: Free function template with enable_if + template ::value, bool>::type = false> +// ^^^ reference [..] std/ +// ^^^^^^^^^ reference [..] std/enable_if# + T convert(int x) { +//^ reference local 7 +// ^^^^^^^ definition [..] convert(767fea59dce4185d). +// ^ definition local 8 + return static_cast(x); +// ^ reference local 7 +// ^ reference local 8 + } + + // Issue 3: Member call through a pointer wrapper (simplified unique_ptr) + template +// ^ definition local 9 + class Ptr { +// ^^^ definition [..] Ptr# + T *p; +// ^ reference local 9 +// ^ definition [..] Ptr#p. + + public: + Ptr(T *ptr) : p(ptr) {} +// ^^^ definition [..] Ptr#`Ptr`(ebd0a1552f8ce24f). +// ^ reference local 9 +// ^^^ definition local 10 +// ^ reference [..] Ptr#p. +// ^^^ reference local 10 + T *operator->() const { return p; } +// ^ reference local 9 +// ^^^^^^^^ definition [..] Ptr#`operator->`(5a2a78a048fb49a8). +// ^ reference [..] Ptr#p. + }; + + class ThreadLoop { +// ^^^^^^^^^^ definition [..] ThreadLoop# + public: + bool isHealthy() const { return true; } +// ^^^^^^^^^ definition [..] ThreadLoop#isHealthy(50ce9a9e25b4a850). + }; + + class ThreadPool { +// ^^^^^^^^^^ definition [..] ThreadPool# + Ptr mThread; +// ^^^ reference [..] Ptr# +// ^^^^^^^^^^ reference [..] ThreadLoop# +// ^^^^^^^ definition [..] ThreadPool#mThread. + + public: + ThreadPool() : mThread(new ThreadLoop()) {} +// ^^^^^^^^^^ definition [..] ThreadPool#ThreadPool(49f6e7a06ebc5aa8). +// ^^^^^^^ reference [..] ThreadPool#mThread. +// ^^^^^^^ reference [..] Ptr#Ptr(ebd0a1552f8ce24f). +// ^^^^^^^^^^ reference [..] ThreadLoop# + + bool checkHealth() const { +// ^^^^^^^^^^^ definition [..] ThreadPool#checkHealth(50ce9a9e25b4a850). + return mThread->isHealthy(); +// ^^^^^^^ reference [..] ThreadPool#mThread. +// ^^ reference [..] Ptr#`operator->`(5a2a78a048fb49a8). +// ^^^^^^^^^ reference [..] ThreadLoop#isHealthy(50ce9a9e25b4a850). + } + }; + + void test() { +// ^^^^ definition [..] test(49f6e7a06ebc5aa8). + Widget w; +// ^^^^^^ reference [..] Widget# +// ^ definition local 11 + w.process(42); +// ^ reference local 11 +// ^^^^^^^ reference [..] Widget#process(9b289cee16747614). + w.process(100); +// ^ reference local 11 +// ^^^^^^^ reference [..] Widget#process(9b289cee16747614). + + auto val = convert(5); +// ^^^ definition local 12 +// ^^^^^^^ reference [..] convert(767fea59dce4185d). + (void)val; +// ^^^ reference local 12 + + ThreadPool pool; +// ^^^^^^^^^^ reference [..] ThreadPool# +// ^^^^ definition local 13 +// ^^^^ reference [..] ThreadPool#ThreadPool(49f6e7a06ebc5aa8). + (void)pool.checkHealth(); +// ^^^^ reference local 13 +// ^^^^^^^^^^^ reference [..] ThreadPool#checkHealth(50ce9a9e25b4a850). + } diff --git a/test/index/types/types.snapshot.cc b/test/index/types/types.snapshot.cc index c5e54516..c1d01615 100644 --- a/test/index/types/types.snapshot.cc +++ b/test/index/types/types.snapshot.cc @@ -343,7 +343,7 @@ }; return ignore_first("", L{}); // ^^^^^^^^^^^^ reference local 3 -// ^ reference [..] trailing_return_type(693bfa61ed1914d5).$anonymous_type_4#`operator()`(dc97d1a1ce4cdab3). +// ^ reference [..] trailing_return_type(693bfa61ed1914d5).$anonymous_type_4#`operator()`(b691caff6c7f530). // ^ reference [..] L# }