From e554660c06eb856a025a4167e887f6ec97bcb822 Mon Sep 17 00:00:00 2001 From: ykiko Date: Thu, 9 Apr 2026 19:15:38 +0800 Subject: [PATCH] fix(completion): use class name for constructor/deduction guide labels (#416) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Fixes a bug where constructors and deduction guides had labels like `vector<_Tp, _Alloc>` instead of just `vector`, causing: 1. Label deduplication to fail (class `vector` != constructor `vector<_Tp, _Alloc>`) 2. Selecting the completion to insert invalid text `vector<_Tp, _Alloc>` Now uses `getParent()->getName()` for constructors and `getDeducedTemplate()->getName()` for deduction guides. ## Test plan - [x] All 494 unit tests pass (existing `DeduplicateByLabel` test covers this) - [x] `pixi run format` clean 🤖 Generated with [Claude Code](https://claude.com/claude-code) ## Summary by CodeRabbit * **Bug Fixes** * Constructor and deduction-guide completions now show the parent type name without template parameters, improving readability and preventing duplicate entries. * **Tests** * Added a unit test verifying completion items for these entries use the parent type name (no template-parameterized labels) and insertion text starts with that name. Co-authored-by: Claude Opus 4.6 --- src/feature/code_completion.cpp | 14 ++++++- tests/unit/feature/code_completion_tests.cpp | 41 ++++++++++++++++++++ 2 files changed, 54 insertions(+), 1 deletion(-) 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;