When two function types have equivalent (but distinct) noexcept specifications, create separate type sugar nodes. This is necessary so that substitution into the exception specification will substitute into the correct expression.

llvm-svn: 284519
This commit is contained in:
Richard Smith 2016-10-18 19:29:18 +00:00
parent 476560aac4
commit 2a2cda58f8
3 changed files with 83 additions and 30 deletions

View File

@ -3150,15 +3150,17 @@ static bool isCanonicalExceptionSpecification(
return true;
// A dynamic exception specification is canonical if it only contains pack
// expansions (so we can't tell whether it's non-throwing).
// expansions (so we can't tell whether it's non-throwing) and all its
// contained types are canonical.
if (ESI.Type == EST_Dynamic) {
for (QualType ET : ESI.Exceptions)
if (!ET->getAs<PackExpansionType>())
if (!ET.isCanonical() || !ET->getAs<PackExpansionType>())
return false;
return true;
}
// A noexcept(expr) specification is canonical if expr is value-dependent.
// A noexcept(expr) specification is (possibly) canonical if expr is
// value-dependent.
if (ESI.Type == EST_ComputedNoexcept)
return ESI.NoexceptExpr && ESI.NoexceptExpr->isValueDependent();
@ -3170,33 +3172,50 @@ ASTContext::getFunctionType(QualType ResultTy, ArrayRef<QualType> ArgArray,
const FunctionProtoType::ExtProtoInfo &EPI) const {
size_t NumArgs = ArgArray.size();
bool NoexceptInType = getLangOpts().CPlusPlus1z;
// Unique functions, to guarantee there is only one function of a particular
// structure.
llvm::FoldingSetNodeID ID;
FunctionProtoType::Profile(ID, ResultTy, ArgArray.begin(), NumArgs, EPI,
*this, true);
QualType Canonical;
bool Unique = false;
void *InsertPos = nullptr;
if (FunctionProtoType *FPT =
FunctionProtoTypes.FindNodeOrInsertPos(ID, InsertPos)) {
QualType Existing = QualType(FPT, 0);
// If we find a pre-existing equivalent FunctionProtoType, we can just reuse
// it so long as our exception specification doesn't contain a dependent
// noexcept expression. If it /does/, we're going to need to create a type
// sugar node to hold the concrete expression.
if (EPI.ExceptionSpec.Type != EST_ComputedNoexcept ||
EPI.ExceptionSpec.NoexceptExpr == FPT->getNoexceptExpr())
return Existing;
// We need a new type sugar node for this one, to hold the new noexcept
// expression. We do no canonicalization here, but that's OK since we don't
// expect to see the same noexcept expression much more than once.
Canonical = getCanonicalType(Existing);
Unique = true;
}
bool NoexceptInType = getLangOpts().CPlusPlus1z;
bool IsCanonicalExceptionSpec =
isCanonicalExceptionSpecification(EPI.ExceptionSpec, NoexceptInType);
// Determine whether the type being created is already canonical or not.
bool isCanonical = IsCanonicalExceptionSpec &&
bool isCanonical = !Unique && IsCanonicalExceptionSpec &&
isCanonicalResultType(ResultTy) && !EPI.HasTrailingReturn;
for (unsigned i = 0; i != NumArgs && isCanonical; ++i)
if (!ArgArray[i].isCanonicalAsParam())
isCanonical = false;
// Unique functions, to guarantee there is only one function of a particular
// structure.
llvm::FoldingSetNodeID ID;
FunctionProtoType::Profile(ID, ResultTy, ArgArray.begin(), NumArgs, EPI,
*this, isCanonical);
void *InsertPos = nullptr;
if (FunctionProtoType *FTP =
FunctionProtoTypes.FindNodeOrInsertPos(ID, InsertPos))
return QualType(FTP, 0);
// If this type isn't canonical, get the canonical version of it.
// The exception spec is not part of the canonical type.
QualType Canonical;
if (!isCanonical) {
// If this type isn't canonical, get the canonical version of it if we don't
// already have it. The exception spec is only partially part of the
// canonical type, and only in C++17 onwards.
if (!isCanonical && Canonical.isNull()) {
SmallVector<QualType, 16> CanonicalArgs;
CanonicalArgs.reserve(NumArgs);
for (unsigned i = 0; i != NumArgs; ++i)
@ -3208,17 +3227,34 @@ ASTContext::getFunctionType(QualType ResultTy, ArrayRef<QualType> ArgArray,
if (IsCanonicalExceptionSpec) {
// Exception spec is already OK.
} else if (NoexceptInType) {
llvm::SmallVector<QualType, 8> ExceptionTypeStorage;
switch (EPI.ExceptionSpec.Type) {
case EST_Unparsed: case EST_Unevaluated: case EST_Uninstantiated:
// We don't know yet. It shouldn't matter what we pick here; no-one
// should ever look at this.
LLVM_FALLTHROUGH;
case EST_None: case EST_MSAny: case EST_Dynamic:
// If we get here for EST_Dynamic, there is at least one
// non-pack-expansion type, so this is not non-throwing.
case EST_None: case EST_MSAny:
CanonicalEPI.ExceptionSpec.Type = EST_None;
break;
// A dynamic exception specification is almost always "not noexcept",
// with the exception that a pack expansion might expand to no types.
case EST_Dynamic: {
bool AnyPacks = false;
for (QualType ET : EPI.ExceptionSpec.Exceptions) {
if (ET->getAs<PackExpansionType>())
AnyPacks = true;
ExceptionTypeStorage.push_back(getCanonicalType(ET));
}
if (!AnyPacks)
CanonicalEPI.ExceptionSpec.Type = EST_None;
else {
CanonicalEPI.ExceptionSpec.Type = EST_Dynamic;
CanonicalEPI.ExceptionSpec.Exceptions = ExceptionTypeStorage;
}
break;
}
case EST_DynamicNone: case EST_BasicNoexcept:
CanonicalEPI.ExceptionSpec.Type = EST_BasicNoexcept;
break;
@ -3289,7 +3325,8 @@ ASTContext::getFunctionType(QualType ResultTy, ArrayRef<QualType> ArgArray,
FunctionProtoType::ExtProtoInfo newEPI = EPI;
new (FTP) FunctionProtoType(ResultTy, ArgArray, Canonical, newEPI);
Types.push_back(FTP);
FunctionProtoTypes.InsertNode(FTP, InsertPos);
if (!Unique)
FunctionProtoTypes.InsertNode(FTP, InsertPos);
return QualType(FTP, 0);
}

View File

@ -87,17 +87,14 @@ namespace DefaultedFnExceptionSpec {
template<typename T>
struct Error {
// FIXME: Type canonicalization causes all the errors to point at the first
// declaration which has the type 'void () noexcept (T::error)'. We should
// get one error for 'Error<int>::Error()' and one for 'Error<int>::~Error()'.
void f() noexcept(T::error); // expected-error 2{{has no members}}
void f() noexcept(T::error);
Error() noexcept(T::error);
Error() noexcept(T::error); // expected-error {{type 'int' cannot be used prior to '::' because it has no members}}
Error(const Error&) noexcept(T::error);
Error(Error&&) noexcept(T::error);
Error &operator=(const Error&) noexcept(T::error);
Error &operator=(Error&&) noexcept(T::error);
~Error() noexcept(T::error);
~Error() noexcept(T::error); // expected-error {{type 'int' cannot be used prior to '::' because it has no members}}
};
struct DelayImplicit {

View File

@ -11,3 +11,22 @@ template<bool A, bool B> void redecl2() noexcept(B); // expected-error {{conflic
// FIXME: It's not clear whether this is supposed to be valid.
template<typename A, typename B> void redecl3() throw(A);
template<typename A, typename B> void redecl3() throw(B);
namespace DependentDefaultCtorExceptionSpec {
template<typename> struct T { static const bool value = true; };
template<class A> struct map {
typedef A a;
map() noexcept(T<a>::value) {}
};
template<class B> struct multimap {
typedef B b;
multimap() noexcept(T<b>::value) {}
};
// Don't crash here.
struct A { multimap<int> Map; } a;
static_assert(noexcept(A()));
}