[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
This commit is contained in:
Sam McCall 2021-02-10 00:11:35 +01:00
parent a874d182c6
commit 5c55d3747b
3 changed files with 128 additions and 31 deletions

View File

@ -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;

View File

@ -5176,6 +5176,75 @@ private:
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();

View File

@ -84,6 +84,9 @@ public:
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 @@ public:
// 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 differ 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