https://github.com/HighCommander4 updated https://github.com/llvm/llvm-project/pull/151643
>From 82f95113d5133c8d8579f6445981ad20dce1881a Mon Sep 17 00:00:00 2001 From: Nathan Ridge <zeratul...@hotmail.com> Date: Sun, 24 Aug 2025 23:03:40 -0400 Subject: [PATCH 1/2] [clang][Sema] Implement additional heuristics from SemaCodeComplete's getApproximateType() in HeuristicResolver After this change, HeuristicResolver should be able to do everything that SemaCodeComplete's getApproximateType() can do (and more). --- clang/include/clang/Sema/HeuristicResolver.h | 6 +- clang/lib/Sema/HeuristicResolver.cpp | 93 +++++++++++++----- .../unittests/Sema/HeuristicResolverTest.cpp | 98 ++++++++++++++++++- 3 files changed, 167 insertions(+), 30 deletions(-) diff --git a/clang/include/clang/Sema/HeuristicResolver.h b/clang/include/clang/Sema/HeuristicResolver.h index 71588bee92d16..93738ab9ebb3d 100644 --- a/clang/include/clang/Sema/HeuristicResolver.h +++ b/clang/include/clang/Sema/HeuristicResolver.h @@ -54,8 +54,6 @@ class HeuristicResolver { std::vector<const NamedDecl *> resolveDeclRefExpr(const DependentScopeDeclRefExpr *RE) const; std::vector<const NamedDecl *> - resolveTypeOfCallExpr(const CallExpr *CE) const; - std::vector<const NamedDecl *> resolveCalleeOfCallExpr(const CallExpr *CE) const; std::vector<const NamedDecl *> resolveUsingValueDecl(const UnresolvedUsingValueDecl *UUVD) const; @@ -93,6 +91,10 @@ class HeuristicResolver { // during simplification, and the operation fails if no pointer type is found. QualType simplifyType(QualType Type, const Expr *E, bool UnwrapPointer); + // Try to heuristically resolve the type of a possibly-dependent expression + // `E`. + QualType resolveExprToType(const Expr *E) const; + // Given an expression `Fn` representing the callee in a function call, // if the call is through a function pointer, try to find the declaration of // the corresponding function pointer type, so that we can recover argument diff --git a/clang/lib/Sema/HeuristicResolver.cpp b/clang/lib/Sema/HeuristicResolver.cpp index 933841beeac3d..e8eb723a9572e 100644 --- a/clang/lib/Sema/HeuristicResolver.cpp +++ b/clang/lib/Sema/HeuristicResolver.cpp @@ -36,7 +36,6 @@ class HeuristicResolverImpl { resolveMemberExpr(const CXXDependentScopeMemberExpr *ME); std::vector<const NamedDecl *> resolveDeclRefExpr(const DependentScopeDeclRefExpr *RE); - std::vector<const NamedDecl *> resolveTypeOfCallExpr(const CallExpr *CE); std::vector<const NamedDecl *> resolveCalleeOfCallExpr(const CallExpr *CE); std::vector<const NamedDecl *> resolveUsingValueDecl(const UnresolvedUsingValueDecl *UUVD); @@ -51,6 +50,7 @@ class HeuristicResolverImpl { llvm::function_ref<bool(const NamedDecl *ND)> Filter); TagDecl *resolveTypeToTagDecl(QualType T); QualType simplifyType(QualType Type, const Expr *E, bool UnwrapPointer); + QualType resolveExprToType(const Expr *E); FunctionProtoTypeLoc getFunctionProtoTypeLoc(const Expr *Fn); private: @@ -72,10 +72,8 @@ class HeuristicResolverImpl { resolveDependentMember(QualType T, DeclarationName Name, llvm::function_ref<bool(const NamedDecl *ND)> Filter); - // Try to heuristically resolve the type of a possibly-dependent expression - // `E`. - QualType resolveExprToType(const Expr *E); std::vector<const NamedDecl *> resolveExprToDecls(const Expr *E); + QualType resolveTypeOfCallExpr(const CallExpr *CE); bool findOrdinaryMemberInDependentClasses(const CXXBaseSpecifier *Specifier, CXXBasePath &Path, @@ -97,18 +95,25 @@ const auto TemplateFilter = [](const NamedDecl *D) { return isa<TemplateDecl>(D); }; -QualType resolveDeclsToType(const std::vector<const NamedDecl *> &Decls, - ASTContext &Ctx) { - if (Decls.size() != 1) // Names an overload set -- just bail. - return QualType(); - if (const auto *TD = dyn_cast<TypeDecl>(Decls[0])) +QualType resolveDeclToType(const NamedDecl *D, ASTContext &Ctx) { + if (const auto *TempD = dyn_cast<TemplateDecl>(D)) { + D = TempD->getTemplatedDecl(); + } + if (const auto *TD = dyn_cast<TypeDecl>(D)) return Ctx.getCanonicalTypeDeclType(TD); - if (const auto *VD = dyn_cast<ValueDecl>(Decls[0])) { + if (const auto *VD = dyn_cast<ValueDecl>(D)) { return VD->getType(); } return QualType(); } +QualType resolveDeclsToType(const std::vector<const NamedDecl *> &Decls, + ASTContext &Ctx) { + if (Decls.size() != 1) // Names an overload set -- just bail. + return QualType(); + return resolveDeclToType(Decls[0], Ctx); +} + TemplateName getReferencedTemplateName(const Type *T) { if (const auto *TST = T->getAs<TemplateSpecializationType>()) { return TST->getTemplateName(); @@ -315,19 +320,29 @@ HeuristicResolverImpl::resolveDeclRefExpr(const DependentScopeDeclRefExpr *RE) { return resolveDependentMember(Qualifier, RE->getDeclName(), StaticFilter); } -std::vector<const NamedDecl *> -HeuristicResolverImpl::resolveTypeOfCallExpr(const CallExpr *CE) { - QualType CalleeType = resolveExprToType(CE->getCallee()); - if (CalleeType.isNull()) - return {}; - if (const auto *FnTypePtr = CalleeType->getAs<PointerType>()) - CalleeType = FnTypePtr->getPointeeType(); - if (const FunctionType *FnType = CalleeType->getAs<FunctionType>()) { - if (const auto *D = resolveTypeToTagDecl(FnType->getReturnType())) { - return {D}; +QualType HeuristicResolverImpl::resolveTypeOfCallExpr(const CallExpr *CE) { + // resolveExprToType(CE->getCallee()) would bail in the case of multiple + // overloads, as it can't produce a single type for them. We can be more + // permissive here, and allow multiple overloads with a common return type. + std::vector<const NamedDecl *> CalleeDecls = + resolveExprToDecls(CE->getCallee()); + QualType CommonReturnType; + for (const NamedDecl *CalleeDecl : CalleeDecls) { + QualType CalleeType = resolveDeclToType(CalleeDecl, Ctx); + if (CalleeType.isNull()) + continue; + if (const auto *FnTypePtr = CalleeType->getAs<PointerType>()) + CalleeType = FnTypePtr->getPointeeType(); + if (const FunctionType *FnType = CalleeType->getAs<FunctionType>()) { + QualType ReturnType = + simplifyType(FnType->getReturnType(), nullptr, false); + if (!CommonReturnType.isNull() && CommonReturnType != ReturnType) { + return {}; // conflicting return types + } + CommonReturnType = ReturnType; } } - return {}; + return CommonReturnType; } std::vector<const NamedDecl *> @@ -378,15 +393,41 @@ HeuristicResolverImpl::resolveExprToDecls(const Expr *E) { return {OE->decls_begin(), OE->decls_end()}; } if (const auto *CE = dyn_cast<CallExpr>(E)) { - return resolveTypeOfCallExpr(CE); + QualType T = resolveTypeOfCallExpr(CE); + if (const auto *D = resolveTypeToTagDecl(T)) { + return {D}; + } + return {}; } if (const auto *ME = dyn_cast<MemberExpr>(E)) return {ME->getMemberDecl()}; + if (const auto *DRE = dyn_cast<DeclRefExpr>(E)) + return {DRE->getDecl()}; return {}; } QualType HeuristicResolverImpl::resolveExprToType(const Expr *E) { + // resolveExprToDecls on a CallExpr only succeeds if the return type is + // a TagDecl, but we may want the type of a call in other cases as well. + // (FIXME: There are probably other cases where we can do something more + // flexible than resoveExprToDecls + resolveDeclsToType, e.g. in the case + // of OverloadExpr we can probably accept overloads with a common type). + if (const auto *CE = dyn_cast<CallExpr>(E)) { + if (QualType Resolved = resolveTypeOfCallExpr(CE); !Resolved.isNull()) + return Resolved; + } + // Similarly, unwrapping a unary dereference operation does not work via + // resolveExprToDecls. + if (const auto *UO = dyn_cast<UnaryOperator>(E->IgnoreParenCasts())) { + if (UO->getOpcode() == UnaryOperatorKind::UO_Deref) { + if (auto Pointee = getPointeeType(resolveExprToType(UO->getSubExpr())); + !Pointee.isNull()) { + return Pointee; + } + } + } + std::vector<const NamedDecl *> Decls = resolveExprToDecls(E); if (!Decls.empty()) return resolveDeclsToType(Decls, Ctx); @@ -565,10 +606,6 @@ std::vector<const NamedDecl *> HeuristicResolver::resolveDeclRefExpr( return HeuristicResolverImpl(Ctx).resolveDeclRefExpr(RE); } std::vector<const NamedDecl *> -HeuristicResolver::resolveTypeOfCallExpr(const CallExpr *CE) const { - return HeuristicResolverImpl(Ctx).resolveTypeOfCallExpr(CE); -} -std::vector<const NamedDecl *> HeuristicResolver::resolveCalleeOfCallExpr(const CallExpr *CE) const { return HeuristicResolverImpl(Ctx).resolveCalleeOfCallExpr(CE); } @@ -604,7 +641,9 @@ QualType HeuristicResolver::simplifyType(QualType Type, const Expr *E, bool UnwrapPointer) { return HeuristicResolverImpl(Ctx).simplifyType(Type, E, UnwrapPointer); } - +QualType HeuristicResolver::resolveExprToType(const Expr *E) const { + return HeuristicResolverImpl(Ctx).resolveExprToType(E); +} FunctionProtoTypeLoc HeuristicResolver::getFunctionProtoTypeLoc(const Expr *Fn) const { return HeuristicResolverImpl(Ctx).getFunctionProtoTypeLoc(Fn); diff --git a/clang/unittests/Sema/HeuristicResolverTest.cpp b/clang/unittests/Sema/HeuristicResolverTest.cpp index 7df25e01e66d4..e8e97793f0e2a 100644 --- a/clang/unittests/Sema/HeuristicResolverTest.cpp +++ b/clang/unittests/Sema/HeuristicResolverTest.cpp @@ -203,7 +203,6 @@ TEST(HeuristicResolver, MemberExpr_AutoTypeDeduction2) { struct B { int waldo; }; - template <typename T> struct A { B b; @@ -238,6 +237,103 @@ TEST(HeuristicResolver, MemberExpr_Chained) { cxxMethodDecl(hasName("foo")).bind("output")); } +TEST(HeuristicResolver, MemberExpr_Chained_ReferenceType) { + std::string Code = R"cpp( + struct B { + int waldo; + }; + template <typename T> + struct A { + B &foo(); + }; + template <typename T> + void bar(A<T> a) { + a.foo().waldo; + } + )cpp"; + // Test resolution of "waldo" in "a.foo().waldo" + expectResolution( + Code, &HeuristicResolver::resolveMemberExpr, + cxxDependentScopeMemberExpr(hasMemberName("waldo")).bind("input"), + fieldDecl(hasName("waldo")).bind("output")); +} + +TEST(HeuristicResolver, MemberExpr_Chained_PointerArrow) { + std::string Code = R"cpp( + struct B { + int waldo; + }; + template <typename T> + B* foo(T); + template <class T> + void bar(T t) { + foo(t)->waldo; + } + )cpp"; + // Test resolution of "waldo" in "foo(t)->waldo" + expectResolution( + Code, &HeuristicResolver::resolveMemberExpr, + cxxDependentScopeMemberExpr(hasMemberName("waldo")).bind("input"), + fieldDecl(hasName("waldo")).bind("output")); +} + +TEST(HeuristicResolver, MemberExpr_Chained_PointerDeref) { + std::string Code = R"cpp( + struct B { + int waldo; + }; + template <typename T> + B* foo(T); + template <class T> + void bar(T t) { + (*foo(t)).waldo; + } + )cpp"; + // Test resolution of "waldo" in "foo(t)->waldo" + expectResolution( + Code, &HeuristicResolver::resolveMemberExpr, + cxxDependentScopeMemberExpr(hasMemberName("waldo")).bind("input"), + fieldDecl(hasName("waldo")).bind("output")); +} + +TEST(HeuristicResolver, MemberExpr_Chained_Overload) { + std::string Code = R"cpp( + struct B { + int waldo; + }; + B overloaded(int); + B overloaded(double); + template <typename T> + void foo(T t) { + overloaded(t).waldo; + } + )cpp"; + // Test resolution of "waldo" in "overloaded(t).waldo" + expectResolution( + Code, &HeuristicResolver::resolveMemberExpr, + cxxDependentScopeMemberExpr(hasMemberName("waldo")).bind("input"), + fieldDecl(hasName("waldo")).bind("output")); +} + +TEST(HeuristicResolver, MemberExpr_CallToFunctionTemplate) { + std::string Code = R"cpp( + struct B { + int waldo; + }; + template <typename T> + B bar(T); + template <typename T> + void foo(T t) { + bar(t).waldo; + } + )cpp"; + // Test resolution of "waldo" in "bar(t).waldo" + expectResolution( + Code, &HeuristicResolver::resolveMemberExpr, + cxxDependentScopeMemberExpr(hasMemberName("waldo")).bind("input"), + fieldDecl(hasName("waldo")).bind("output")); +} + TEST(HeuristicResolver, MemberExpr_ReferenceType) { std::string Code = R"cpp( struct B { >From 7b78f65cba28c3489b94bb7e0bc79c48f05303e0 Mon Sep 17 00:00:00 2001 From: Nathan Ridge <zeratul...@hotmail.com> Date: Sun, 24 Aug 2025 23:05:28 -0400 Subject: [PATCH 2/2] [clang][Sema] Replace most of SemaCodeComplete's getApproximateType() with calls to HeuristicResolver --- clang/lib/Sema/SemaCodeComplete.cpp | 97 +++-------------------------- 1 file changed, 7 insertions(+), 90 deletions(-) diff --git a/clang/lib/Sema/SemaCodeComplete.cpp b/clang/lib/Sema/SemaCodeComplete.cpp index c6adead5a65f5..fc5bb15ec0f2c 100644 --- a/clang/lib/Sema/SemaCodeComplete.cpp +++ b/clang/lib/Sema/SemaCodeComplete.cpp @@ -5830,96 +5830,13 @@ class ConceptInfo { // We accept some lossiness (like dropping parameters). // We only try to handle common expressions on the LHS of MemberExpr. QualType getApproximateType(const Expr *E, HeuristicResolver &Resolver) { - if (E->getType().isNull()) - return QualType(); - // Don't drop implicit cast if it's an array decay. - if (auto *ICE = dyn_cast<ImplicitCastExpr>(E); - !ICE || ICE->getCastKind() != CK_ArrayToPointerDecay) - E = E->IgnoreParenImpCasts(); - QualType Unresolved = E->getType(); - // Resolve DependentNameType - if (const auto *DNT = Unresolved->getAs<DependentNameType>()) { - if (auto Decls = Resolver.resolveDependentNameType(DNT); - Decls.size() == 1) { - if (const auto *TD = dyn_cast<TypeDecl>(Decls[0])) - return TD->getASTContext().getTypeDeclType(TD); - } - } - // We only resolve DependentTy, or undeduced autos (including auto* etc). - if (!Unresolved->isSpecificBuiltinType(BuiltinType::Dependent)) { - AutoType *Auto = Unresolved->getContainedAutoType(); - if (!Auto || !Auto->isUndeducedAutoType()) - return Unresolved; - } - // 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(), Resolver); - 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: resolve using HeuristicResolver. - if (const auto *CDSME = llvm::dyn_cast<CXXDependentScopeMemberExpr>(E)) { - for (const auto *Member : Resolver.resolveMemberExpr(CDSME)) { - if (const auto *VD = dyn_cast<ValueDecl>(Member)) { - return VD->getType().getNonReferenceType(); - } - } - } - // A reference to an `auto` variable: approximate-resolve its initializer. - if (const auto *DRE = llvm::dyn_cast<DeclRefExpr>(E)) { - if (const auto *VD = llvm::dyn_cast<VarDecl>(DRE->getDecl())) { - if (VD->hasInit()) - return getApproximateType(VD->getInit(), Resolver); - } - } - if (const auto *UO = llvm::dyn_cast<UnaryOperator>(E)) { - if (UO->getOpcode() == UnaryOperatorKind::UO_Deref) { - // We recurse into the subexpression because it could be of dependent - // type. - if (auto Pointee = - getApproximateType(UO->getSubExpr(), Resolver)->getPointeeType(); - !Pointee.isNull()) - return Pointee; - // Our caller expects a non-null result, even though the SubType is - // supposed to have a pointee. Fall through to Unresolved anyway. - } - } - return Unresolved; + QualType Result = Resolver.resolveExprToType(E); + if (Result.isNull()) + return Result; + Result = Resolver.simplifyType(Result.getNonReferenceType(), E, false); + if (Result.isNull()) + return Result; + return Result.getNonReferenceType(); } // If \p Base is ParenListExpr, assume a chain of comma operators and pick the _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits