diff --git a/src/feature/code_completion.cpp b/src/feature/code_completion.cpp index 8cd9d268..29f3ff2e 100644 --- a/src/feature/code_completion.cpp +++ b/src/feature/code_completion.cpp @@ -386,9 +386,21 @@ public: break; } - auto label = ast::name_of(declaration); auto kind = completion_kind(declaration); + // For constructors and deduction guides, use the class name + // (without template args) instead of the full type name. + // e.g. "vector" instead of "vector<_Tp, _Alloc>". + std::string label; + if(auto* ctor = llvm::dyn_cast(declaration)) { + label = ctor->getParent()->getName().str(); + } else if(auto* guide = + llvm::dyn_cast(declaration)) { + label = guide->getDeducedTemplate()->getName().str(); + } else { + label = ast::name_of(declaration); + } + llvm::SmallString<256> qualified_name; bool is_callable = kind == protocol::CompletionItemKind::Function || kind == protocol::CompletionItemKind::Method || diff --git a/tests/unit/feature/code_completion_tests.cpp b/tests/unit/feature/code_completion_tests.cpp index c3272f19..4b3bd65e 100644 --- a/tests/unit/feature/code_completion_tests.cpp +++ b/tests/unit/feature/code_completion_tests.cpp @@ -192,6 +192,47 @@ void bar() { ASSERT_EQ(*it->kind, protocol::CompletionItemKind::Class); } +TEST_CASE(ConstructorLabelNoTemplateArgs) { + // Constructors of class templates should use plain class name as label, + // not "Foo" or "Foo<_Tp, _Alloc>". This ensures dedup works and + // insertion text is correct. + feature::CodeCompletionOptions opts; + opts.bundle_overloads = false; + code_complete(R"cpp( +template +struct Bazzz { + Bazzz() {} + Bazzz(T x) {} + Bazzz(T x, U y) {} +}; + +template +Bazzz(T) -> Bazzz; + +void bar() { + Ba$(pos) +} +)cpp", + opts); + + // Non-bundled mode should produce multiple "Bazzz" items (class + constructors + guide). + auto count = std::ranges::count_if(items, [](const protocol::CompletionItem& item) { + return item.label == "Bazzz"; + }); + ASSERT_TRUE(count > 1); + + // Every item's label must be plain "Bazzz", never "Bazzz". + // And the insertion text must also be "Bazzz" (not the templated form). + for(auto& item: items) { + if(item.label.find("Bazzz") != std::string::npos) { + ASSERT_EQ(item.label, "Bazzz"); + auto& edit = std::get(*item.text_edit); + ASSERT_TRUE(edit.new_text.starts_with("Bazzz")); + ASSERT_TRUE(edit.new_text.find("<") == std::string::npos); + } + } +} + TEST_CASE(NoBundleOverloads) { feature::CodeCompletionOptions opts; opts.bundle_overloads = false;