[Clang][NFCI] Cleanup the fix for default function argument substitution (#104911)

(This is one step towards tweaking `getTemplateInstantiationArgs()` as
discussed in https://github.com/llvm/llvm-project/pull/102922)

We don't always substitute into default arguments while transforming a
function parameter. In that case, we would preserve the uninstantiated
expression until after, e.g. building up a CXXDefaultArgExpr and
instantiate the expression there.

For member function instantiation, this algorithm used to cause a
problem in that the default argument of an out-of-line member function
specialization couldn't get properly instantiated. This is because, in
`getTemplateInstantiationArgs()`, we would give up visiting a function's
declaration context if the function is a specialization of a member
template. For example,

```cpp
template <class T>
struct S {
  template <class U>
  void f(T = sizeof(T));
};

template <> template <class U>
void S<int>::f(int) {}
```

The default argument `sizeof(U)` that lexically appears inside the
declaration would be copied to the function declaration in the class
template specialization `S<int>`, as well as to the function's
out-of-line definition. We use template arguments collected from the
out-of-line function definition when substituting into the default
arguments. We would therefore give up the traversal after the function,
resulting in a single-level template argument of the `f` itself. However
the default argument here could still reference the template parameters
of the primary template, hence the error.

In fact, this is similar to constraint checking in some respects: we
actually want the "whole" template arguments relative to the primary
template, not those relative to the function definition. So this patch
adds another flag to indicate `getTemplateInstantiationArgs()` for that.

This patch also consolidates the tests for default arguments and removes
some unnecessary tests.
This commit is contained in:
Younan Zhang
2024-08-21 17:01:05 +08:00
committed by GitHub
parent 5f91de9d18
commit 8ac140f390
3 changed files with 22 additions and 20 deletions

View File

@@ -13071,12 +13071,19 @@ public:
/// ForConstraintInstantiation indicates we should continue looking when
/// encountering a lambda generic call operator, and continue looking for
/// arguments on an enclosing class template.
///
/// \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.
MultiLevelTemplateArgumentList getTemplateInstantiationArgs(
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 SkipForSpecialization = false,
bool ForDefaultArgumentSubstitution = false);
/// RAII object to handle the state changes required to synthesize
/// a function body.

View File

@@ -256,7 +256,8 @@ HandleClassTemplateSpec(const ClassTemplateSpecializationDecl *ClassTemplSpec,
Response HandleFunction(Sema &SemaRef, const FunctionDecl *Function,
MultiLevelTemplateArgumentList &Result,
const FunctionDecl *Pattern, bool RelativeToPrimary,
bool ForConstraintInstantiation) {
bool ForConstraintInstantiation,
bool ForDefaultArgumentSubstitution) {
// Add template arguments from a function template specialization.
if (!RelativeToPrimary &&
Function->getTemplateSpecializationKindForInstantiation() ==
@@ -286,7 +287,8 @@ Response HandleFunction(Sema &SemaRef, const FunctionDecl *Function,
// If this function was instantiated from a specialized member that is
// a function template, we're done.
assert(Function->getPrimaryTemplate() && "No function template?");
if (Function->getPrimaryTemplate()->isMemberSpecialization())
if (!ForDefaultArgumentSubstitution &&
Function->getPrimaryTemplate()->isMemberSpecialization())
return Response::Done();
// If this function is a generic lambda specialization, we are done.
@@ -468,7 +470,7 @@ MultiLevelTemplateArgumentList Sema::getTemplateInstantiationArgs(
const NamedDecl *ND, const DeclContext *DC, bool Final,
std::optional<ArrayRef<TemplateArgument>> Innermost, bool RelativeToPrimary,
const FunctionDecl *Pattern, bool ForConstraintInstantiation,
bool SkipForSpecialization) {
bool SkipForSpecialization, bool ForDefaultArgumentSubstitution) {
assert((ND || DC) && "Can't find arguments for a decl if one isn't provided");
// Accumulate the set of template argument lists in this structure.
MultiLevelTemplateArgumentList Result;
@@ -510,7 +512,8 @@ MultiLevelTemplateArgumentList Sema::getTemplateInstantiationArgs(
SkipForSpecialization);
} else if (const auto *Function = dyn_cast<FunctionDecl>(CurDecl)) {
R = HandleFunction(*this, Function, Result, Pattern, RelativeToPrimary,
ForConstraintInstantiation);
ForConstraintInstantiation,
ForDefaultArgumentSubstitution);
} else if (const auto *Rec = dyn_cast<CXXRecordDecl>(CurDecl)) {
R = HandleRecordDecl(*this, Rec, Result, Context,
ForConstraintInstantiation);
@@ -3227,7 +3230,6 @@ bool Sema::SubstDefaultArgument(
// default argument expression appears.
ContextRAII SavedContext(*this, FD);
std::unique_ptr<LocalInstantiationScope> LIS;
MultiLevelTemplateArgumentList NewTemplateArgs = TemplateArgs;
if (ForCallExpr) {
// When instantiating a default argument due to use in a call expression,
@@ -3240,19 +3242,10 @@ bool Sema::SubstDefaultArgument(
/*ForDefinition*/ false);
if (addInstantiatedParametersToScope(FD, PatternFD, *LIS, TemplateArgs))
return true;
const FunctionTemplateDecl *PrimaryTemplate = FD->getPrimaryTemplate();
if (PrimaryTemplate && PrimaryTemplate->isOutOfLine()) {
TemplateArgumentList *CurrentTemplateArgumentList =
TemplateArgumentList::CreateCopy(getASTContext(),
TemplateArgs.getInnermost());
NewTemplateArgs = getTemplateInstantiationArgs(
FD, FD->getDeclContext(), /*Final=*/false,
CurrentTemplateArgumentList->asArray(), /*RelativeToPrimary=*/true);
}
}
runWithSufficientStackSpace(Loc, [&] {
Result = SubstInitializer(PatternExpr, NewTemplateArgs,
Result = SubstInitializer(PatternExpr, TemplateArgs,
/*DirectInit*/ false);
});
}

View File

@@ -4699,10 +4699,12 @@ bool Sema::InstantiateDefaultArgument(SourceLocation CallLoc, FunctionDecl *FD,
//
// template<typename T>
// A<T> Foo(int a = A<T>::FooImpl());
MultiLevelTemplateArgumentList TemplateArgs =
getTemplateInstantiationArgs(FD, FD->getLexicalDeclContext(),
/*Final=*/false, /*Innermost=*/std::nullopt,
/*RelativeToPrimary=*/true);
MultiLevelTemplateArgumentList TemplateArgs = getTemplateInstantiationArgs(
FD, FD->getLexicalDeclContext(),
/*Final=*/false, /*Innermost=*/std::nullopt,
/*RelativeToPrimary=*/true, /*Pattern=*/nullptr,
/*ForConstraintInstantiation=*/false, /*SkipForSpecialization=*/false,
/*ForDefaultArgumentSubstitution=*/true);
if (SubstDefaultArgument(CallLoc, Param, TemplateArgs, /*ForCallExpr*/ true))
return true;