Skip to content

[Clang] Reapply CWG2369 "Ordering between constraints and substitution" #122423

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 22 commits into from
Jun 2, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
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: 62 additions & 11 deletions clang/include/clang/Sema/Sema.h
Original file line number Diff line number Diff line change
Expand Up @@ -10505,13 +10505,34 @@ class Sema final : public SemaBase {
OverloadCandidateParamOrder PO = {},
bool AggregateCandidateDeduction = false);

struct CheckNonDependentConversionsFlag {
/// Do not consider any user-defined conversions when constructing the
/// initializing sequence.
bool SuppressUserConversions;

/// Before constructing the initializing sequence, we check whether the
/// parameter type and argument type contain any user defined conversions.
/// If so, do not initialize them. This effectively bypasses some undesired
/// instantiation before checking constaints, which might otherwise result
/// in non-SFINAE errors e.g. recursive constraints.
bool OnlyInitializeNonUserDefinedConversions;

CheckNonDependentConversionsFlag(
bool SuppressUserConversions,
bool OnlyInitializeNonUserDefinedConversions)
: SuppressUserConversions(SuppressUserConversions),
OnlyInitializeNonUserDefinedConversions(
OnlyInitializeNonUserDefinedConversions) {}
};

/// Check that implicit conversion sequences can be formed for each argument
/// whose corresponding parameter has a non-dependent type, per DR1391's
/// [temp.deduct.call]p10.
bool CheckNonDependentConversions(
FunctionTemplateDecl *FunctionTemplate, ArrayRef<QualType> ParamTypes,
ArrayRef<Expr *> Args, OverloadCandidateSet &CandidateSet,
ConversionSequenceList &Conversions, bool SuppressUserConversions,
ConversionSequenceList &Conversions,
CheckNonDependentConversionsFlag UserConversionFlag,
CXXRecordDecl *ActingContext = nullptr, QualType ObjectType = QualType(),
Expr::Classification ObjectClassification = {},
OverloadCandidateParamOrder PO = {});
Expand Down Expand Up @@ -12555,14 +12576,22 @@ class Sema final : public SemaBase {
///
/// \param OriginalCallArgs If non-NULL, the original call arguments against
/// which the deduced argument types should be compared.
/// \param CheckNonDependent Callback before substituting into the declaration
/// with the deduced template arguments.
/// \param OnlyInitializeNonUserDefinedConversions is used as a workaround for
/// some breakages introduced by CWG2369, where non-user-defined conversions
/// are checked first before the constraints.
TemplateDeductionResult FinishTemplateArgumentDeduction(
FunctionTemplateDecl *FunctionTemplate,
SmallVectorImpl<DeducedTemplateArgument> &Deduced,
unsigned NumExplicitlySpecified, FunctionDecl *&Specialization,
sema::TemplateDeductionInfo &Info,
SmallVectorImpl<OriginalCallArg> const *OriginalCallArgs,
bool PartialOverloading, bool PartialOrdering,
llvm::function_ref<bool()> CheckNonDependent = [] { return false; });
llvm::function_ref<bool(bool)> CheckNonDependent =
[](bool /*OnlyInitializeNonUserDefinedConversions*/) {
return false;
});

/// Perform template argument deduction from a function call
/// (C++ [temp.deduct.call]).
Expand Down Expand Up @@ -12598,7 +12627,7 @@ class Sema final : public SemaBase {
bool PartialOrdering, QualType ObjectType,
Expr::Classification ObjectClassification,
bool ForOverloadSetAddressResolution,
llvm::function_ref<bool(ArrayRef<QualType>)> CheckNonDependent);
llvm::function_ref<bool(ArrayRef<QualType>, bool)> CheckNonDependent);

