Skip to content

Commit

Permalink
[Clang][Sema] Fix explicit specializations of member function templat…
Browse files Browse the repository at this point in the history
…es with a deduced return type (#86817)

Clang erroneously rejects the following:
```
template<typename T>
struct A
{
    template<typename U>
    auto f();
};

template<>
template<typename U>
auto A<int>::f(); // error: conflicting types for 'f'
```
This happens because the explicit specialization of `f` has its return
type replaced with a dependent `AutoType` in `ActOnFunctionDeclarator`,
but no such replacement occurs for the implicitly instantiated function
template `A<int>::f`. Since the return types don't match, the explicit
specialization is diagnosed as an invalid redeclaration.

This patch moves the replacement of the return type to
`CheckFunctionDeclaration` so it also happens during instantiation.
`setObjectOfFriendDecl` will have been called by then, so the `isFriend
&& CurContext->isDependentContext()` condition is made redundant &
removed (as it already happens in `DeclContext::isDependentContext`).
`Sema::IsOverload` only checks the _declared_ return type (which isn't
changed by the adjustment), so adjusting the return type afterwards
should be safe.
  • Loading branch information
sdkrystian committed Apr 2, 2024
1 parent a4798bb commit eb08c0f
Show file tree
Hide file tree
Showing 4 changed files with 50 additions and 20 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
// RUN: not clang-tidy -checks='-*,modernize-use-override' %T/diagnostics/input.cpp -- -DCOMPILATION_ERROR 2>&1 | FileCheck -check-prefix=CHECK6 -implicit-check-not='{{warning:|error:}}' %s
// RUN: clang-tidy -checks='-*,modernize-use-override,clang-diagnostic-macro-redefined' %s -- -DMACRO_FROM_COMMAND_LINE -std=c++20 | FileCheck -check-prefix=CHECK4 -implicit-check-not='{{warning:|error:}}' %s
// RUN: clang-tidy -checks='-*,modernize-use-override,clang-diagnostic-macro-redefined,clang-diagnostic-literal-conversion' %s -- -DMACRO_FROM_COMMAND_LINE -std=c++20 -Wno-macro-redefined | FileCheck --check-prefix=CHECK7 -implicit-check-not='{{warning:|error:}}' %s
// RUN: not clang-tidy -checks='-*,modernize-use-override' %s -- -std=c++20 -DPR64602 | FileCheck -check-prefix=CHECK8 -implicit-check-not='{{warning:|error:}}' %s
// RUN: clang-tidy -checks='-*,modernize-use-override' %s -- -std=c++20 -DPR64602

// CHECK1: error: no input files [clang-diagnostic-error]
// CHECK1: error: no such file or directory: '{{.*}}nonexistent.cpp' [clang-diagnostic-error]
Expand Down Expand Up @@ -68,6 +68,4 @@ auto S<>::foo(auto)
{
return 1;
}
// CHECK8: error: conflicting types for 'foo' [clang-diagnostic-error]
// CHECK8: note: previous declaration is here
#endif
2 changes: 2 additions & 0 deletions clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -483,6 +483,8 @@ Bug Fixes to C++ Support
following the first `::` were ignored).
- Fix an out-of-bounds crash when checking the validity of template partial specializations. (part of #GH86757).
- Fix an issue caused by not handling invalid cases when substituting into the parameter mapping of a constraint. Fixes (#GH86757).
- Fixed a bug that prevented member function templates of class templates declared with a deduced return type
from being explicitly specialized for a given implicit instantiation of the class template.

Bug Fixes to AST Handling
^^^^^^^^^^^^^^^^^^^^^^^^^
Expand Down
46 changes: 29 additions & 17 deletions clang/lib/Sema/SemaDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10124,23 +10124,6 @@ Sema::ActOnFunctionDeclarator(Scope *S, Declarator &D, DeclContext *DC,
Diag(D.getDeclSpec().getVirtualSpecLoc(), diag::err_auto_fn_virtual);
}

if (getLangOpts().CPlusPlus14 &&
(NewFD->isDependentContext() ||
(isFriend && CurContext->isDependentContext())) &&
NewFD->getReturnType()->isUndeducedType()) {
// If the function template is referenced directly (for instance, as a
// member of the current instantiation), pretend it has a dependent type.
// This is not really justified by the standard, but is the only sane
// thing to do.
// FIXME: For a friend function, we have not marked the function as being
// a friend yet, so 'isDependentContext' on the FD doesn't work.
const FunctionProtoType *FPT =
NewFD->getType()->castAs<FunctionProtoType>();
QualType Result = SubstAutoTypeDependent(FPT->getReturnType());
NewFD->setType(Context.getFunctionType(Result, FPT->getParamTypes(),
FPT->getExtProtoInfo()));
}

// C++ [dcl.fct.spec]p3:
// The inline specifier shall not appear on a block scope function
// declaration.
Expand Down Expand Up @@ -12112,6 +12095,35 @@ bool Sema::CheckFunctionDeclaration(Scope *S, FunctionDecl *NewFD,

CheckConstPureAttributesUsage(*this, NewFD);

// C++ [dcl.spec.auto.general]p12:
// Return type deduction for a templated function with a placeholder in its
// declared type occurs when the definition is instantiated even if the
// function body contains a return statement with a non-type-dependent
// operand.
//
// C++ [temp.dep.expr]p3:
// An id-expression is type-dependent if it is a template-id that is not a
// concept-id and is dependent; or if its terminal name is:
// - [...]
// - associated by name lookup with one or more declarations of member
// functions of a class that is the current instantiation declared with a
// return type that contains a placeholder type,
// - [...]
//
// If this is a templated function with a placeholder in its return type,
// make the placeholder type dependent since it won't be deduced until the
// definition is instantiated. We do this here because it needs to happen
// for implicitly instantiated member functions/member function templates.
if (getLangOpts().CPlusPlus14 &&
(NewFD->isDependentContext() &&
NewFD->getReturnType()->isUndeducedType())) {
const FunctionProtoType *FPT =
NewFD->getType()->castAs<FunctionProtoType>();
QualType NewReturnType = SubstAutoTypeDependent(FPT->getReturnType());
NewFD->setType(Context.getFunctionType(NewReturnType, FPT->getParamTypes(),
FPT->getExtProtoInfo()));
}

// C++11 [dcl.constexpr]p8:
// A constexpr specifier for a non-static member function that is not
// a constructor declares that member function to be const.
Expand Down
18 changes: 18 additions & 0 deletions clang/test/SemaCXX/deduced-return-type-cxx14.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,24 @@ namespace Templates {
int (S::*(*p)())(double) = f;
int (S::*(*q)())(double) = f<S, double>;
}

template<typename T>
struct MemberSpecialization {
auto f();
template<typename U> auto f(U);
template<typename U> auto *f(U);
};

template<>
auto MemberSpecialization<int>::f();

template<>
template<typename U>
auto MemberSpecialization<int>::f(U);

template<>
template<typename U>
auto *MemberSpecialization<int>::f(U);
}

auto fwd_decl_using();
Expand Down

0 comments on commit eb08c0f

Please sign in to comment.