Skip to content

[Clang] Defer the instantiation of explicit-specifier until constraint checking completes #70548

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
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
4 changes: 4 additions & 0 deletions clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -682,6 +682,10 @@ Bug Fixes to C++ Support
Fixes:
(`#68769 <https://github.com/llvm/llvm-project/issues/68769>`_)

- Clang now defers the instantiation of explicit specifier until constraint checking
completes (except deduction guides). Fixes:
(`#59827 <https://github.com/llvm/llvm-project/issues/59827>`_)

Bug Fixes to AST Handling
^^^^^^^^^^^^^^^^^^^^^^^^^
- Fixed an import failure of recursive friend class template.
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 @@ -10430,6 +10430,9 @@ class Sema final {
const CXXConstructorDecl *Tmpl,
const MultiLevelTemplateArgumentList &TemplateArgs);

ExplicitSpecifier instantiateExplicitSpecifier(
const MultiLevelTemplateArgumentList &TemplateArgs, ExplicitSpecifier ES);

NamedDecl *FindInstantiatedDecl(SourceLocation Loc, NamedDecl *D,
const MultiLevelTemplateArgumentList &TemplateArgs,
bool FindingInstantiatedContext = false);
Expand Down
53 changes: 53 additions & 0 deletions clang/lib/Sema/SemaTemplateDeduction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3553,6 +3553,48 @@ static unsigned getPackIndexForParam(Sema &S,
llvm_unreachable("parameter index would not be produced from template");
}

// if `Specialization` is a `CXXConstructorDecl` or `CXXConversionDecl`,
// we'll try to instantiate and update its explicit specifier after constraint
// checking.
static Sema::TemplateDeductionResult instantiateExplicitSpecifierDeferred(
Sema &S, FunctionDecl *Specialization,
const MultiLevelTemplateArgumentList &SubstArgs,
TemplateDeductionInfo &Info, FunctionTemplateDecl *FunctionTemplate,
ArrayRef<TemplateArgument> DeducedArgs) {
auto GetExplicitSpecifier = [](FunctionDecl *D) {
return isa<CXXConstructorDecl>(D)
? cast<CXXConstructorDecl>(D)->getExplicitSpecifier()
: cast<CXXConversionDecl>(D)->getExplicitSpecifier();
};
auto SetExplicitSpecifier = [](FunctionDecl *D, ExplicitSpecifier ES) {
isa<CXXConstructorDecl>(D)
? cast<CXXConstructorDecl>(D)->setExplicitSpecifier(ES)
: cast<CXXConversionDecl>(D)->setExplicitSpecifier(ES);
};

ExplicitSpecifier ES = GetExplicitSpecifier(Specialization);
Expr *ExplicitExpr = ES.getExpr();
if (!ExplicitExpr)
return Sema::TDK_Success;
if (!ExplicitExpr->isValueDependent())
return Sema::TDK_Success;

Sema::InstantiatingTemplate Inst(
S, Info.getLocation(), FunctionTemplate, DeducedArgs,
Sema::CodeSynthesisContext::DeducedTemplateArgumentSubstitution, Info);
if (Inst.isInvalid())
return Sema::TDK_InstantiationDepth;
Sema::SFINAETrap Trap(S);
const ExplicitSpecifier InstantiatedES =
S.instantiateExplicitSpecifier(SubstArgs, ES);
if (InstantiatedES.isInvalid() || Trap.hasErrorOccurred()) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Do we have a test that checks this case at all? Same for the other failures, if we know how to test them we should write a test to verify them.

Specialization->setInvalidDecl(true);
return Sema::TDK_SubstitutionFailure;
}
SetExplicitSpecifier(Specialization, InstantiatedES);
return Sema::TDK_Success;
}

/// Finish template argument deduction for a function template,
/// checking the deduced template arguments for completeness and forming
/// the function template specialization.
Expand Down Expand Up @@ -3682,6 +3724,17 @@ Sema::TemplateDeductionResult Sema::FinishTemplateArgumentDeduction(
}
}

// We skipped the instantiation of the explicit-specifier during the
// substitution of `FD` before. So, we try to instantiate it back if
// `Specialization` is either a constructor or a conversion function.
if (isa<CXXConstructorDecl, CXXConversionDecl>(Specialization)) {
if (TDK_Success != instantiateExplicitSpecifierDeferred(
*this, Specialization, SubstArgs, Info,
FunctionTemplate, DeducedArgs)) {
return TDK_SubstitutionFailure;
}
}

if (OriginalCallArgs) {
// C++ [temp.deduct.call]p4:
// In general, the deduction process attempts to find template argument
Expand Down
40 changes: 26 additions & 14 deletions clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -563,26 +563,24 @@ static void instantiateDependentAMDGPUFlatWorkGroupSizeAttr(
S.addAMDGPUFlatWorkGroupSizeAttr(New, Attr, MinExpr, MaxExpr);
}

static ExplicitSpecifier
instantiateExplicitSpecifier(Sema &S,
const MultiLevelTemplateArgumentList &TemplateArgs,
ExplicitSpecifier ES, FunctionDecl *New) {
ExplicitSpecifier Sema::instantiateExplicitSpecifier(
const MultiLevelTemplateArgumentList &TemplateArgs, ExplicitSpecifier ES) {
if (!ES.getExpr())
return ES;
Expr *OldCond = ES.getExpr();
Expr *Cond = nullptr;
{
EnterExpressionEvaluationContext Unevaluated(
S, Sema::ExpressionEvaluationContext::ConstantEvaluated);
ExprResult SubstResult = S.SubstExpr(OldCond, TemplateArgs);
*this, Sema::ExpressionEvaluationContext::ConstantEvaluated);
ExprResult SubstResult = SubstExpr(OldCond, TemplateArgs);
if (SubstResult.isInvalid()) {
return ExplicitSpecifier::Invalid();
}
Cond = SubstResult.get();
}
ExplicitSpecifier Result(Cond, ES.getKind());
if (!Cond->isTypeDependent())
S.tryResolveExplicitSpecifier(Result);
tryResolveExplicitSpecifier(Result);
return Result;
}

Expand Down Expand Up @@ -2073,8 +2071,8 @@ Decl *TemplateDeclInstantiator::VisitFunctionDecl(

ExplicitSpecifier InstantiatedExplicitSpecifier;
if (auto *DGuide = dyn_cast<CXXDeductionGuideDecl>(D)) {
InstantiatedExplicitSpecifier = instantiateExplicitSpecifier(
SemaRef, TemplateArgs, DGuide->getExplicitSpecifier(), DGuide);
InstantiatedExplicitSpecifier = SemaRef.instantiateExplicitSpecifier(
TemplateArgs, DGuide->getExplicitSpecifier());
if (InstantiatedExplicitSpecifier.isInvalid())
return nullptr;
}
Expand Down Expand Up @@ -2453,11 +2451,25 @@ Decl *TemplateDeclInstantiator::VisitCXXMethodDecl(
}
}

ExplicitSpecifier InstantiatedExplicitSpecifier =
instantiateExplicitSpecifier(SemaRef, TemplateArgs,
ExplicitSpecifier::getFromDecl(D), D);
if (InstantiatedExplicitSpecifier.isInvalid())
return nullptr;
auto InstantiatedExplicitSpecifier = ExplicitSpecifier::getFromDecl(D);
// deduction guides need this
const bool CouldInstantiate =
InstantiatedExplicitSpecifier.getExpr() == nullptr ||
!InstantiatedExplicitSpecifier.getExpr()->isValueDependent();

// Delay the instantiation of the explicit-specifier until after the
// constraints are checked during template argument deduction.
if (CouldInstantiate ||
SemaRef.CodeSynthesisContexts.back().Kind !=
Sema::CodeSynthesisContext::DeducedTemplateArgumentSubstitution) {
InstantiatedExplicitSpecifier = SemaRef.instantiateExplicitSpecifier(
TemplateArgs, InstantiatedExplicitSpecifier);

if (InstantiatedExplicitSpecifier.isInvalid())
return nullptr;
} else {
InstantiatedExplicitSpecifier.setKind(ExplicitSpecKind::Unresolved);
}

// Implicit destructors/constructors created for local classes in
// DeclareImplicit* (see SemaDeclCXX.cpp) might not have an associated TSI.
Expand Down
31 changes: 31 additions & 0 deletions clang/test/SemaCXX/cxx2a-explicit-bool-deferred.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// RUN: %clang_cc1 -fsyntax-only -verify -std=c++2a %s

template <typename T1, typename T2> struct is_same {
static constexpr bool value = false;
};

template <typename T> struct is_same<T, T> {
static constexpr bool value = true;
};

template <class T, class U>
concept SameHelper = is_same<T, U>::value;
template <class T, class U>
concept same_as = SameHelper<T, U> && SameHelper<U, T>;

namespace deferred_instantiation {
template <class X> constexpr X do_not_instantiate() { return nullptr; }

struct T {
template <same_as<float> X> explicit(do_not_instantiate<X>()) T(X) {}

T(int) {}
};

T t(5);
// expected-error@17{{cannot initialize}}
// expected-note@20{{in instantiation of function template specialization}}
// expected-note@30{{while substituting deduced template arguments}}
// expected-note@30{{in instantiation of function template specialization}}
T t2(5.0f);
} // namespace deferred_instantiation