Skip to content

Commit

Permalink
[Sema] Lambdas are not part of immediate context for deduction
Browse files Browse the repository at this point in the history
This commit implements [temp.deduct]p9.
Test updates include:
- New notes in `cxx1y-init-captures.cpp`, `lambda-expressions.cpp`
  and 'warn-unused-lambda-capture.cpp'.
  This seems to be caused by diagnosing errors earlier (during
  deduction) that were previously surfaced later (during
  instantiation).
- New error `lambda-unevaluated.cpp` is in line with [temp.deduct]p9.

Reviewed By: erichkeane, #clang-language-wg

Differential Revision: https://reviews.llvm.org/D148802
  • Loading branch information
ilya-biryukov committed May 9, 2023
1 parent 709098f commit 629170f
Show file tree
Hide file tree
Showing 28 changed files with 194 additions and 50 deletions.
3 changes: 3 additions & 0 deletions clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,9 @@ C++20 Feature Support
building of standard modules. This diagnostic may be strengthened into an
error again in the future once there is a less fragile way to mark a module
as being part of the implementation rather than a user module.
- Clang now implements `[temp.deduct]p9`. Substitution failures inside lambdas from
unevaluated contexts will be surfaced as errors. They were previously handled as
SFINAE.

C++23 Feature Support
^^^^^^^^^^^^^^^^^^^^^
Expand Down
2 changes: 2 additions & 0 deletions clang/include/clang/Basic/DiagnosticSemaKinds.td
Original file line number Diff line number Diff line change
Expand Up @@ -5327,6 +5327,8 @@ def note_constraint_normalization_here : Note<
def note_parameter_mapping_substitution_here : Note<
"while substituting into concept arguments here; substitution failures not "
"allowed in concept arguments">;
def note_lambda_substitution_here : Note<
"while substituting into a lambda expression here">;
def note_instantiation_contexts_suppressed : Note<
"(skipping %0 context%s0 in backtrace; use -ftemplate-backtrace-limit=0 to "
"see all)">;
Expand Down
3 changes: 3 additions & 0 deletions clang/include/clang/Sema/Sema.h
Original file line number Diff line number Diff line change
Expand Up @@ -9261,6 +9261,9 @@ class Sema final {
/// a TemplateDecl.
DeducedTemplateArgumentSubstitution,

/// We are substituting into a lambda expression.
LambdaExpressionSubstitution,

/// We are substituting prior template arguments into a new
/// template parameter. The template parameter itself is either a
/// NonTypeTemplateParmDecl or a TemplateTemplateParmDecl.
Expand Down
2 changes: 2 additions & 0 deletions clang/lib/Frontend/FrontendActions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -372,6 +372,8 @@ class DefaultTemplateInstCallback : public TemplateInstantiationCallback {
return "ExplicitTemplateArgumentSubstitution";
case CodeSynthesisContext::DeducedTemplateArgumentSubstitution:
return "DeducedTemplateArgumentSubstitution";
case CodeSynthesisContext::LambdaExpressionSubstitution:
return "LambdaExpressionSubstitution";
case CodeSynthesisContext::PriorTemplateArgumentSubstitution:
return "PriorTemplateArgumentSubstitution";
case CodeSynthesisContext::DefaultTemplateArgumentChecking:
Expand Down
35 changes: 32 additions & 3 deletions clang/lib/Sema/SemaTemplateInstantiate.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
#include "clang/Sema/Template.h"
#include "clang/Sema/TemplateDeduction.h"
#include "clang/Sema/TemplateInstCallback.h"
#include "llvm/ADT/ScopeExit.h"
#include "llvm/Support/ErrorHandling.h"
#include "llvm/Support/TimeProfiler.h"
#include <optional>
Expand Down Expand Up @@ -368,6 +369,7 @@ bool Sema::CodeSynthesisContext::isInstantiationRecord() const {
case InitializingStructuredBinding:
case MarkingClassDllexported:
case BuildingBuiltinDumpStructCall:
case LambdaExpressionSubstitution:
return false;

// This function should never be called when Kind's value is Memoization.
Expand Down Expand Up @@ -962,6 +964,10 @@ void Sema::PrintInstantiationStack() {
case CodeSynthesisContext::Memoization:
break;

case CodeSynthesisContext::LambdaExpressionSubstitution:
Diags.Report(Active->PointOfInstantiation,
diag::note_lambda_substitution_here);
break;
case CodeSynthesisContext::ConstraintsCheck: {
unsigned DiagID = 0;
if (!Active->Entity) {
Expand Down Expand Up @@ -1017,6 +1023,7 @@ std::optional<TemplateDeductionInfo *> Sema::isSFINAEContext() const {
if (InNonInstantiationSFINAEContext)
return std::optional<TemplateDeductionInfo *>(nullptr);

bool SawLambdaSubstitution = false;
for (SmallVectorImpl<CodeSynthesisContext>::const_reverse_iterator
Active = CodeSynthesisContexts.rbegin(),
ActiveEnd = CodeSynthesisContexts.rend();
Expand All @@ -1038,6 +1045,15 @@ std::optional<TemplateDeductionInfo *> Sema::isSFINAEContext() const {
case CodeSynthesisContext::NestedRequirementConstraintsCheck:
// This is a template instantiation, so there is no SFINAE.
return std::nullopt;
case CodeSynthesisContext::LambdaExpressionSubstitution:
// [temp.deduct]p9
// A lambda-expression appearing in a function type or a template
// parameter is not considered part of the immediate context for the
// purposes of template argument deduction.

// We need to check parents.
SawLambdaSubstitution = true;
break;

case CodeSynthesisContext::DefaultTemplateArgumentInstantiation:
case CodeSynthesisContext::PriorTemplateArgumentSubstitution:
Expand All @@ -1050,12 +1066,17 @@ std::optional<TemplateDeductionInfo *> Sema::isSFINAEContext() const {

case CodeSynthesisContext::ExplicitTemplateArgumentSubstitution:
case CodeSynthesisContext::DeducedTemplateArgumentSubstitution:
// We're either substituting explicitly-specified template arguments,
// deduced template arguments. SFINAE applies unless we are in a lambda
// expression, see [temp.deduct]p9.
if (SawLambdaSubstitution)
return std::nullopt;
[[fallthrough]];
case CodeSynthesisContext::ConstraintSubstitution:
case CodeSynthesisContext::RequirementInstantiation:
case CodeSynthesisContext::RequirementParameterInstantiation:
// We're either substituting explicitly-specified template arguments,
// deduced template arguments, a constraint expression or a requirement
// in a requires expression, so SFINAE applies.
// SFINAE always applies in a constraint expression or a requirement
// in a requires expression.
assert(Active->DeductionInfo && "Missing deduction info pointer");
return Active->DeductionInfo;

Expand Down Expand Up @@ -1344,6 +1365,14 @@ namespace {
ExprResult TransformLambdaExpr(LambdaExpr *E) {
LocalInstantiationScope Scope(SemaRef, /*CombineWithOuterScope=*/true);
Sema::ConstraintEvalRAII<TemplateInstantiator> RAII(*this);

Sema::CodeSynthesisContext C;
C.Kind = clang::Sema::CodeSynthesisContext::LambdaExpressionSubstitution;
C.PointOfInstantiation = E->getBeginLoc();
SemaRef.pushCodeSynthesisContext(C);
auto PopCtx =
llvm::make_scope_exit([this] { SemaRef.popCodeSynthesisContext(); });

ExprResult Result = inherited::TransformLambdaExpr(E);
if (Result.isInvalid())
return Result;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ struct NoDefaultCtor {
template<typename T>
void defargs_in_template_unused(T t) {
auto l1 = [](const T& value = T()) { }; // expected-error{{no matching constructor for initialization of 'NoDefaultCtor'}} \
// expected-note {{in instantiation of default function argument expression for 'operator()<NoDefaultCtor>' required here}}
// expected-note {{in instantiation of default function argument expression for 'operator()<NoDefaultCtor>' required here}} \
// expected-note {{while substituting into a lambda expression here}}
l1(t);
}

Expand All @@ -45,7 +46,8 @@ template void defargs_in_template_unused(NoDefaultCtor); // expected-note{{in i
template<typename T>
void defargs_in_template_used() {
auto l1 = [](const T& value = T()) { }; // expected-error{{no matching constructor for initialization of 'NoDefaultCtor'}} \
// expected-note {{in instantiation of default function argument expression for 'operator()<NoDefaultCtor>' required here}}
// expected-note {{in instantiation of default function argument expression for 'operator()<NoDefaultCtor>' required here}} \
// expected-note {{while substituting into a lambda expression here}}
l1();
}

Expand Down
3 changes: 3 additions & 0 deletions clang/test/CXX/expr/expr.prim/expr.prim.lambda/p11-1y.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ auto init_kind_2 = [ec = ExplicitCopy()] {}; // expected-error {{no matching con
template<typename T> void init_kind_template() {
auto init_kind_1 = [ec(T())] {};
auto init_kind_2 = [ec = T()] {}; // expected-error {{no matching constructor}}
// expected-note@-1 {{while substituting into a lambda expression here}}
}
template void init_kind_template<int>();
template void init_kind_template<ExplicitCopy>(); // expected-note {{instantiation of}}
Expand All @@ -52,6 +53,7 @@ auto bad_init_6 = [a{overload_fn}] {}; // expected-error {{cannot deduce type fo
auto bad_init_7 = [a{{1}}] {}; // expected-error {{cannot deduce type for lambda capture 'a' from nested initializer list}}

template<typename...T> void pack_1(T...t) { (void)[a(t...)] {}; } // expected-error {{initializer missing for lambda capture 'a'}}
// expected-note@-1 {{while substituting into a lambda expression here}}
template void pack_1<>(); // expected-note {{instantiation of}}

// No lifetime-extension of the temporary here.
Expand All @@ -74,6 +76,7 @@ auto s = [s(move(S()))] {};

template<typename T> T instantiate_test(T t) {
[x(&t)]() { *x = 1; } (); // expected-error {{assigning to 'const char *'}}
// expected-note@-1 {{while substituting into a lambda expression here}}
return t;
}
int instantiate_test_1 = instantiate_test(0);
Expand Down
1 change: 1 addition & 0 deletions clang/test/CXX/expr/expr.prim/expr.prim.lambda/p23.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ void init_capture_pack_err(Args ...args) {
template<typename ...Args>
void init_capture_pack_multi(Args ...args) {
[as(args...)] {} (); // expected-error {{initializer missing for lambda capture 'as'}} expected-error {{multiple}}
// expected-note@-1 2{{while substituting into a lambda expression}}
}
template void init_capture_pack_multi(); // expected-note {{instantiation}}
template void init_capture_pack_multi(int);
Expand Down
1 change: 1 addition & 0 deletions clang/test/CXX/expr/expr.prim/expr.prim.lambda/p4.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ void test_result_type(int N) {
template <typename T>
void test_result_type_tpl(int N) {
auto l1 = []() -> T {}; // expected-error{{incomplete result type 'Incomplete' in lambda expression}}
// expected-note@-1{{while substituting into a lambda expression here}}
typedef int vla[N];
auto l2 = []() -> vla {}; // expected-error{{function cannot return array type 'vla' (aka 'int[N]')}}
}
Expand Down
2 changes: 2 additions & 0 deletions clang/test/CXX/stmt.stmt/stmt.select/stmt.if/p2.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ namespace generic_lambda {
[](auto x) {
if constexpr (sizeof(T) == 1 && sizeof(x) == 1)
T::error(); // expected-error 2{{'::'}}
// expected-note@-3 2{{while substituting into a lambda expression here}}
} (0);
}

Expand All @@ -88,6 +89,7 @@ namespace generic_lambda {
if constexpr (sizeof(T) == 1)
if constexpr (sizeof(x) == 1)
T::error(); // expected-error {{'::'}}
// expected-note@-4 {{while substituting into a lambda expression here}}
} (0);
}

Expand Down
56 changes: 56 additions & 0 deletions clang/test/CXX/temp/temp.deduct/p9.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// RUN: %clang_cc1 -std=c++20 -verify %s
// [temp.deduct.p9]
// A lambda-expression appearing in a function type or a template parameter is
// not considered part of the immediate context for the purposes of template
// argument deduction.
// [Note: The intent is to avoid requiring implementations to deal with
// substitution failure involving arbitrary statements.]
template <class T>
auto f(T) -> decltype([]() { T::invalid; } ());
void f(...);
void test_f() {
f(0); // expected-error@-3 {{type 'int' cannot be used prior to '::'}}
// expected-note@-1 {{while substituting deduced template arguments}}
// expected-note@-5 {{while substituting into a lambda expression here}}
}

template <class T, unsigned = sizeof([]() { T::invalid; })>
void g(T);
void g(...);
void test_g() {
g(0); // expected-error@-4 {{type 'int' cannot be used prior to '::'}}
// expected-note@-4 {{in instantiation of default argument}}
// expected-note@-2 {{while substituting deduced template arguments}}
// expected-note@-7 {{while substituting into a lambda expression here}}
}

template <class T>
auto h(T) -> decltype([x = T::invalid]() { });
void h(...);
void test_h() {
h(0); // expected-error@-3 {{type 'int' cannot be used prior to '::'}}
// expected-note@-1 {{while substituting deduced template arguments}}
// expected-note@-5 {{while substituting into a lambda expression here}}
}

template <class T>
auto i(T) -> decltype([]() -> typename T::invalid { });
void i(...);
void test_i() {
i(0); // expected-error@-3 {{type 'int' cannot be used prior to '::'}}
// expected-note@-1 {{while substituting deduced template arguments}}
// expected-note@-5 {{while substituting into a lambda expression here}}
}


// In this example, the lambda itself is not part of an immediate context, but
// substitution to the lambda expression succeeds, producing dependent
// `decltype(x.invalid)`. The call to the lambda, however, is in the immediate context
// and it produces a SFINAE failure. Hence, we pick the second overload
// and don't produce any errors.
template <class T>
auto j(T t) -> decltype([](auto x) -> decltype(x.invalid) { } (t)); // #1
void j(...); // #2
void test_j() {
j(0); // deduction fails on #1, calls #2.
}
1 change: 1 addition & 0 deletions clang/test/CXX/temp/temp.param/p15-cxx0x.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ template<typename...Ts> struct A {
B() {
consume([]{
int arr[Vs]; // expected-error {{negative size}}
// expected-note@-2 {{while substituting into a lambda expression here}}
}...);
}
};
Expand Down
1 change: 1 addition & 0 deletions clang/test/PCH/cxx1y-init-captures.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ int y = counter();
void g() {
f(0); // ok
// expected-error@18 {{lvalue of type 'const char *const'}}
// expected-note@18 {{substituting into a lambda}}
f("foo"); // expected-note {{here}}
}

Expand Down
10 changes: 5 additions & 5 deletions clang/test/SemaCXX/cxx1y-generic-lambdas-capturing.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -563,8 +563,8 @@ struct X {

int g() {
auto L = [=](auto a) {
return [](int i) { // expected-note {{explicitly capture 'this'}}
return [=](auto b) {
return [](int i) { // expected-note {{explicitly capture 'this'}} expected-note {{while substituting into a lambda}}
return [=](auto b) { // expected-note {{while substituting into a lambda}}
f(decltype(a){}); //expected-error{{this}}
int x = i;
};
Expand All @@ -587,8 +587,8 @@ struct X {

int g() {
auto L = [=](auto a) {
return [](auto b) { // expected-note {{explicitly capture 'this'}}
return [=](int i) {
return [](auto b) { // expected-note {{explicitly capture 'this'}} expected-note {{while substituting into a lambda}}
return [=](int i) { // expected-note {{while substituting into a lambda}}
f(b);
f(decltype(a){}); //expected-error{{this}}
};
Expand All @@ -612,7 +612,7 @@ struct X {
int g() {
auto L = [=](auto a) {
return [](auto b) { // expected-note {{explicitly capture 'this'}}
return [=](int i) {
return [=](int i) { // expected-note {{while substituting into a lambda}}
f(b); //expected-error{{this}}
f(decltype(a){});
};
Expand Down
2 changes: 1 addition & 1 deletion clang/test/SemaCXX/cxx1y-generic-lambdas.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,7 @@ int test() {
{
int i = 10; //expected-note 3{{declared here}}
auto L = [](auto a) {
return [](auto b) { //expected-note 3{{begins here}} expected-note 6 {{capture 'i' by}} expected-note 6 {{default capture by}}
return [](auto b) { //expected-note 3{{begins here}} expected-note 6 {{capture 'i' by}} expected-note 6 {{default capture by}} expected-note {{while substituting into a lambda}}
i = b; //expected-error 3{{cannot be implicitly captured}}
return b;
};
Expand Down
12 changes: 6 additions & 6 deletions clang/test/SemaCXX/cxx1y-init-captures.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,17 @@ namespace variadic_expansion {
return a;
}() ...);
};
auto N2 = [x = y, //expected-note3{{begins here}} expected-note 6 {{default capture by}}
auto N2 = [x = y, //expected-note3{{begins here}} expected-note 6 {{default capture by}} expected-note 2 {{substituting into a lambda}}
&z = y, n = f(t...),
o = f([&a(t)](T& ... t)->decltype(auto) { return a; }(t...)...)](T& ... s) { // expected-note 6 {{capture 't' by}}
fv([&a(t)]()->decltype(auto) { //expected-error 3{{captured}}
fv([&a(t)]()->decltype(auto) { //expected-error 3{{captured}} expected-note 2{{substituting into a lambda}}
return a;
}() ...);
};

}

void h(int i, char c) { g(i, c); } //expected-note{{in instantiation}}
void h(int i, char c) { g(i, c); } //expected-note 2{{in instantiation}}
}

namespace odr_use_within_init_capture {
Expand Down Expand Up @@ -117,7 +117,7 @@ int test(T t = T{}) {
}
{ // will need to capture x in outer lambda
const T x = 10; //expected-note {{declared}}
auto L = [z = x](char a) { //expected-note {{begins}} expected-note 2 {{capture 'x' by}} expected-note 2 {{default capture by}}
auto L = [z = x](char a) { //expected-note {{begins}} expected-note 2 {{capture 'x' by}} expected-note 2 {{default capture by}} expected-note {{substituting into a lambda}}
auto M = [&y = x](T b) { //expected-error {{cannot be implicitly captured}}
return y;
};
Expand Down Expand Up @@ -145,7 +145,7 @@ int test(T t = T{}) {
}
{ // will need to capture x in outer lambda
const int x = 10; //expected-note {{declared}}
auto L = [z = x](char a) { //expected-note {{begins}} expected-note 2 {{capture 'x' by}} expected-note 2 {{default capture by}}
auto L = [z = x](char a) { //expected-note {{begins}} expected-note 2 {{capture 'x' by}} expected-note 2 {{default capture by}} expected-note {{substituting into a lambda}}
auto M = [&y = x](T b) { //expected-error {{cannot be implicitly captured}}
return y;
};
Expand All @@ -164,7 +164,7 @@ int test(T t = T{}) {
return 0;
}

int run = test(); //expected-note {{instantiation}}
int run = test(); //expected-note 2 {{instantiation}}

}

Expand Down
4 changes: 2 additions & 2 deletions clang/test/SemaCXX/cxx1z-lambda-star-this.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ class B {
template <class T = int>
void foo() {
(void)[this] { return x; };
(void)[*this] { return x; }; //expected-error2{{call to deleted}}
(void)[*this] { return x; }; //expected-error2{{call to deleted}} expected-note {{while substituting into a lambda}}
}

B() = default;
Expand All @@ -63,7 +63,7 @@ class B {
public:
template <class T = int>
auto foo() {
const auto &L = [*this](auto a) mutable { //expected-error{{call to deleted}}
const auto &L = [*this](auto a) mutable { //expected-error{{call to deleted}} expected-note {{while substituting into a lambda}}
d += a;
return [this](auto b) { return d += b; };
};
Expand Down
3 changes: 2 additions & 1 deletion clang/test/SemaCXX/cxx20-decomposition.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,8 @@ namespace ODRUseTests {
(void)[&b](auto c) { return b + [](auto) { // expected-note 3{{lambda expression begins here}} \
// expected-note 6{{capture 'a'}} \
// expected-note 6{{default capture}} \
// expected-note {{in instantiation}}
// expected-note {{in instantiation}} \
// expected-note {{while substituting into a lambda}}
return a; // expected-error 3{{variable 'a' cannot be implicitly captured}}
}(0); }(0); // expected-note 2{{in instantiation}}
}
Expand Down

0 comments on commit 629170f

Please sign in to comment.