Skip to content

[Clang][Sema] Revisit the fix for the lambda within a type alias template decl #89934

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Aug 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -88,12 +89,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 @@ -139,22 +147,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 @@ -293,23 +307,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 @@ -421,10 +420,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 @@ -1647,12 +1645,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
Comment on lines +105 to +113
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have to admit we still have lots of edge cases that our current approach (i.e. by extracting template arguments from the type alias instantiation context) doesn't cover. However, I think we can still land this patch (because it addresses the regressions and covers a number of cases) and probably come up with another solution later.

Perhaps, I think one promising approach is to change the TypeAliasTemplateDecl to a kind of DeclContext and thereby we can invent something e.g. TypeAliasTemplateSpecializationDecl so that they can be handled like what we do for member function templates.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe, or something along those lines.
(Or maybe we need to push some sort of state when parsing / transforming an alias and have a more generic way to get at template parameter)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It turns out to be an evaluation context that eventually becomes the lambda context declaration.
Given that Matheus is going to refactor these codes shortly in the future, and we have not been correctly compiling this case for a long time (https://gcc.godbolt.org/z/sbYT1Pa91), I think I can delegate it to @mizvekov, and hopefully, he could fix the test here.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test case passes with my current WIP contextdecl patch.

By the way, Value needs to be constexpr in order for that static_assert to work.

} // 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
Loading