/// Deduce template arguments when taking the address of a function
/// template (C++ [temp.deduct.funcaddr]) or matching a specialization to
Expand Down Expand Up @@ -13074,6 +13103,9 @@ class Sema final : public SemaBase {
/// Was the enclosing context a non-instantiation SFINAE context?
bool SavedInNonInstantiationSFINAEContext;

/// Whether we're substituting into constraints.
bool InConstraintSubstitution;

/// The point of instantiation or synthesis within the source code.
SourceLocation PointOfInstantiation;

Expand Down Expand Up @@ -13123,9 +13155,9 @@ class Sema final : public SemaBase {

CodeSynthesisContext()
: Kind(TemplateInstantiation),
SavedInNonInstantiationSFINAEContext(false), Entity(nullptr),
Template(nullptr), TemplateArgs(nullptr), NumTemplateArgs(0),
DeductionInfo(nullptr) {}
SavedInNonInstantiationSFINAEContext(false),
InConstraintSubstitution(false), Entity(nullptr), Template(nullptr),
TemplateArgs(nullptr), NumTemplateArgs(0), DeductionInfo(nullptr) {}

/// Determines whether this template is an actual instantiation
/// that should be counted toward the maximum instantiation depth.
Expand Down Expand Up @@ -13369,9 +13401,22 @@ class Sema final : public SemaBase {
///
/// \param SkipForSpecialization when specified, any template specializations
/// in a traversal would be ignored.
///
/// \param ForDefaultArgumentSubstitution indicates we should continue looking
/// when encountering a specialized member function template, rather than
/// returning immediately.
void getTemplateInstantiationArgs(
MultiLevelTemplateArgumentList &Result, const NamedDecl *D,
const DeclContext *DC = nullptr, bool Final = false,
std::optional<ArrayRef<TemplateArgument>> Innermost = std::nullopt,
bool RelativeToPrimary = false, const FunctionDecl *Pattern = nullptr,
bool ForConstraintInstantiation = false,
bool SkipForSpecialization = false,
bool ForDefaultArgumentSubstitution = false);

/// This creates a new \p MultiLevelTemplateArgumentList and invokes the other
/// overload with it as the first parameter. Prefer this overload in most
/// situations.
MultiLevelTemplateArgumentList getTemplateInstantiationArgs(
const NamedDecl *D, const DeclContext *DC = nullptr, bool Final = false,
std::optional<ArrayRef<TemplateArgument>> Innermost = std::nullopt,
Expand Down Expand Up @@ -13644,7 +13689,7 @@ class Sema final : public SemaBase {
ExprResult
SubstConstraintExpr(Expr *E,
const MultiLevelTemplateArgumentList &TemplateArgs);
// Unlike the above, this does not evaluates constraints.
// Unlike the above, this does not evaluate constraints.
ExprResult SubstConstraintExprWithoutSatisfaction(
Expr *E, const MultiLevelTemplateArgumentList &TemplateArgs);

Expand Down Expand Up @@ -13794,6 +13839,12 @@ class Sema final : public SemaBase {
return CodeSynthesisContexts.size() > NonInstantiationEntries;
}

/// Determine whether we are currently performing constraint substitution.
bool inConstraintSubstitution() const {
return !CodeSynthesisContexts.empty() &&
CodeSynthesisContexts.back().InConstraintSubstitution;
}

using EntityPrinter = llvm::function_ref<void(llvm::raw_ostream &)>;

/// \brief create a Requirement::SubstitutionDiagnostic with only a
Expand Down Expand Up @@ -14786,10 +14837,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
6 changes: 6 additions & 0 deletions clang/include/clang/Sema/Template.h
Original file line number Diff line number Diff line change
Expand Up @@ -522,6 +522,12 @@ enum class TemplateSubstitutionKind : char {
llvm::PointerUnion<Decl *, DeclArgumentPack *> *
findInstantiationOf(const Decl *D);

/// Similar to \p findInstantiationOf(), but it wouldn't assert if the
/// instantiation was not found within the current instantiation scope. This
/// is helpful for on-demand declaration instantiation.
llvm::PointerUnion<Decl *, DeclArgumentPack *> *
getInstantiationOfIfExists(const Decl *D);

void InstantiatedLocal(const Decl *D, Decl *Inst);
void InstantiatedLocalPackArg(const Decl *D, VarDecl *Inst);
void MakeInstantiatedLocalArgPack(const Decl *D);
Expand Down
47 changes: 45 additions & 2 deletions clang/lib/Sema/SemaConcept.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -792,7 +792,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
// CheckFunctionTemplateConstraints after this will check it
// better.
if (FD->isDependentContext() ||
FD->getTemplatedKind() ==
Expand Down Expand Up @@ -1060,12 +1060,55 @@ bool Sema::EnsureTemplateArgumentListConstraints(
return false;
}

bool Sema::CheckInstantiatedFunctionTemplateConstraints(
static bool CheckFunctionConstraintsWithoutInstantiation(
Sema &SemaRef, SourceLocation PointOfInstantiation,
FunctionTemplateDecl *Template, ArrayRef<TemplateArgument> TemplateArgs,
ConstraintSatisfaction &Satisfaction) {
SmallVector<AssociatedConstraint, 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.

// FIXME: Add TemplateArgs through the 'Innermost' parameter once
// the refactoring of getTemplateInstantiationArgs() relands.
MultiLevelTemplateArgumentList MLTAL;
MLTAL.addOuterTemplateArguments(Template, std::nullopt, /*Final=*/false);
SemaRef.getTemplateInstantiationArgs(
MLTAL, /*D=*/FD, FD,
/*Final=*/false, /*Innermost=*/std::nullopt, /*RelativeToPrimary=*/true,
/*Pattern=*/nullptr, /*ForConstraintInstantiation=*/true);
MLTAL.replaceInnermostTemplateArguments(Template, TemplateArgs);

Sema::ContextRAII SavedContext(SemaRef, FD);
std::optional<Sema::CXXThisScopeRAII> ThisScope;
if (auto *Method = dyn_cast<CXXMethodDecl>(FD))
ThisScope.emplace(SemaRef, /*Record=*/Method->getParent(),
/*ThisQuals=*/Method->getMethodQualifiers());
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<AssociatedConstraint, 3> TemplateAC;
Expand Down
82 changes: 67 additions & 15 deletions clang/lib/Sema/SemaOverload.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7848,11 +7848,14 @@ static void AddMethodTemplateCandidateImmediately(
/*PartialOrdering=*/false, ObjectType, ObjectClassification,
CandidateSet.getKind() ==
clang::OverloadCandidateSet::CSK_AddressOfOverloadSet,
[&](ArrayRef<QualType> ParamTypes) {
[&](ArrayRef<QualType> ParamTypes,
bool OnlyInitializeNonUserDefinedConversions) {
return S.CheckNonDependentConversions(
MethodTmpl, ParamTypes, Args, CandidateSet, Conversions,
SuppressUserConversions, ActingContext, ObjectType,
ObjectClassification, PO);
Sema::CheckNonDependentConversionsFlag(
SuppressUserConversions,
OnlyInitializeNonUserDefinedConversions),
ActingContext, ObjectType, ObjectClassification, PO);
});
Result != TemplateDeductionResult::Success) {
OverloadCandidate &Candidate =
Expand Down Expand Up @@ -7964,10 +7967,14 @@ static void AddTemplateOverloadCandidateImmediately(
/*ObjectClassification=*/Expr::Classification(),
CandidateSet.getKind() ==
OverloadCandidateSet::CSK_AddressOfOverloadSet,
[&](ArrayRef<QualType> ParamTypes) {
[&](ArrayRef<QualType> ParamTypes,
bool OnlyInitializeNonUserDefinedConversions) {
return S.CheckNonDependentConversions(
FunctionTemplate, ParamTypes, Args, CandidateSet, Conversions,
SuppressUserConversions, nullptr, QualType(), {}, PO);
Sema::CheckNonDependentConversionsFlag(
SuppressUserConversions,
OnlyInitializeNonUserDefinedConversions),
nullptr, QualType(), {}, PO);
});
Result != TemplateDeductionResult::Success) {
OverloadCandidate &Candidate =
Expand Down Expand Up @@ -8041,7 +8048,8 @@ void Sema::AddTemplateOverloadCandidate(
bool Sema::CheckNonDependentConversions(
FunctionTemplateDecl *FunctionTemplate, ArrayRef<QualType> ParamTypes,
ArrayRef<Expr *> Args, OverloadCandidateSet &CandidateSet,
ConversionSequenceList &Conversions, bool SuppressUserConversions,
ConversionSequenceList &Conversions,
CheckNonDependentConversionsFlag UserConversionFlag,
CXXRecordDecl *ActingContext, QualType ObjectType,
Expr::Classification ObjectClassification, OverloadCandidateParamOrder PO) {
// FIXME: The cases in which we allow explicit conversions for constructor
Expand All @@ -8054,8 +8062,9 @@ bool Sema::CheckNonDependentConversions(
bool HasThisConversion = Method && !isa<CXXConstructorDecl>(Method);
unsigned ThisConversions = HasThisConversion ? 1 : 0;

Conversions =
CandidateSet.allocateConversionSequences(ThisConversions + Args.size());
if (Conversions.empty())
Conversions =
CandidateSet.allocateConversionSequences(ThisConversions + Args.size());

// Overload resolution is always an unevaluated context.
EnterExpressionEvaluationContext Unevaluated(
Expand All @@ -8079,6 +8088,46 @@ bool Sema::CheckNonDependentConversions(
}
}

// A speculative workaround for self-dependent constraint bugs that manifest
// after CWG2369.
// FIXME: Add references to the standard once P3606 is adopted.
auto MaybeInvolveUserDefinedConversion = [&](QualType ParamType,
QualType ArgType) {
ParamType = ParamType.getNonReferenceType();
ArgType = ArgType.getNonReferenceType();
bool PointerConv = ParamType->isPointerType() && ArgType->isPointerType();
if (PointerConv) {
ParamType = ParamType->getPointeeType();
ArgType = ArgType->getPointeeType();
}

if (auto *RT = ParamType->getAs<RecordType>())
if (auto *RD = dyn_cast<CXXRecordDecl>(RT->getDecl());
RD && RD->hasDefinition()) {
if (llvm::any_of(LookupConstructors(RD), [](NamedDecl *ND) {
auto Info = getConstructorInfo(ND);
if (!Info)
return false;
CXXConstructorDecl *Ctor = Info.Constructor;
/// isConvertingConstructor takes copy/move constructors into
/// account!
return !Ctor->isCopyOrMoveConstructor() &&
Ctor->isConvertingConstructor(
/*AllowExplicit=*/true);
}))
return true;
}

if (auto *RT = ArgType->getAs<RecordType>())
if (auto *RD = dyn_cast<CXXRecordDecl>(RT->getDecl());
RD && RD->hasDefinition() &&
!RD->getVisibleConversionFunctions().empty()) {
return true;
}

return false;
};

unsigned Offset =
Method && Method->hasCXXExplicitFunctionObjectParameter() ? 1 : 0;

Expand All @@ -8099,13 +8148,16 @@ bool Sema::CheckNonDependentConversions(
// For members, 'this' got ConvIdx = 0 previously.
ConvIdx = ThisConversions + I;
}
Conversions[ConvIdx]
= TryCopyInitialization(*this, Args[I], ParamType,
SuppressUserConversions,
/*InOverloadResolution=*/true,
/*AllowObjCWritebackConversion=*/
getLangOpts().ObjCAutoRefCount,
AllowExplicit);
if (Conversions[ConvIdx].isInitialized())
continue;
if (UserConversionFlag.OnlyInitializeNonUserDefinedConversions &&
MaybeInvolveUserDefinedConversion(ParamType, Args[I]->getType()))
continue;
Conversions[ConvIdx] = TryCopyInitialization(
*this, Args[I], ParamType, UserConversionFlag.SuppressUserConversions,
/*InOverloadResolution=*/true,
/*AllowObjCWritebackConversion=*/
getLangOpts().ObjCAutoRefCount, AllowExplicit);
if (Conversions[ConvIdx].isBad())
return true;
}
Expand Down
Loading
Loading