Skip to content
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

[Clang] Implement CWG2369 "Ordering between constraints and substitution" #102857

Open
wants to merge 25 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
1119f0a
[Clang][NFCI] Slightly refactor getTemplateInstantiationArgs()
zyn0217 Aug 12, 2024
ca00718
Merge branch 'main' into cwg-2369-2
zyn0217 Aug 13, 2024
2dd772d
Resolve a merge conflict
zyn0217 Aug 13, 2024
96bf64c
CWG 2369
zyn0217 Aug 11, 2024
6b7072d
Rectify diagnostics
zyn0217 Aug 12, 2024
bb31d36
Instantiate function parameters as needed
zyn0217 Aug 12, 2024
631be75
Remove debugging logs
zyn0217 Aug 17, 2024
7d484e2
Revert unrelated changes
zyn0217 Aug 17, 2024
c515407
Fix tests
zyn0217 Aug 17, 2024
a9da5a3
Fix libcxx tests
zyn0217 Aug 17, 2024
cebe706
Instantiate the decls as we substituting constraints
zyn0217 Aug 18, 2024
e0b21a5
[libc++][chono] Use hidden friends for leap_second comparison.
mordante Aug 18, 2024
30dc1d1
clang-format
zyn0217 Aug 19, 2024
9d3981f
Merge branch 'main' into cwg-2369
zyn0217 Aug 21, 2024
afb8a90
Merge branch 'main' into cwg-2369
zyn0217 Aug 28, 2024
6a5085a
Quote the standard wordings, update the DR status page
zyn0217 Aug 28, 2024
974a0ad
Revert unrelated changes
zyn0217 Aug 28, 2024
d19e700
oops, I forgot it
zyn0217 Aug 28, 2024
5be9921
Merge branch 'main' into cwg-2369
zyn0217 Oct 8, 2024
8ace714
Simplify the implementation
zyn0217 Oct 8, 2024
da61fab
Add parentheses to sizeof expression
zyn0217 Oct 8, 2024
18da86a
Merge branch 'main' into cwg-2369
zyn0217 Oct 9, 2024
1b8f0f6
Revert comment changes
zyn0217 Oct 9, 2024
ef5acad
Rebase on top of the new getTemplateInstantiationArgs()
zyn0217 Oct 9, 2024
6fc2f7d
Merge branch 'main' into cwg-2369
zyn0217 Oct 9, 2024
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
15 changes: 8 additions & 7 deletions clang/include/clang/Sema/Sema.h
Original file line number Diff line number Diff line change
Expand Up @@ -13007,11 +13007,11 @@ class Sema final : public SemaBase {
/// instantiation arguments.
///
/// \param DC In the event we don't HAVE a declaration yet, we instead provide
/// the decl context where it will be created. In this case, the `Innermost`
/// should likely be provided. If ND is non-null, this is ignored.
/// the decl context where it will be created. In this case, the \p
/// Innermost should likely be provided. If \p ND is non-null, this is ignored.
///
/// \param Innermost if non-NULL, specifies a template argument list for the
/// template declaration passed as ND.
/// template declaration passed as \p ND.
///
/// \param RelativeToPrimary true if we should get the template
/// arguments relative to the primary template, even when we're
Expand Down Expand Up @@ -13302,6 +13302,7 @@ class Sema final : public SemaBase {
ExprResult
SubstConstraintExpr(Expr *E,
const MultiLevelTemplateArgumentList &TemplateArgs);

// Unlike the above, this does not evaluates constraints.
ExprResult SubstConstraintExprWithoutSatisfaction(
Expr *E, const MultiLevelTemplateArgumentList &TemplateArgs);
Expand Down Expand Up @@ -14416,10 +14417,10 @@ class Sema final : public SemaBase {
const MultiLevelTemplateArgumentList &TemplateArgs,
SourceRange TemplateIDRange);

bool CheckInstantiatedFunctionTemplateConstraints(
SourceLocation PointOfInstantiation, FunctionDecl *Decl,
ArrayRef<TemplateArgument> TemplateArgs,
ConstraintSatisfaction &Satisfaction);
bool CheckFunctionTemplateConstraints(SourceLocation PointOfInstantiation,
FunctionDecl *Decl,
ArrayRef<TemplateArgument> TemplateArgs,
ConstraintSatisfaction &Satisfaction);

/// \brief Emit diagnostics explaining why a constraint expression was deemed
/// unsatisfied.
Expand Down
3 changes: 3 additions & 0 deletions clang/include/clang/Sema/Template.h
Original file line number Diff line number Diff line change
Expand Up @@ -522,6 +522,9 @@ enum class TemplateSubstitutionKind : char {
llvm::PointerUnion<Decl *, DeclArgumentPack *> *
findInstantiationOf(const Decl *D);

llvm::PointerUnion<Decl *, DeclArgumentPack *> *
findInstantiationUnsafe(const Decl *D);

void InstantiatedLocal(const Decl *D, Decl *Inst);
void InstantiatedLocalPackArg(const Decl *D, VarDecl *Inst);
void MakeInstantiatedLocalArgPack(const Decl *D);
Expand Down
44 changes: 41 additions & 3 deletions clang/lib/Sema/SemaConcept.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -847,7 +847,7 @@ bool Sema::CheckFunctionConstraints(const FunctionDecl *FD,
bool ForOverloadResolution) {
// Don't check constraints if the function is dependent. Also don't check if
// this is a function template specialization, as the call to
// CheckinstantiatedFunctionTemplateConstraints after this will check it
// CheckInstantiatedFunctionTemplateConstraints after this will check it
// better.
if (FD->isDependentContext() ||
FD->getTemplatedKind() ==
Expand Down Expand Up @@ -1109,12 +1109,50 @@ bool Sema::EnsureTemplateArgumentListConstraints(
return false;
}

bool Sema::CheckInstantiatedFunctionTemplateConstraints(
static bool CheckFunctionConstraintsWithoutInstantiation(
Sema &SemaRef, SourceLocation PointOfInstantiation,
FunctionTemplateDecl *Template, ArrayRef<TemplateArgument> TemplateArgs,
ConstraintSatisfaction &Satisfaction) {
SmallVector<const Expr *, 3> TemplateAC;
Template->getAssociatedConstraints(TemplateAC);
if (TemplateAC.empty()) {
Satisfaction.IsSatisfied = true;
return false;
}

LocalInstantiationScope Scope(SemaRef);

FunctionDecl *FD = Template->getTemplatedDecl();
// Collect the list of template arguments relative to the 'primary'
// template. We need the entire list, since the constraint is completely
// uninstantiated at this point.
MultiLevelTemplateArgumentList MLTAL =
SemaRef.getTemplateInstantiationArgs(FD, /*DC=*/nullptr,
/*Final=*/false,
/*Innermost=*/TemplateArgs,
/*RelativeToPrimary=*/true,
/*ForConstraintInstantiation=*/true);

std::optional<Sema::CXXThisScopeRAII> ThisScope;
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we need to point CurContext to the function here as well?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, I think so - but I couldn't find a case where that affects anything.

if (auto *Method = dyn_cast<CXXMethodDecl>(FD))
ThisScope.emplace(SemaRef, /*Record=*/Method->getParent(),
/*ThisQuals=*/Method->getMethodQualifiers());
Comment on lines +1137 to +1139
Copy link
Contributor

Choose a reason for hiding this comment

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

When is that useful?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Honestly, I don't know. :(

I copied that from the previous implementation, and I was expecting it to make a difference in situations where member function calls are involved in a constraint evaluation. But I ran the regression tests with that removed and everything seemed fine.

I will have another try on a build without this patch. It could also be the case we're missing some tests, but I don't know...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Huh, I removed these CXXThisScopeRAII lines in CheckInstantiatedFunctionTemplateConstraints() and all libcxx and clang regressions still passed. I guess we probably could remove these setups there

return SemaRef.CheckConstraintSatisfaction(
Template, TemplateAC, MLTAL, PointOfInstantiation, Satisfaction);
}

bool Sema::CheckFunctionTemplateConstraints(
SourceLocation PointOfInstantiation, FunctionDecl *Decl,
ArrayRef<TemplateArgument> TemplateArgs,
ConstraintSatisfaction &Satisfaction) {
// In most cases we're not going to have constraints, so check for that first.
FunctionTemplateDecl *Template = Decl->getPrimaryTemplate();

if (!Template)
return ::CheckFunctionConstraintsWithoutInstantiation(
*this, PointOfInstantiation, Decl->getDescribedFunctionTemplate(),
TemplateArgs, Satisfaction);

// Note - code synthesis context for the constraints check is created
// inside CheckConstraintsSatisfaction.
SmallVector<const Expr *, 3> TemplateAC;
Expand Down Expand Up @@ -1517,7 +1555,7 @@ substituteParameterMappings(Sema &S, NormalizedConstraint &N,
static bool substituteParameterMappings(Sema &S, NormalizedConstraint &N,
const ConceptSpecializationExpr *CSE) {
MultiLevelTemplateArgumentList MLTAL = S.getTemplateInstantiationArgs(
CSE->getNamedConcept(), CSE->getNamedConcept()->getLexicalDeclContext(),
CSE->getNamedConcept(), CSE->getNamedConcept()->getDeclContext(),
Copy link
Contributor

Choose a reason for hiding this comment

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

Do you have tests with friend functions?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is another leftover of my previous changes on getTemplateInstantiationArgs()... Will revert it

/*Final=*/false, CSE->getTemplateArguments(),
/*RelativeToPrimary=*/true,
/*ForConstraintInstantiation=*/true);
Expand Down
48 changes: 34 additions & 14 deletions clang/lib/Sema/SemaTemplateDeduction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3982,18 +3982,6 @@ TemplateDeductionResult Sema::FinishTemplateArgumentDeduction(
Result != TemplateDeductionResult::Success)
return Result;

// C++ [temp.deduct.call]p10: [DR1391]
// If deduction succeeds for all parameters that contain
// template-parameters that participate in template argument deduction,
// and all template arguments are explicitly specified, deduced, or
// obtained from default template arguments, remaining parameters are then
// compared with the corresponding arguments. For each remaining parameter
// P with a type that was non-dependent before substitution of any
// explicitly-specified template arguments, if the corresponding argument
// A cannot be implicitly converted to P, deduction fails.
if (CheckNonDependent())
return TemplateDeductionResult::NonDependentConversionFailure;

// Form the template argument list from the deduced template arguments.
TemplateArgumentList *SugaredDeducedArgumentList =
TemplateArgumentList::CreateCopy(Context, SugaredBuilder);
Expand All @@ -4008,6 +3996,38 @@ TemplateDeductionResult Sema::FinishTemplateArgumentDeduction(
Owner = FunctionTemplate->getLexicalDeclContext();
FunctionDecl *FD = FunctionTemplate->getTemplatedDecl();

// C++20 [temp.deduct.general]p5: (CWG2369)
// If the function template has associated constraints, those constraints are
// checked for satisfaction. If the constraints are not satisfied, type
// deduction fails.
// FIXME: We haven't implemented CWG2369 for lambdas yet, because we need
// to figure out how to instantiate lambda captures to the scope without
// first instantiating the lambda.
bool IsLambda = isLambdaCallOperator(FD) || isLambdaConversionOperator(FD);
if (!IsLambda && !IsIncomplete) {
if (CheckFunctionTemplateConstraints(
Info.getLocation(),
FunctionTemplate->getCanonicalDecl()->getTemplatedDecl(),
CanonicalBuilder, Info.AssociatedConstraintsSatisfaction))
return TemplateDeductionResult::MiscellaneousDeductionFailure;
if (!Info.AssociatedConstraintsSatisfaction.IsSatisfied) {
Info.reset(Info.takeSugared(),
TemplateArgumentList::CreateCopy(Context, CanonicalBuilder));
Comment on lines +4014 to +4015
Copy link
Contributor

Choose a reason for hiding this comment

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

Why is that necessary?

return TemplateDeductionResult::ConstraintsNotSatisfied;
}
}
// C++ [temp.deduct.call]p10: [DR1391]
zyn0217 marked this conversation as resolved.
Show resolved Hide resolved
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
// C++ [temp.deduct.call]p10: [DR1391]
// CWG1391

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oops, I thought I have fixed it everywhere...

// If deduction succeeds for all parameters that contain
// template-parameters that participate in template argument deduction,
// and all template arguments are explicitly specified, deduced, or
// obtained from default template arguments, remaining parameters are then
// compared with the corresponding arguments. For each remaining parameter
// P with a type that was non-dependent before substitution of any
// explicitly-specified template arguments, if the corresponding argument
// A cannot be implicitly converted to P, deduction fails.
if (CheckNonDependent())
return TemplateDeductionResult::NonDependentConversionFailure;

MultiLevelTemplateArgumentList SubstArgs(
FunctionTemplate, CanonicalDeducedArgumentList->asArray(),
/*Final=*/false);
Expand Down Expand Up @@ -4042,8 +4062,8 @@ TemplateDeductionResult Sema::FinishTemplateArgumentDeduction(
// ([temp.constr.decl]), those constraints are checked for satisfaction
// ([temp.constr.constr]). If the constraints are not satisfied, type
// deduction fails.
if (!IsIncomplete) {
if (CheckInstantiatedFunctionTemplateConstraints(
if (IsLambda && !IsIncomplete) {
Copy link
Contributor

Choose a reason for hiding this comment

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

can you add a fixme comment here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

There's one on line 4003, maybe that just suffices?

if (CheckFunctionTemplateConstraints(
Info.getLocation(), Specialization, CanonicalBuilder,
Info.AssociatedConstraintsSatisfaction))
return TemplateDeductionResult::MiscellaneousDeductionFailure;
Expand Down
8 changes: 5 additions & 3 deletions clang/lib/Sema/SemaTemplateDeductionGuide.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -891,10 +891,12 @@ Expr *buildIsDeducibleConstraint(Sema &SemaRef,
Context.getTrivialTypeSourceInfo(
Context.getDeducedTemplateSpecializationType(
TemplateName(AliasTemplate), /*DeducedType=*/QualType(),
/*IsDependent=*/true)), // template specialization type whose
// arguments will be deduced.
/*IsDependent=*/true),
AliasTemplate->getLocation()), // template specialization type whose
// arguments will be deduced.
Context.getTrivialTypeSourceInfo(
ReturnType), // type from which template arguments are deduced.
ReturnType, AliasTemplate->getLocation()), // type from which template
// arguments are deduced.
};
return TypeTraitExpr::Create(
Context, Context.getLogicalOperationType(), AliasTemplate->getLocation(),
Expand Down
72 changes: 61 additions & 11 deletions clang/lib/Sema/SemaTemplateInstantiate.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1365,6 +1365,20 @@ namespace {
// Whether an incomplete substituion should be treated as an error.
bool BailOutOnIncomplete;

private:
bool isSubstitutingConstraints() const {
return llvm::any_of(
llvm::reverse(SemaRef.CodeSynthesisContexts), [](auto &Context) {
return Context.Kind ==
Sema::CodeSynthesisContext::ConstraintSubstitution;
});
}

// CWG2770: Function parameters should be instantiated when they are
// needed by a satisfaction check of an atomic constraint or
// (recursively) by another function parameter.
bool maybeInstantiateFunctionParameterToScope(ParmVarDecl *OldParm);

public:
typedef TreeTransform<TemplateInstantiator> inherited;

Expand All @@ -1378,9 +1392,7 @@ namespace {
void setEvaluateConstraints(bool B) {
EvaluateConstraints = B;
}
bool getEvaluateConstraints() {
return EvaluateConstraints;
}
bool getEvaluateConstraints() const { return EvaluateConstraints; }

/// Determine whether the given type \p T has already been
/// transformed.
Expand Down Expand Up @@ -1421,12 +1433,19 @@ namespace {
ArrayRef<UnexpandedParameterPack> Unexpanded,
bool &ShouldExpand, bool &RetainExpansion,
std::optional<unsigned> &NumExpansions) {
return getSema().CheckParameterPacksForExpansion(EllipsisLoc,
PatternRange, Unexpanded,
TemplateArgs,
ShouldExpand,
RetainExpansion,
NumExpansions);
if (SemaRef.CurrentInstantiationScope && isSubstitutingConstraints()) {
for (UnexpandedParameterPack ParmPack : Unexpanded) {
NamedDecl *VD = ParmPack.first.dyn_cast<NamedDecl *>();
if (!isa_and_present<ParmVarDecl>(VD))
continue;
if (maybeInstantiateFunctionParameterToScope(cast<ParmVarDecl>(VD)))
return true;
}
}

return getSema().CheckParameterPacksForExpansion(
EllipsisLoc, PatternRange, Unexpanded, TemplateArgs, ShouldExpand,
RetainExpansion, NumExpansions);
}

void ExpandingFunctionParameterPack(ParmVarDecl *Pack) {
Expand Down Expand Up @@ -1916,9 +1935,33 @@ Decl *TemplateInstantiator::TransformDecl(SourceLocation Loc, Decl *D) {
// template parameter.
}

if (SemaRef.CurrentInstantiationScope) {
if (isSubstitutingConstraints() && isa<ParmVarDecl>(D) &&
maybeInstantiateFunctionParameterToScope(cast<ParmVarDecl>(D)))
return nullptr;
}

return SemaRef.FindInstantiatedDecl(Loc, cast<NamedDecl>(D), TemplateArgs);
}

bool TemplateInstantiator::maybeInstantiateFunctionParameterToScope(
ParmVarDecl *OldParm) {
if (SemaRef.CurrentInstantiationScope->findInstantiationUnsafe(OldParm))
return false;
// The current context might have been changed in the process of transforming
// lambda expression. So resume it before we substitute into the parameter.
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
// lambda expression. So resume it before we substitute into the parameter.
// lambda expression. So restore it before we substitute into the parameter.

Sema::ContextRAII Context(SemaRef, OldParm->getDeclContext());

SmallVector<QualType> PTypes;
Sema::ExtParameterInfoBuilder TInfoBuilder;

return inherited::TransformFunctionTypeParams(
/*Loc=*/SourceLocation(), /*Params=*/OldParm, /*ParamTypes=*/nullptr,
/*ParamInfos=*/nullptr, /*PTypes=*/PTypes, /*PVars=*/nullptr,
TInfoBuilder, /*LastParamTransformed=*/nullptr,
/*IgnoreParameterIndex=*/true);
}

Decl *TemplateInstantiator::TransformDefinition(SourceLocation Loc, Decl *D) {
Decl *Inst = getSema().SubstDecl(D, getSema().CurContext, TemplateArgs);
if (!Inst)
Expand Down Expand Up @@ -4542,9 +4585,8 @@ static const Decl *getCanonicalParmVarDecl(const Decl *D) {
return D;
}


llvm::PointerUnion<Decl *, LocalInstantiationScope::DeclArgumentPack *> *
LocalInstantiationScope::findInstantiationOf(const Decl *D) {
LocalInstantiationScope::findInstantiationUnsafe(const Decl *D) {
D = getCanonicalParmVarDecl(D);
for (LocalInstantiationScope *Current = this; Current;
Current = Current->Outer) {
Expand All @@ -4569,6 +4611,14 @@ LocalInstantiationScope::findInstantiationOf(const Decl *D) {
break;
}

return nullptr;
}

llvm::PointerUnion<Decl *, LocalInstantiationScope::DeclArgumentPack *> *
LocalInstantiationScope::findInstantiationOf(const Decl *D) {
auto *Result = findInstantiationUnsafe(D);
if (Result)
return Result;
// If we're performing a partial substitution during template argument
// deduction, we may not have values for template parameters yet.
if (isa<NonTypeTemplateParmDecl>(D) || isa<TemplateTypeParmDecl>(D) ||
Expand Down
11 changes: 6 additions & 5 deletions clang/lib/Sema/TreeTransform.h
Original file line number Diff line number Diff line change
Expand Up @@ -713,7 +713,7 @@ class TreeTransform {
/// variables vector are acceptable.
///
/// LastParamTransformed, if non-null, will be set to the index of the last
/// parameter on which transfromation was started. In the event of an error,
/// parameter on which transformation was started. In the event of an error,
/// this will contain the parameter which failed to instantiate.
///
/// Return true on error.
Expand All @@ -722,7 +722,8 @@ class TreeTransform {
const QualType *ParamTypes,
const FunctionProtoType::ExtParameterInfo *ParamInfos,
SmallVectorImpl<QualType> &PTypes, SmallVectorImpl<ParmVarDecl *> *PVars,
Sema::ExtParameterInfoBuilder &PInfos, unsigned *LastParamTransformed);
Sema::ExtParameterInfoBuilder &PInfos, unsigned *LastParamTransformed,
bool IgnoreParameterIndex = false);

bool TransformFunctionTypeParams(
SourceLocation Loc, ArrayRef<ParmVarDecl *> Params,
Expand Down Expand Up @@ -5997,16 +5998,16 @@ bool TreeTransform<Derived>::TransformFunctionTypeParams(
const FunctionProtoType::ExtParameterInfo *ParamInfos,
SmallVectorImpl<QualType> &OutParamTypes,
SmallVectorImpl<ParmVarDecl *> *PVars,
Sema::ExtParameterInfoBuilder &PInfos,
unsigned *LastParamTransformed) {
Sema::ExtParameterInfoBuilder &PInfos, unsigned *LastParamTransformed,
bool IgnoreParameterIndex) {
int indexAdjustment = 0;

unsigned NumParams = Params.size();
for (unsigned i = 0; i != NumParams; ++i) {
if (LastParamTransformed)
*LastParamTransformed = i;
if (ParmVarDecl *OldParm = Params[i]) {
assert(OldParm->getFunctionScopeIndex() == i);
assert(IgnoreParameterIndex || OldParm->getFunctionScopeIndex() == i);

std::optional<unsigned> NumExpansions;
ParmVarDecl *NewParm = nullptr;
Expand Down
32 changes: 32 additions & 0 deletions clang/test/CXX/drs/cwg23xx.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -528,3 +528,35 @@ namespace cwg2397 { // cwg2397: 17
} // namespace cwg2397

#endif

#if __cplusplus >= 202002L

namespace cwg2369 { // cwg2369: partial

template <class T> struct Z {
typedef typename T::x xx;
};

template <class T>
concept C = requires { typename T::A; };
template <C T> typename Z<T>::xx f(void *, T); // #1
template <class T> void f(int, T); // #2

struct A {
} a;

struct ZZ {
template <class T, class = typename Z<T>::xx> operator T *();
operator int();
};

void foo() {
ZZ zz;
f(1, a); // OK, deduction fails for #1 because there is no conversion from int
// to void*
f(zz, 42); // OK, deduction fails for #1 because C<int> is not satisfied
}

} // namespace cwg2369

#endif
Loading
Loading