forked from OSchip/llvm-project
[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:
parent
a874d182c6
commit
5c55d3747b
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue