Skip to content

Commit

Permalink
[Clang][Sema] Revisit the fix for the lambda within a type alias temp…
Browse files Browse the repository at this point in the history
…late decl (#89934)

In the last patch #82310, we used template depths to tell if such alias
decls contain lambdas, which is wrong because the lambda can also appear
as a part of the default argument, and that would make
`getTemplateInstantiationArgs` provide extra template arguments in
undesired contexts. This leads to issue #89853.

Moreover, our approach
for #82104 was sadly wrong.
We tried to teach `DeduceReturnType` to consider alias template
arguments; however, giving these arguments in the context where they
should have been substituted in a `TransformCallExpr` call is never
correct.

This patch addresses such problems by using a `RecursiveASTVisitor` to
check if the lambda is contained by an alias `Decl`, as well as
twiddling the lambda dependencies - we should also build a dependent
lambda expression if the surrounding alias template arguments were
dependent.

Fixes #89853
Fixes #102760
Fixes #105885

(cherry picked from commit b412ec5)
  • Loading branch information
zyn0217 authored and tru committed Sep 1, 2024
1 parent 456006b commit 52ab956
Show file tree
Hide file tree
Showing 2 changed files with 110 additions and 38 deletions.
73 changes: 38 additions & 35 deletions clang/lib/Sema/SemaTemplateInstantiate.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
#include "clang/AST/Expr.h"
#include "clang/AST/ExprConcepts.h"
#include "clang/AST/PrettyDeclStackTrace.h"
#include "clang/AST/RecursiveASTVisitor.h"
#include "clang/AST/Type.h"
#include "clang/AST/TypeLoc.h"
#include "clang/AST/TypeVisitor.h"
Expand Down Expand Up @@ -87,12 +88,19 @@ struct Response {
// than lambda classes.
const FunctionDecl *
getPrimaryTemplateOfGenericLambda(const FunctionDecl *LambdaCallOperator) {
if (!isLambdaCallOperator(LambdaCallOperator))
return LambdaCallOperator;
while (true) {
if (auto *FTD = dyn_cast_if_present<FunctionTemplateDecl>(
LambdaCallOperator->getDescribedTemplate());
FTD && FTD->getInstantiatedFromMemberTemplate()) {
LambdaCallOperator =
FTD->getInstantiatedFromMemberTemplate()->getTemplatedDecl();
} else if (LambdaCallOperator->getPrimaryTemplate()) {
// Cases where the lambda operator is instantiated in
// TemplateDeclInstantiator::VisitCXXMethodDecl.
LambdaCallOperator =
LambdaCallOperator->getPrimaryTemplate()->getTemplatedDecl();
} else if (auto *Prev = cast<CXXMethodDecl>(LambdaCallOperator)
->getInstantiatedFromMemberFunction())
LambdaCallOperator = Prev;
Expand Down Expand Up @@ -138,22 +146,28 @@ getEnclosingTypeAliasTemplateDecl(Sema &SemaRef) {
// Check if we are currently inside of a lambda expression that is
// surrounded by a using alias declaration. e.g.
// template <class> using type = decltype([](auto) { ^ }());
// By checking if:
// 1. The lambda expression and the using alias declaration share the
// same declaration context.
// 2. They have the same template depth.
// We have to do so since a TypeAliasTemplateDecl (or a TypeAliasDecl) is never
// a DeclContext, nor does it have an associated specialization Decl from which
// we could collect these template arguments.
bool isLambdaEnclosedByTypeAliasDecl(
const FunctionDecl *PrimaryLambdaCallOperator,
const FunctionDecl *LambdaCallOperator,
const TypeAliasTemplateDecl *PrimaryTypeAliasDecl) {
return cast<CXXRecordDecl>(PrimaryLambdaCallOperator->getDeclContext())
->getTemplateDepth() ==
PrimaryTypeAliasDecl->getTemplateDepth() &&
getLambdaAwareParentOfDeclContext(
const_cast<FunctionDecl *>(PrimaryLambdaCallOperator)) ==
PrimaryTypeAliasDecl->getDeclContext();
struct Visitor : RecursiveASTVisitor<Visitor> {
Visitor(const FunctionDecl *CallOperator) : CallOperator(CallOperator) {}
bool VisitLambdaExpr(const LambdaExpr *LE) {
// Return true to bail out of the traversal, implying the Decl contains
// the lambda.
return getPrimaryTemplateOfGenericLambda(LE->getCallOperator()) !=
CallOperator;
}
const FunctionDecl *CallOperator;
};

QualType Underlying =
PrimaryTypeAliasDecl->getTemplatedDecl()->getUnderlyingType();

return !Visitor(getPrimaryTemplateOfGenericLambda(LambdaCallOperator))
.TraverseType(Underlying);
}

// Add template arguments from a variable template instantiation.
Expand Down Expand Up @@ -290,23 +304,8 @@ Response HandleFunction(Sema &SemaRef, const FunctionDecl *Function,

// If this function is a generic lambda specialization, we are done.
if (!ForConstraintInstantiation &&
isGenericLambdaCallOperatorOrStaticInvokerSpecialization(Function)) {
// TypeAliasTemplateDecls should be taken into account, e.g.
// when we're deducing the return type of a lambda.
//
// template <class> int Value = 0;
// template <class T>
// using T = decltype([]<int U = 0>() { return Value<T>; }());
//
if (auto TypeAlias = getEnclosingTypeAliasTemplateDecl(SemaRef)) {
if (isLambdaEnclosedByTypeAliasDecl(
/*PrimaryLambdaCallOperator=*/getPrimaryTemplateOfGenericLambda(
Function),
/*PrimaryTypeAliasDecl=*/TypeAlias.PrimaryTypeAliasDecl))
return Response::UseNextDecl(Function);
}
isGenericLambdaCallOperatorOrStaticInvokerSpecialization(Function))
return Response::Done();
}

} else if (Function->getDescribedFunctionTemplate()) {
assert(
Expand Down Expand Up @@ -418,10 +417,9 @@ Response HandleRecordDecl(Sema &SemaRef, const CXXRecordDecl *Rec,
// Retrieve the template arguments for a using alias declaration.
// This is necessary for constraint checking, since we always keep
// constraints relative to the primary template.
if (auto TypeAlias = getEnclosingTypeAliasTemplateDecl(SemaRef)) {
const FunctionDecl *PrimaryLambdaCallOperator =
getPrimaryTemplateOfGenericLambda(Rec->getLambdaCallOperator());
if (isLambdaEnclosedByTypeAliasDecl(PrimaryLambdaCallOperator,
if (auto TypeAlias = getEnclosingTypeAliasTemplateDecl(SemaRef);
ForConstraintInstantiation && TypeAlias) {
if (isLambdaEnclosedByTypeAliasDecl(Rec->getLambdaCallOperator(),
TypeAlias.PrimaryTypeAliasDecl)) {
Result.addOuterTemplateArguments(TypeAlias.Template,
TypeAlias.AssociatedTemplateArguments,
Expand Down Expand Up @@ -1642,12 +1640,17 @@ namespace {

CXXRecordDecl::LambdaDependencyKind
ComputeLambdaDependency(LambdaScopeInfo *LSI) {
auto &CCS = SemaRef.CodeSynthesisContexts.back();
if (CCS.Kind ==
Sema::CodeSynthesisContext::TypeAliasTemplateInstantiation) {
unsigned TypeAliasDeclDepth = CCS.Entity->getTemplateDepth();
if (auto TypeAlias =
TemplateInstArgsHelpers::getEnclosingTypeAliasTemplateDecl(
getSema());
TypeAlias && TemplateInstArgsHelpers::isLambdaEnclosedByTypeAliasDecl(
LSI->CallOperator, TypeAlias.PrimaryTypeAliasDecl)) {
unsigned TypeAliasDeclDepth = TypeAlias.Template->getTemplateDepth();
if (TypeAliasDeclDepth >= TemplateArgs.getNumSubstitutedLevels())
return CXXRecordDecl::LambdaDependencyKind::LDK_AlwaysDependent;
for (const TemplateArgument &TA : TypeAlias.AssociatedTemplateArguments)
if (TA.isDependent())
return CXXRecordDecl::LambdaDependencyKind::LDK_AlwaysDependent;
}
return inherited::ComputeLambdaDependency(LSI);
}
Expand Down
75 changes: 72 additions & 3 deletions clang/test/SemaTemplate/alias-template-with-lambdas.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -91,15 +91,84 @@ void bar() {

namespace GH82104 {

template <typename, typename...> int Zero = 0;
template <typename, typename... D> constexpr int Value = sizeof...(D);

template <typename T, typename...U>
using T14 = decltype([]<int V = 0>() { return Zero<T, U...>; }());
template <typename T, typename... U>
using T14 = decltype([]<int V = 0>(auto Param) {
return Value<T, U...> + V + (int)sizeof(Param);
}("hello"));

template <typename T> using T15 = T14<T, T>;

static_assert(__is_same(T15<char>, int));

// FIXME: This still crashes because we can't extract template arguments T and U
// outside of the instantiation context of T16.
#if 0
template <typename T, typename... U>
using T16 = decltype([](auto Param) requires (sizeof(Param) != 1 && sizeof...(U) > 0) {
return Value<T, U...> + sizeof(Param);
});
static_assert(T16<int, char, float>()(42) == 2 + sizeof(42));
#endif
} // namespace GH82104

namespace GH89853 {

template <typename = void>
static constexpr auto innocuous = []<int m> { return m; };

template <auto Pred = innocuous<>>
using broken = decltype(Pred.template operator()<42>());

broken<> *boom;

template <auto Pred =
[]<char c> {
(void)static_cast<char>(c);
}>
using broken2 = decltype(Pred.template operator()<42>());

broken2<> *boom2;

template <auto Pred = []<char m> { return m; }>
using broken3 = decltype(Pred.template operator()<42>());

broken3<> *boom3;

static constexpr auto non_default = []<char c>(True auto) {
(void) static_cast<char>(c);
};

template<True auto Pred>
using broken4 = decltype(Pred.template operator()<42>(Pred));

broken4<non_default>* boom4;

} // namespace GH89853

namespace GH105885 {

template<int>
using test = decltype([](auto...) {
}());

static_assert(__is_same(test<0>, void));

} // namespace GH105885

namespace GH102760 {

auto make_tuple = []< class Tag, class... Captures>(Tag, Captures...) {
return []< class _Fun >( _Fun) -> void requires requires { 0; }
{};
};

template < class, class... _As >
using Result = decltype(make_tuple(0)(_As{}...));

using T = Result<int, int>;

} // namespace GH102760

} // namespace lambda_calls

0 comments on commit 52ab956

Please sign in to comment.