diff --git a/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp b/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp --- a/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp +++ b/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp @@ -1098,6 +1098,20 @@ EXPECT_THAT(Completions, Contains(Named("TT"))); } +TEST(CompletionTest, NestedTemplateHeuristics) { + auto Completions = completions(R"cpp( +struct Plain { int xxx; }; +template class Templ { Plain ppp; }; +template void foo(Templ &t) { + // Formally ppp has DependentTy, because Templ may be specialized. + // However we sholud be able to see into it using the primary template. + t.ppp.^ +} +)cpp") + .Completions; + EXPECT_THAT(Completions, Contains(Named("xxx"))); +} + TEST(CompletionTest, RecordCCResultCallback) { std::vector RecordedCompletions; CodeCompleteOptions Opts; diff --git a/clang/lib/Sema/SemaCodeComplete.cpp b/clang/lib/Sema/SemaCodeComplete.cpp --- a/clang/lib/Sema/SemaCodeComplete.cpp +++ b/clang/lib/Sema/SemaCodeComplete.cpp @@ -5176,6 +5176,75 @@ llvm::DenseMap Results; }; + +// Returns a type for E that yields acceptable member completions. +// In particular, when E->getType() is DependentTy, try to guess a likely type. +// We accept some lossiness (like dropping parameters). +// We only try to handle common expressions on the LHS of MemberExpr. +QualType getApproximateType(const Expr *E) { + QualType Unresolved = E->getType(); + if (Unresolved.isNull() || + !Unresolved->isSpecificBuiltinType(BuiltinType::Dependent)) + return Unresolved; + E = E->IgnoreParens(); + // A call: approximate-resolve callee to a function type, get its return type + if (const CallExpr *CE = llvm::dyn_cast(E)) { + QualType Callee = getApproximateType(CE->getCallee()); + if (Callee.isNull() || + Callee->isSpecificPlaceholderType(BuiltinType::BoundMember)) + Callee = Expr::findBoundMemberType(CE->getCallee()); + if (Callee.isNull()) + return Unresolved; + + if (const auto *FnTypePtr = Callee->getAs()) { + Callee = FnTypePtr->getPointeeType(); + } else if (const auto *BPT = Callee->getAs()) { + Callee = BPT->getPointeeType(); + } + if (const FunctionType *FnType = Callee->getAs()) + return FnType->getReturnType().getNonReferenceType(); + + // Unresolved call: try to guess the return type. + if (const auto *OE = llvm::dyn_cast(CE->getCallee())) { + // If all candidates have the same approximate return type, use it. + // Discard references and const to allow more to be "the same". + // (In particular, if there's one candidate + ADL, resolve it). + const Type *Common = nullptr; + for (const auto *D : OE->decls()) { + QualType ReturnType; + if (const auto *FD = llvm::dyn_cast(D)) + ReturnType = FD->getReturnType(); + else if (const auto *FTD = llvm::dyn_cast(D)) + ReturnType = FTD->getTemplatedDecl()->getReturnType(); + if (ReturnType.isNull()) + continue; + const Type *Candidate = + ReturnType.getNonReferenceType().getCanonicalType().getTypePtr(); + if (Common && Common != Candidate) + return Unresolved; // Multiple candidates. + Common = Candidate; + } + if (Common != nullptr) + return QualType(Common, 0); + } + } + // A dependent member: approximate-resolve the base, then lookup. + if (const auto *CDSME = llvm::dyn_cast(E)) { + QualType Base = CDSME->isImplicitAccess() + ? CDSME->getBaseType() + : getApproximateType(CDSME->getBase()); + if (CDSME->isArrow() && !Base.isNull()) + Base = Base->getPointeeType(); // could handle unique_ptr etc here? + RecordDecl *RD = Base.isNull() ? nullptr : getAsRecordDecl(Base); + if (RD && RD->isCompleteDefinition()) { + for (const auto &Member : RD->lookup(CDSME->getMember())) + if (const ValueDecl *VD = llvm::dyn_cast(Member)) + return VD->getType().getNonReferenceType(); + } + } + return Unresolved; +} + } // namespace void Sema::CodeCompleteMemberReferenceExpr(Scope *S, Expr *Base, @@ -5198,7 +5267,7 @@ ExprResult ConvertedBase = PerformMemberExprBaseConversion(Base, IsArrow); if (ConvertedBase.isInvalid()) return; - QualType ConvertedBaseType = ConvertedBase.get()->getType(); + QualType ConvertedBaseType = getApproximateType(ConvertedBase.get()); enum CodeCompletionContext::Kind contextKind; @@ -5234,7 +5303,7 @@ return false; Base = ConvertedBase.get(); - QualType BaseType = Base->getType(); + QualType BaseType = getApproximateType(Base); if (BaseType.isNull()) return false; ExprValueKind BaseKind = Base->getValueKind(); diff --git a/clang/test/CodeCompletion/member-access.cpp b/clang/test/CodeCompletion/member-access.cpp --- a/clang/test/CodeCompletion/member-access.cpp +++ b/clang/test/CodeCompletion/member-access.cpp @@ -84,6 +84,9 @@ T function() { } T field; + TemplateClass &relatedField; + BaseTemplate &relatedFunction(); + void overload1(const T &); void overload1(const S &); }; @@ -102,8 +105,12 @@ // CHECK-CC2: overload1 : [#void#]overload1(<#const T &#>) // CHECK-CC2: overload1 : [#void#]overload1(<#const S &#>) -// RUN: %clang_cc1 -fsyntax-only -code-completion-at=%s:94:10 %s -o - | FileCheck -check-prefix=CHECK-CC2 %s -// RUN: %clang_cc1 -fsyntax-only -code-completion-at=%s:95:12 %s -o - | FileCheck -check-prefix=CHECK-CC2 %s +// RUN: %clang_cc1 -fsyntax-only -code-completion-at=%s:97:10 %s -o - | FileCheck -check-prefix=CHECK-CC2 %s +// RUN: %clang_cc1 -fsyntax-only -code-completion-at=%s:98:12 %s -o - | FileCheck -check-prefix=CHECK-CC2 %s + + object.relatedField.relatedFunction().baseTemplateField; +// CHECK-DEP-CHAIN: baseTemplateField : [#T#]baseTemplateField +// RUN: %clang_cc1 -fsyntax-only -code-completion-at=%s:111:41 %s -o - | FileCheck -check-prefix=CHECK-DEP-CHAIN %s } @@ -120,8 +127,8 @@ // CHECK-CC3: overload1 : [#void#]overload1(<#const int &#>) // CHECK-CC3: overload1 : [#void#]overload1(<#const double &#>) -// RUN: %clang_cc1 -fsyntax-only -code-completion-at=%s:112:10 %s -o - | FileCheck -check-prefix=CHECK-CC3 %s -// RUN: %clang_cc1 -fsyntax-only -code-completion-at=%s:113:12 %s -o - | FileCheck -check-prefix=CHECK-CC3 %s +// RUN: %clang_cc1 -fsyntax-only -code-completion-at=%s:119:10 %s -o - | FileCheck -check-prefix=CHECK-CC3 %s +// RUN: %clang_cc1 -fsyntax-only -code-completion-at=%s:120:12 %s -o - | FileCheck -check-prefix=CHECK-CC3 %s } template @@ -135,17 +142,17 @@ // CHECK-CC4: BaseTemplate : BaseTemplate:: // CHECK-CC4: baseTemplateField : [#int#]baseTemplateField // CHECK-CC4: baseTemplateFunction : [#int#]baseTemplateFunction() -// RUN: %clang_cc1 -fsyntax-only -code-completion-at=%s:134:8 %s -o - | FileCheck -check-prefix=CHECK-CC4 %s +// RUN: %clang_cc1 -fsyntax-only -code-completion-at=%s:141:8 %s -o - | FileCheck -check-prefix=CHECK-CC4 %s o2.baseTemplateField; // CHECK-CC5: BaseTemplate : BaseTemplate:: // CHECK-CC5: baseTemplateField : [#T#]baseTemplateField // CHECK-CC5: baseTemplateFunction : [#T#]baseTemplateFunction() -// RUN: %clang_cc1 -fsyntax-only -code-completion-at=%s:139:8 %s -o - | FileCheck -check-prefix=CHECK-CC5 %s +// RUN: %clang_cc1 -fsyntax-only -code-completion-at=%s:146:8 %s -o - | FileCheck -check-prefix=CHECK-CC5 %s this->o1; // CHECK-CC6: [#void#]function() // CHECK-CC6: o1 : [#BaseTemplate#]o1 // CHECK-CC6: o2 : [#BaseTemplate#]o2 -// RUN: %clang_cc1 -fsyntax-only -code-completion-at=%s:144:11 %s -o - | FileCheck -check-prefix=CHECK-CC6 %s +// RUN: %clang_cc1 -fsyntax-only -code-completion-at=%s:151:11 %s -o - | FileCheck -check-prefix=CHECK-CC6 %s } static void staticFn(T &obj); @@ -162,9 +169,9 @@ // CHECK-CC7: o2 : [#BaseTemplate#]o2 // CHECK-CC7: staticFn : [#void#]staticFn(<#T &obj#>) // CHECK-CC7: Template : Template -// RUN: %clang_cc1 -fsyntax-only -code-completion-at=%s:158:16 %s -o - | FileCheck -check-prefix=CHECK-CC7 %s +// RUN: %clang_cc1 -fsyntax-only -code-completion-at=%s:165:16 %s -o - | FileCheck -check-prefix=CHECK-CC7 %s typename Template::Nested m; -// RUN: %clang_cc1 -fsyntax-only -code-completion-at=%s:166:25 %s -o - | FileCheck -check-prefix=CHECK-CC7 %s +// RUN: %clang_cc1 -fsyntax-only -code-completion-at=%s:173:25 %s -o - | FileCheck -check-prefix=CHECK-CC7 %s } class Proxy2 { @@ -181,34 +188,34 @@ p. } -// RUN: %clang_cc1 -fsyntax-only -code-completion-with-fixits -code-completion-at=%s:177:6 %s -o - | FileCheck -check-prefix=CHECK-CC8 --implicit-check-not="Derived : Derived(" %s +// RUN: %clang_cc1 -fsyntax-only -code-completion-with-fixits -code-completion-at=%s:184:6 %s -o - | FileCheck -check-prefix=CHECK-CC8 --implicit-check-not="Derived : Derived(" %s // CHECK-CC8: Base1 (InBase) : Base1:: // CHECK-CC8: member1 (InBase) : [#int#][#Base1::#]member1 // CHECK-CC8: member1 (InBase) : [#int#][#Base2::#]member1 // CHECK-CC8: member2 (InBase) : [#float#][#Base1::#]member2 // CHECK-CC8: member3 (InBase) : [#double#][#Base2::#]member3 // CHECK-CC8: member4 : [#int#]member4 -// CHECK-CC8: member5 : [#int#]member5 (requires fix-it: {177:4-177:6} to ".") +// CHECK-CC8: member5 : [#int#]member5 (requires fix-it: {184:4-184:6} to ".") // CHECK-CC8: memfun1 (InBase) : [#void#][#Base3::#]memfun1(<#float#>) // CHECK-CC8: memfun1 (InBase) : [#void#][#Base3::#]memfun1(<#double#>)[# const#] // CHECK-CC8: memfun1 (Hidden,InBase) : [#void#]Base2::memfun1(<#int#>) // CHECK-CC8: memfun2 (InBase) : [#void#][#Base3::#]memfun2(<#int#>) // CHECK-CC8: memfun3 : [#int#]memfun3(<#int#>) -// CHECK-CC8: operator-> : [#Derived *#]operator->()[# const#] (requires fix-it: {177:4-177:6} to ".") +// CHECK-CC8: operator-> : [#Derived *#]operator->()[# const#] (requires fix-it: {184:4-184:6} to ".") -// RUN: %clang_cc1 -fsyntax-only -code-completion-with-fixits -code-completion-at=%s:181:6 %s -o - | FileCheck -check-prefix=CHECK-CC9 --implicit-check-not="Derived : Derived(" %s +// RUN: %clang_cc1 -fsyntax-only -code-completion-with-fixits -code-completion-at=%s:188:6 %s -o - | FileCheck -check-prefix=CHECK-CC9 --implicit-check-not="Derived : Derived(" %s // CHECK-CC9: Base1 (InBase) : Base1:: -// CHECK-CC9: member1 (InBase) : [#int#][#Base1::#]member1 (requires fix-it: {181:4-181:5} to "->") -// CHECK-CC9: member1 (InBase) : [#int#][#Base2::#]member1 (requires fix-it: {181:4-181:5} to "->") -// CHECK-CC9: member2 (InBase) : [#float#][#Base1::#]member2 (requires fix-it: {181:4-181:5} to "->") -// CHECK-CC9: member3 (InBase) : [#double#][#Base2::#]member3 (requires fix-it: {181:4-181:5} to "->") -// CHECK-CC9: member4 : [#int#]member4 (requires fix-it: {181:4-181:5} to "->") +// CHECK-CC9: member1 (InBase) : [#int#][#Base1::#]member1 (requires fix-it: {188:4-188:5} to "->") +// CHECK-CC9: member1 (InBase) : [#int#][#Base2::#]member1 (requires fix-it: {188:4-188:5} to "->") +// CHECK-CC9: member2 (InBase) : [#float#][#Base1::#]member2 (requires fix-it: {188:4-188:5} to "->") +// CHECK-CC9: member3 (InBase) : [#double#][#Base2::#]member3 (requires fix-it: {188:4-188:5} to "->") +// CHECK-CC9: member4 : [#int#]member4 (requires fix-it: {188:4-188:5} to "->") // CHECK-CC9: member5 : [#int#]member5 -// CHECK-CC9: memfun1 (InBase) : [#void#][#Base3::#]memfun1(<#float#>) (requires fix-it: {181:4-181:5} to "->") -// CHECK-CC9: memfun1 (InBase) : [#void#][#Base3::#]memfun1(<#double#>)[# const#] (requires fix-it: {181:4-181:5} to "->") -// CHECK-CC9: memfun1 (Hidden,InBase) : [#void#]Base2::memfun1(<#int#>) (requires fix-it: {181:4-181:5} to "->") -// CHECK-CC9: memfun2 (InBase) : [#void#][#Base3::#]memfun2(<#int#>) (requires fix-it: {181:4-181:5} to "->") -// CHECK-CC9: memfun3 : [#int#]memfun3(<#int#>) (requires fix-it: {181:4-181:5} to "->") +// CHECK-CC9: memfun1 (InBase) : [#void#][#Base3::#]memfun1(<#float#>) (requires fix-it: {188:4-188:5} to "->") +// CHECK-CC9: memfun1 (InBase) : [#void#][#Base3::#]memfun1(<#double#>)[# const#] (requires fix-it: {188:4-188:5} to "->") +// CHECK-CC9: memfun1 (Hidden,InBase) : [#void#]Base2::memfun1(<#int#>) (requires fix-it: {188:4-188:5} to "->") +// CHECK-CC9: memfun2 (InBase) : [#void#][#Base3::#]memfun2(<#int#>) (requires fix-it: {188:4-188:5} to "->") +// CHECK-CC9: memfun3 : [#int#]memfun3(<#int#>) (requires fix-it: {188:4-188:5} to "->") // CHECK-CC9: operator-> : [#Derived *#]operator->()[# const#] // These overload sets differ only by return type and this-qualifiers. @@ -234,28 +241,28 @@ static_cast(X). } -// RUN: %clang_cc1 -fsyntax-only -code-completion-at=%s:225:7 %s -o - | FileCheck -check-prefix=CHECK-LVALUE %s \ +// RUN: %clang_cc1 -fsyntax-only -code-completion-at=%s:232:7 %s -o - | FileCheck -check-prefix=CHECK-LVALUE %s \ // RUN: --implicit-check-not="[#int#]ConstOverload(" \ // RUN: --implicit-check-not="[#double#]RefOverload(" \ // RUN: --implicit-check-not="[#char#]RefOverload(" // CHECK-LVALUE-DAG: [#double#]ConstOverload( // CHECK-LVALUE-DAG: [#int#]RefOverload( -// RUN: %clang_cc1 -fsyntax-only -code-completion-at=%s:228:12 %s -o - | FileCheck -check-prefix=CHECK-CONSTLVALUE %s \ +// RUN: %clang_cc1 -fsyntax-only -code-completion-at=%s:235:12 %s -o - | FileCheck -check-prefix=CHECK-CONSTLVALUE %s \ // RUN: --implicit-check-not="[#double#]ConstOverload(" \ // RUN: --implicit-check-not="[#int#]RefOverload(" \ // RUN: --implicit-check-not="[#char#]RefOverload(" // CHECK-CONSTLVALUE: [#int#]ConstOverload( // CHECK-CONSTLVALUE: [#double#]RefOverload( -// RUN: %clang_cc1 -fsyntax-only -code-completion-at=%s:231:15 %s -o - | FileCheck -check-prefix=CHECK-PRVALUE %s \ +// RUN: %clang_cc1 -fsyntax-only -code-completion-at=%s:238:15 %s -o - | FileCheck -check-prefix=CHECK-PRVALUE %s \ // RUN: --implicit-check-not="[#int#]ConstOverload(" \ // RUN: --implicit-check-not="[#int#]RefOverload(" \ // RUN: --implicit-check-not="[#double#]RefOverload(" // CHECK-PRVALUE: [#double#]ConstOverload( // CHECK-PRVALUE: [#char#]RefOverload( -// RUN: %clang_cc1 -fsyntax-only -code-completion-at=%s:234:31 %s -o - | FileCheck -check-prefix=CHECK-XVALUE %s \ +// RUN: %clang_cc1 -fsyntax-only -code-completion-at=%s:241:31 %s -o - | FileCheck -check-prefix=CHECK-XVALUE %s \ // RUN: --implicit-check-not="[#int#]ConstOverload(" \ // RUN: --implicit-check-not="[#int#]RefOverload(" \ // RUN: --implicit-check-not="[#double#]RefOverload(" @@ -269,7 +276,7 @@ } s; return s. } -// RUN: %clang_cc1 -fsyntax-only -code-completion-at=%s:270:12 %s -o - | FileCheck -check-prefix=CHECK-OPER %s \ +// RUN: %clang_cc1 -fsyntax-only -code-completion-at=%s:277:12 %s -o - | FileCheck -check-prefix=CHECK-OPER %s \ // RUN: --implicit-check-not="[#char#]operator=(" // CHECK-OPER: [#int#]operator=( @@ -280,5 +287,12 @@ // No overload matches, but we have recovery-expr with the correct type. overloaded(). } -// RUN: not %clang_cc1 -fsyntax-only -frecovery-ast -frecovery-ast-type -code-completion-at=%s:281:16 %s -o - | FileCheck -check-prefix=CHECK-RECOVERY %s +// RUN: not %clang_cc1 -fsyntax-only -frecovery-ast -frecovery-ast-type -code-completion-at=%s:288:16 %s -o - | FileCheck -check-prefix=CHECK-RECOVERY %s // CHECK-RECOVERY: [#int#]member +template +void fooDependent(T t) { + // Overload not resolved, but we notice all candidates return the same type. + overloaded(t). +} +// RUN: %clang_cc1 -fsyntax-only -code-completion-at=%s:295:17 %s -o - | FileCheck -check-prefix=CHECK-OVERLOAD %s +// CHECK-OVERLOAD: [#int#]member