Skip to content

[clang] Reland: Instantiate concepts with sugared template arguments #101782

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 1 commit into from
Aug 5, 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
1 change: 1 addition & 0 deletions clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ Improvements to Clang's diagnostics
- Clang now diagnoses undefined behavior in constant expressions more consistently. This includes invalid shifts, and signed overflow in arithmetic.

- -Wdangling-assignment-gsl is enabled by default.
- Clang now does a better job preserving the template arguments as written when specializing concepts.

Improvements to Clang's time-trace
----------------------------------
Expand Down
6 changes: 3 additions & 3 deletions clang/include/clang/Sema/Template.h
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,8 @@ enum class TemplateSubstitutionKind : char {
/// Replaces the current 'innermost' level with the provided argument list.
/// This is useful for type deduction cases where we need to get the entire
/// list from the AST, but then add the deduced innermost list.
void replaceInnermostTemplateArguments(Decl *AssociatedDecl, ArgList Args) {
void replaceInnermostTemplateArguments(Decl *AssociatedDecl, ArgList Args,
bool Final = false) {
assert((!TemplateArgumentLists.empty() || NumRetainedOuterLevels) &&
"Replacing in an empty list?");

Expand All @@ -246,8 +247,7 @@ enum class TemplateSubstitutionKind : char {
TemplateArgumentLists[0].Args = Args;
} else {
--NumRetainedOuterLevels;
TemplateArgumentLists.push_back(
{{AssociatedDecl, /*Final=*/false}, Args});
TemplateArgumentLists.push_back({{AssociatedDecl, Final}, Args});
}
}

Expand Down
11 changes: 7 additions & 4 deletions clang/lib/Sema/SemaConcept.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -414,7 +414,8 @@ DiagRecursiveConstraintEval(Sema &S, llvm::FoldingSetNodeID &ID,
E->Profile(ID, S.Context, /*Canonical=*/true);
for (const auto &List : MLTAL)
for (const auto &TemplateArg : List.Args)
TemplateArg.Profile(ID, S.Context);
S.Context.getCanonicalTemplateArgument(TemplateArg)
.Profile(ID, S.Context);

// Note that we have to do this with our own collection, because there are
// times where a constraint-expression check can cause us to need to evaluate
Expand Down Expand Up @@ -638,8 +639,8 @@ bool Sema::CheckConstraintSatisfaction(
// here.
llvm::SmallVector<TemplateArgument, 4> FlattenedArgs;
for (auto List : TemplateArgsLists)
FlattenedArgs.insert(FlattenedArgs.end(), List.Args.begin(),
List.Args.end());
for (const TemplateArgument &Arg : List.Args)
FlattenedArgs.emplace_back(Context.getCanonicalTemplateArgument(Arg));

llvm::FoldingSetNodeID ID;
ConstraintSatisfaction::Profile(ID, Context, Template, FlattenedArgs);
Expand Down Expand Up @@ -823,6 +824,8 @@ Sema::SetupConstraintCheckingTemplateArgumentsAndScope(
/*RelativeToPrimary=*/true,
/*Pattern=*/nullptr,
/*ForConstraintInstantiation=*/true);
if (TemplateArgs)
MLTAL.replaceInnermostTemplateArguments(FD, *TemplateArgs, /*Final=*/true);
if (SetupConstraintScope(FD, TemplateArgs, MLTAL, Scope))
return std::nullopt;

Expand Down Expand Up @@ -1476,7 +1479,7 @@ static bool substituteParameterMappings(Sema &S, NormalizedConstraint &N,
const ConceptSpecializationExpr *CSE) {
MultiLevelTemplateArgumentList MLTAL = S.getTemplateInstantiationArgs(
CSE->getNamedConcept(), CSE->getNamedConcept()->getLexicalDeclContext(),
/*Final=*/false, CSE->getTemplateArguments(),
/*Final=*/true, CSE->getTemplateArguments(),
/*RelativeToPrimary=*/true,
/*Pattern=*/nullptr,
/*ForConstraintInstantiation=*/true);
Expand Down
5 changes: 2 additions & 3 deletions clang/lib/Sema/SemaExprCXX.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9330,14 +9330,13 @@ Sema::BuildExprRequirement(
// be satisfied.
TemplateParameterList *TPL =
ReturnTypeRequirement.getTypeConstraintTemplateParameterList();
QualType MatchedType =
Context.getReferenceQualifiedType(E).getCanonicalType();
QualType MatchedType = Context.getReferenceQualifiedType(E);
llvm::SmallVector<TemplateArgument, 1> Args;
Args.push_back(TemplateArgument(MatchedType));

auto *Param = cast<TemplateTypeParmDecl>(TPL->getParam(0));

MultiLevelTemplateArgumentList MLTAL(Param, Args, /*Final=*/false);
MultiLevelTemplateArgumentList MLTAL(Param, Args, /*Final=*/true);
MLTAL.addOuterRetainedLevels(TPL->getDepth());
const TypeConstraint *TC = Param->getTypeConstraint();
assert(TC && "Type Constraint cannot be null here");
Expand Down
10 changes: 5 additions & 5 deletions clang/lib/Sema/SemaTemplate.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4358,13 +4358,13 @@ Sema::CheckConceptTemplateId(const CXXScopeSpec &SS,

auto *CSD = ImplicitConceptSpecializationDecl::Create(
Context, NamedConcept->getDeclContext(), NamedConcept->getLocation(),
CanonicalConverted);
SugaredConverted);
ConstraintSatisfaction Satisfaction;
bool AreArgsDependent =
TemplateSpecializationType::anyDependentTemplateArguments(
*TemplateArgs, CanonicalConverted);
MultiLevelTemplateArgumentList MLTAL(NamedConcept, CanonicalConverted,
/*Final=*/false);
*TemplateArgs, SugaredConverted);
MultiLevelTemplateArgumentList MLTAL(NamedConcept, SugaredConverted,
/*Final=*/true);
LocalInstantiationScope Scope(*this);

EnterExpressionEvaluationContext EECtx{
Expand Down Expand Up @@ -5582,7 +5582,7 @@ bool Sema::CheckTemplateArgumentList(
CXXThisScopeRAII(*this, RD, ThisQuals, RD != nullptr);

MultiLevelTemplateArgumentList MLTAL = getTemplateInstantiationArgs(
Template, NewContext, /*Final=*/false, CanonicalConverted,
Template, NewContext, /*Final=*/true, SugaredConverted,
/*RelativeToPrimary=*/true,
/*Pattern=*/nullptr,
/*ForConceptInstantiation=*/true);
Expand Down
16 changes: 8 additions & 8 deletions clang/lib/Sema/SemaTemplateDeduction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3078,7 +3078,7 @@ CheckDeducedArgumentConstraints(Sema &S, TemplateDeclT *Template,
// If we don't need to replace the deduced template arguments,
// we can add them immediately as the inner-most argument list.
if (!DeducedArgsNeedReplacement(Template))
Innermost = CanonicalDeducedArgs;
Innermost = SugaredDeducedArgs;

MultiLevelTemplateArgumentList MLTAL = S.getTemplateInstantiationArgs(
Template, Template->getDeclContext(), /*Final=*/false, Innermost,
Expand All @@ -3090,7 +3090,7 @@ CheckDeducedArgumentConstraints(Sema &S, TemplateDeclT *Template,
// not class-scope explicit specialization, so replace with Deduced Args
// instead of adding to inner-most.
if (!Innermost)
MLTAL.replaceInnermostTemplateArguments(Template, CanonicalDeducedArgs);
MLTAL.replaceInnermostTemplateArguments(Template, SugaredDeducedArgs);

if (S.CheckConstraintSatisfaction(Template, AssociatedConstraints, MLTAL,
Info.getLocation(),
Expand Down Expand Up @@ -3913,13 +3913,13 @@ TemplateDeductionResult Sema::FinishTemplateArgumentDeduction(
(CanonicalBuilder.size() ==
FunctionTemplate->getTemplateParameters()->size())) {
if (CheckInstantiatedFunctionTemplateConstraints(
Info.getLocation(), Specialization, CanonicalBuilder,
Info.getLocation(), Specialization, SugaredBuilder,
Info.AssociatedConstraintsSatisfaction))
return TemplateDeductionResult::MiscellaneousDeductionFailure;

if (!Info.AssociatedConstraintsSatisfaction.IsSatisfied) {
Info.reset(Info.takeSugared(),
TemplateArgumentList::CreateCopy(Context, CanonicalBuilder));
Info.reset(TemplateArgumentList::CreateCopy(Context, SugaredBuilder),
Info.takeCanonical());
return TemplateDeductionResult::ConstraintsNotSatisfied;
}
}
Expand Down Expand Up @@ -4993,8 +4993,8 @@ static bool CheckDeducedPlaceholderConstraints(Sema &S, const AutoType &Type,
/*PartialTemplateArgs=*/false,
SugaredConverted, CanonicalConverted))
return true;
MultiLevelTemplateArgumentList MLTAL(Concept, CanonicalConverted,
/*Final=*/false);
MultiLevelTemplateArgumentList MLTAL(Concept, SugaredConverted,
/*Final=*/true);
// Build up an EvaluationContext with an ImplicitConceptSpecializationDecl so
// that the template arguments of the constraint can be preserved. For
// example:
Expand All @@ -5008,7 +5008,7 @@ static bool CheckDeducedPlaceholderConstraints(Sema &S, const AutoType &Type,
S, Sema::ExpressionEvaluationContext::Unevaluated,
ImplicitConceptSpecializationDecl::Create(
S.getASTContext(), Concept->getDeclContext(), Concept->getLocation(),
CanonicalConverted));
SugaredConverted));
if (S.CheckConstraintSatisfaction(Concept, {Concept->getConstraintExpr()},
MLTAL, TypeLoc.getLocalSourceRange(),
Satisfaction))
Expand Down
2 changes: 1 addition & 1 deletion clang/lib/Serialization/ASTReaderDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2391,7 +2391,7 @@ void ASTDeclReader::VisitImplicitConceptSpecializationDecl(
VisitDecl(D);
llvm::SmallVector<TemplateArgument, 4> Args;
for (unsigned I = 0; I < D->NumTemplateArgs; ++I)
Args.push_back(Record.readTemplateArgument(/*Canonicalize=*/true));
Args.push_back(Record.readTemplateArgument(/*Canonicalize=*/false));
D->setTemplateArguments(Args);
}

Expand Down
10 changes: 6 additions & 4 deletions clang/test/AST/ast-dump-concepts.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,9 @@ struct Foo {
// CHECK: TemplateTypeParmDecl {{.*}} referenced Concept {{.*}} 'binary_concept'
// CHECK-NEXT: `-ConceptSpecializationExpr {{.*}} <col:13, col:31> 'bool' Concept {{.*}} 'binary_concept'
// CHECK-NEXT: |-ImplicitConceptSpecializationDecl {{.*}} <line:13:9> col:9
// CHECK-NEXT: | |-TemplateArgument type 'type-parameter-1-0'
// CHECK-NEXT: | | `-TemplateTypeParmType {{.*}} 'type-parameter-1-0' dependent {{.*}}depth 1 index 0
// CHECK-NEXT: | |-TemplateArgument type 'R'
// CHECK-NEXT: | | `-TemplateTypeParmType {{.*}} 'R' dependent {{.*}}depth 1 index 0
// CHECK-NEXT: | | `-TemplateTypeParm {{.*}} 'R'
// CHECK-NEXT: | `-TemplateArgument type 'int'
// CHECK-NEXT: | `-BuiltinType {{.*}} 'int'
// CHECK-NEXT: |-TemplateArgument {{.*}} type 'R'
Expand All @@ -35,8 +36,9 @@ struct Foo {
// CHECK: TemplateTypeParmDecl {{.*}} referenced Concept {{.*}} 'unary_concept'
// CHECK-NEXT: `-ConceptSpecializationExpr {{.*}} <col:13> 'bool'
// CHECK-NEXT: |-ImplicitConceptSpecializationDecl {{.*}} <line:10:9> col:9
// CHECK-NEXT: | `-TemplateArgument type 'type-parameter-1-0'
// CHECK-NEXT: | `-TemplateTypeParmType {{.*}} 'type-parameter-1-0' dependent {{.*}}depth 1 index 0
// CHECK-NEXT: | `-TemplateArgument type 'R'
// CHECK-NEXT: | `-TemplateTypeParmType {{.*}} 'R' dependent {{.*}}depth 1 index 0
// CHECK-NEXT: | `-TemplateTypeParm {{.*}} 'R'
template <unary_concept R>
Foo(R);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,14 @@ using r2i2 = r2<A>; // expected-error{{constraints not satisfied for class templ
using r2i3 = r2<D>;
using r2i4 = r2<const D>; // expected-error{{constraints not satisfied for class template 'r2' [with T = const D]}}

template<typename T> requires requires { { sizeof(T) }; } // expected-note{{because 'sizeof(T)' would be invalid: invalid application of 'sizeof' to an incomplete type 'void'}} expected-note{{because 'sizeof(T)' would be invalid: invalid application of 'sizeof' to an incomplete type 'nonexistent'}}
template<typename T> requires requires { { sizeof(T) }; } // expected-note{{because 'sizeof(T)' would be invalid: invalid application of 'sizeof' to an incomplete type 'void'}} expected-note{{because 'sizeof(T)' would be invalid: invalid application of 'sizeof' to an incomplete type 'class nonexistent'}}
struct r3 {};

using r3i1 = r3<int>;
using r3i2 = r3<A>;
using r3i3 = r3<A &>;
using r3i4 = r3<void>; // expected-error{{constraints not satisfied for class template 'r3' [with T = void]}}
using r3i4 = r3<class nonexistent>; // expected-error{{constraints not satisfied for class template 'r3' [with T = nonexistent]}}
using r3i4 = r3<class nonexistent>; // expected-error{{constraints not satisfied for class template 'r3' [with T = class nonexistent]}}

// Non-dependent expressions

Expand Down Expand Up @@ -149,7 +149,7 @@ namespace std_example {
template<typename T> constexpr bool is_same_v<T, T> = true;

template<typename T, typename U> concept same_as = is_same_v<T, U>;
// expected-note@-1 {{because 'is_same_v<int, int *>' evaluated to false}}
// expected-note@-1 {{because 'is_same_v<int, typename T2::inner>' evaluated to false}}

static_assert(C1<int>);
static_assert(C1<int*>);
Expand All @@ -173,9 +173,9 @@ namespace std_example {
int operator *() { return 0; }
};
static_assert(C2<T1>);
template<C2 T> struct C2_check {}; // expected-note{{because 'int' does not satisfy 'C2'}} expected-note{{because 'std_example::T2' does not satisfy 'C2'}}
template<C2 T> struct C2_check {}; // expected-note{{because 'int' does not satisfy 'C2'}} expected-note{{because 'T2' does not satisfy 'C2'}}
using c2c1 = C2_check<int>; // expected-error{{constraints not satisfied for class template 'C2_check' [with T = int]}}
using c2c2 = C2_check<T2>; // expected-error{{constraints not satisfied for class template 'C2_check' [with T = std_example::T2]}}
using c2c2 = C2_check<T2>; // expected-error{{constraints not satisfied for class template 'C2_check' [with T = T2]}}

template<typename T>
void g(T t) noexcept(sizeof(T) == 1) {}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ using r4i = X<void>::r4<int>; // expected-error{{constraints not satisfied for c

// C++ [expr.prim.req.nested] Examples
namespace std_example {
template<typename U> concept C1 = sizeof(U) == 1; // expected-note{{because 'sizeof(int) == 1' (4 == 1) evaluated to false}}
template<typename U> concept C1 = sizeof(U) == 1; // expected-note{{because 'sizeof(decltype(+t)) == 1' (4 == 1) evaluated to false}}
template<typename T> concept D =
requires (T t) {
requires C1<decltype (+t)>; // expected-note{{because 'decltype(+t)' (aka 'int') does not satisfy 'C1'}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,14 +39,14 @@ using r2i4 = r2<const D>; // expected-error{{constraints not satisfied for class

template<typename T> requires requires { sizeof(T); }
// expected-note@-1{{because 'sizeof(T)' would be invalid: invalid application of 'sizeof' to an incomplete type 'void'}}
// expected-note@-2{{because 'sizeof(T)' would be invalid: invalid application of 'sizeof' to an incomplete type 'nonexistent'}}
// expected-note@-2{{because 'sizeof(T)' would be invalid: invalid application of 'sizeof' to an incomplete type 'class nonexistent'}}
struct r3 {};

using r3i1 = r3<int>;
using r3i2 = r3<A>;
using r3i3 = r3<A &>;
using r3i4 = r3<void>; // expected-error{{constraints not satisfied for class template 'r3' [with T = void]}}
using r3i4 = r3<class nonexistent>; // expected-error{{constraints not satisfied for class template 'r3' [with T = nonexistent]}}
using r3i4 = r3<class nonexistent>; // expected-error{{constraints not satisfied for class template 'r3' [with T = class nonexistent]}}

template<typename T> requires requires (T t) { 0; "a"; (void)'a'; }
struct r4 {};
Expand Down
12 changes: 6 additions & 6 deletions clang/test/CXX/expr/expr.prim/expr.prim.req/type-requirement.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -182,14 +182,14 @@ namespace std_example {
static_assert(C1<has_inner_and_type> && C2<has_inner_and_type> && C3<has_inner_and_type>);
template<C1 T> struct C1_check {};
// expected-note@-1 {{because 'int' does not satisfy 'C1'}}
// expected-note@-2 {{because 'std_example::has_type' does not satisfy 'C1'}}
// expected-note@-2 {{because 'has_type' does not satisfy 'C1'}}
template<C2 T> struct C2_check {};
// expected-note@-1 {{because 'std_example::has_inner' does not satisfy 'C2'}}
// expected-note@-1 {{because 'has_inner' does not satisfy 'C2'}}
template<C3 T> struct C3_check {};
// expected-note@-1 {{because 'void' does not satisfy 'C3'}}
using c1 = C1_check<int>; // expected-error{{constraints not satisfied for class template 'C1_check' [with T = int]}}
using c2 = C1_check<has_type>; // expected-error{{constraints not satisfied for class template 'C1_check' [with T = std_example::has_type]}}
using c3 = C2_check<has_inner>; // expected-error{{constraints not satisfied for class template 'C2_check' [with T = std_example::has_inner]}}
using c2 = C1_check<has_type>; // expected-error{{constraints not satisfied for class template 'C1_check' [with T = has_type]}}
using c3 = C2_check<has_inner>; // expected-error{{constraints not satisfied for class template 'C2_check' [with T = has_inner]}}
using c4 = C3_check<void>; // expected-error{{constraints not satisfied for class template 'C3_check' [with T = void]}}
}

Expand All @@ -199,10 +199,10 @@ template <typename T> concept C = requires { requires requires { T::a; }; };
// expected-note@-1 {{because 'T::a' would be invalid: no member named 'a' in 'PR48656::T1'}}

template <C...> struct A {};
// expected-note@-1 {{because 'PR48656::T1' does not satisfy 'C'}}
// expected-note@-1 {{because 'T1' does not satisfy 'C'}}

struct T1 {};
template struct A<T1>; // expected-error {{constraints not satisfied for class template 'A' [with $0 = <PR48656::T1>]}}
template struct A<T1>; // expected-error {{constraints not satisfied for class template 'A' [with $0 = <T1>]}}

struct T2 { static constexpr bool a = false; };
template struct A<T2>;
Expand Down
2 changes: 1 addition & 1 deletion clang/test/CXX/temp/temp.constr/temp.constr.normal/p1.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ template<typename T> requires Bar<T> && true struct S<T> { };

template<typename T> concept True2 = sizeof(T) >= 0;
template<typename T> concept Foo2 = True2<T*>;
// expected-error@-1{{'type name' declared as a pointer to a reference of type 'type-parameter-0-0 &'}}
// expected-error@-1{{'type name' declared as a pointer to a reference of type 'T &'}}
template<typename T> concept Bar2 = Foo2<T&>;
// expected-note@-1{{while substituting into concept arguments here; substitution failures not allowed in concept arguments}}
template<typename T> requires Bar2<T> struct S2 { };
Expand Down
4 changes: 2 additions & 2 deletions clang/test/CXX/temp/temp.param/p10-2a.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,8 @@ concept OneOf = (is_same_v<T, Ts> || ...);
// expected-note@-5 {{and 'is_same_v<short, char>' evaluated to false}}
// expected-note@-6 3{{because 'is_same_v<int, char[1]>' evaluated to false}}
// expected-note@-7 3{{and 'is_same_v<int, char[2]>' evaluated to false}}
// expected-note@-8 2{{because 'is_same_v<std::nullptr_t, char>' evaluated to false}}
// expected-note@-9 2{{and 'is_same_v<std::nullptr_t, int>' evaluated to false}}
// expected-note@-8 2{{because 'is_same_v<decltype(nullptr), char>' evaluated to false}}
// expected-note@-9 2{{and 'is_same_v<decltype(nullptr), int>' evaluated to false}}

template<OneOf<char[1], char[2]> T, OneOf<int, long, char> U>
// expected-note@-1 2{{because 'OneOf<char, char[1], char[2]>' evaluated to false}}
Expand Down
Loading
Loading