[clang] 5c55d37 - [CodeComplete] Member completion: heuristically resolve some dependent base exprs
Sam McCall via cfe-commits
cfe-commits at lists.llvm.org
Thu Feb 11 02:03:48 PST 2021
Author: Sam McCall
Date: 2021-02-11T11:03:40+01:00
New Revision: 5c55d3747b0cab17aaa07729cf90a49dbda71bbe
URL: https://github.com/llvm/llvm-project/commit/5c55d3747b0cab17aaa07729cf90a49dbda71bbe
DIFF: https://github.com/llvm/llvm-project/commit/5c55d3747b0cab17aaa07729cf90a49dbda71bbe.diff
LOG: [CodeComplete] Member completion: heuristically resolve some dependent base exprs
Today, inside a template, you can get completion for:
Foo<T> t;
t.^
t has dependent type Foo<T>, and we use the primary template to find its members.
However we also want this to work:
t.foo.bar().^
The type of t.foo.bar() is DependentTy, so we attempt to resolve using similar
heuristics (e.g. primary template).
Differential Revision: https://reviews.llvm.org/D96376
Added:
Modified:
clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp
clang/lib/Sema/SemaCodeComplete.cpp
clang/test/CodeCompletion/member-access.cpp
Removed:
################################################################################
diff --git a/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp b/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp
index 5e8e242086ba..9403cda57cfe 100644
--- a/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp
+++ b/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp
@@ -1098,6 +1098,20 @@ template <template <class> class TT> int foo() {
EXPECT_THAT(Completions, Contains(Named("TT")));
}
+TEST(CompletionTest, NestedTemplateHeuristics) {
+ auto Completions = completions(R"cpp(
+struct Plain { int xxx; };
+template <typename T> class Templ { Plain ppp; };
+template <typename T> void foo(Templ<T> &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<CodeCompletion> RecordedCompletions;
CodeCompleteOptions Opts;
diff --git a/clang/lib/Sema/SemaCodeComplete.cpp b/clang/lib/Sema/SemaCodeComplete.cpp
index 2f5a28e132b3..1e4d5dd62aa0 100644
--- a/clang/lib/Sema/SemaCodeComplete.cpp
+++ b/clang/lib/Sema/SemaCodeComplete.cpp
@@ -5176,6 +5176,75 @@ class ConceptInfo {
llvm::DenseMap<const IdentifierInfo *, Member> 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<CallExpr>(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<PointerType>()) {
+ Callee = FnTypePtr->getPointeeType();
+ } else if (const auto *BPT = Callee->getAs<BlockPointerType>()) {
+ Callee = BPT->getPointeeType();
+ }
+ if (const FunctionType *FnType = Callee->getAs<FunctionType>())
+ return FnType->getReturnType().getNonReferenceType();
+
+ // Unresolved call: try to guess the return type.
+ if (const auto *OE = llvm::dyn_cast<OverloadExpr>(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<FunctionDecl>(D))
+ ReturnType = FD->getReturnType();
+ else if (const auto *FTD = llvm::dyn_cast<FunctionTemplateDecl>(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<CXXDependentScopeMemberExpr>(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<ValueDecl>(Member))
+ return VD->getType().getNonReferenceType();
+ }
+ }
+ return Unresolved;
+}
+
} // namespace
void Sema::CodeCompleteMemberReferenceExpr(Scope *S, Expr *Base,
@@ -5198,7 +5267,7 @@ void Sema::CodeCompleteMemberReferenceExpr(Scope *S, Expr *Base,
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 @@ void Sema::CodeCompleteMemberReferenceExpr(Scope *S, Expr *Base,
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
index 6fe4d1f6a274..88129d45bccf 100644
--- a/clang/test/CodeCompletion/member-access.cpp
+++ b/clang/test/CodeCompletion/member-access.cpp
@@ -84,6 +84,9 @@ class TemplateClass: public Base1 , public BaseTemplate<T> {
T function() { }
T field;
+ TemplateClass<S, T> &relatedField;
+ BaseTemplate<S> &relatedFunction();
+
void overload1(const T &);
void overload1(const S &);
};
@@ -102,8 +105,12 @@ void completeDependentMembers(TemplateClass<T, S> &object,
// 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 @@ void completeDependentSpecializedMembers(TemplateClass<int, double> &object,
// 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 <typename T>
@@ -135,17 +142,17 @@ class Template {
// 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<int>#]o1
// CHECK-CC6: o2 : [#BaseTemplate<T>#]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 @@ void dependentColonColonCompletion() {
// CHECK-CC7: o2 : [#BaseTemplate<T>#]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<T>::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 @@ void test3(const Proxy2 &p) {
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
diff er only by return type and this-qualifiers.
@@ -234,28 +241,28 @@ void testXValue(Overloads& X) {
static_cast<Overloads&&>(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 @@ void testOverloadOperator() {
} 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 @@ void foo() {
// 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 <typename T>
+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
More information about the cfe-commits
mailing list