P0012R1: Make exception specifications be part of the type system. This

implements the bulk of the change (modifying the type system to include
exception specifications), but not all the details just yet.

llvm-svn: 284337
This commit is contained in:
Richard Smith 2016-10-16 17:54:23 +00:00
parent e3a9a676f9
commit 3c4f8d2e96
29 changed files with 445 additions and 85 deletions

View File

@ -3364,9 +3364,15 @@ public:
return reinterpret_cast<FunctionDecl *const *>(param_type_end())[1];
}
/// Determine whether this function type has a non-throwing exception
/// specification.
CanThrowResult canThrow(const ASTContext &Ctx) const;
/// Determine whether this function type has a non-throwing exception
/// specification. If this depends on template arguments, returns
/// \c ResultIfDependent.
bool isNothrow(const ASTContext &Ctx, bool ResultIfDependent = false) const;
bool isNothrow(const ASTContext &Ctx, bool ResultIfDependent = false) const {
return ResultIfDependent ? canThrow(Ctx) != CT_Can
: canThrow(Ctx) == CT_Cannot;
}
bool isVariadic() const { return Variadic; }

View File

@ -1644,7 +1644,8 @@ def err_init_conversion_failed : Error<
"%select{none|const|restrict|const and restrict|volatile|const and volatile|"
"volatile and restrict|const, volatile, and restrict}5 vs "
"%select{none|const|restrict|const and restrict|volatile|const and volatile|"
"volatile and restrict|const, volatile, and restrict}6)}4">;
"volatile and restrict|const, volatile, and restrict}6)"
"|: different exception specifications}4">;
def err_lvalue_to_rvalue_ref : Error<"rvalue reference %diff{to type $ cannot "
"bind to lvalue of type $|cannot bind to incompatible lvalue}0,1">;
@ -3278,7 +3279,8 @@ def note_ovl_candidate : Note<"candidate "
"%select{none|const|restrict|const and restrict|volatile|const and volatile"
"|volatile and restrict|const, volatile, and restrict}3 but found "
"%select{none|const|restrict|const and restrict|volatile|const and volatile"
"|volatile and restrict|const, volatile, and restrict}4)}2">;
"|volatile and restrict|const, volatile, and restrict}4)"
"| has different exception specification}2">;
def note_ovl_candidate_inherited_constructor : Note<
"constructor from base class %0 inherited here">;
@ -6101,7 +6103,8 @@ def note_hidden_overloaded_virtual_declared_here : Note<
"%select{none|const|restrict|const and restrict|volatile|const and volatile|"
"volatile and restrict|const, volatile, and restrict}2 vs "
"%select{none|const|restrict|const and restrict|volatile|const and volatile|"
"volatile and restrict|const, volatile, and restrict}3)}1">;
"volatile and restrict|const, volatile, and restrict}3)"
"|: different exception specifications}1">;
def warn_using_directive_in_header : Warning<
"using namespace directive in global context in header">,
InGroup<HeaderHygiene>, DefaultIgnore;
@ -6343,7 +6346,8 @@ def err_typecheck_convert_incompatible : Error<
"%select{none|const|restrict|const and restrict|volatile|const and volatile|"
"volatile and restrict|const, volatile, and restrict}5 vs "
"%select{none|const|restrict|const and restrict|volatile|const and volatile|"
"volatile and restrict|const, volatile, and restrict}6)}4">;
"volatile and restrict|const, volatile, and restrict}6)"
"|: different exception specifications}4">;
def err_typecheck_missing_return_type_incompatible : Error<
"%diff{return type $ must match previous return type $|"
"return type must match previous return type}0,1 when %select{block "

View File

@ -62,7 +62,7 @@ namespace clang {
ICK_Lvalue_To_Rvalue, ///< Lvalue-to-rvalue conversion (C++ 4.1)
ICK_Array_To_Pointer, ///< Array-to-pointer conversion (C++ 4.2)
ICK_Function_To_Pointer, ///< Function-to-pointer (C++ 4.3)
ICK_NoReturn_Adjustment, ///< Removal of noreturn from a type (Clang)
ICK_Function_Conversion, ///< Function pointer conversion (C++17 4.13)
ICK_Qualification, ///< Qualification conversions (C++ 4.4)
ICK_Integral_Promotion, ///< Integral promotions (C++ 4.5)
ICK_Floating_Promotion, ///< Floating point promotions (C++ 4.6)

View File

@ -2373,7 +2373,7 @@ public:
bool IgnoreBaseAccess);
bool IsQualificationConversion(QualType FromType, QualType ToType,
bool CStyle, bool &ObjCLifetimeConversion);
bool IsNoReturnConversion(QualType FromType, QualType ToType,
bool IsFunctionConversion(QualType FromType, QualType ToType,
QualType &ResultTy);
bool DiagnoseMultipleUserDefinedConversion(Expr *From, QualType ToType);
bool isSameOrCompatibleFunctionType(CanQualType Param, CanQualType Arg);

View File

@ -3193,6 +3193,34 @@ ASTContext::getCanonicalFunctionResultType(QualType ResultType) const {
return CanResultType;
}
static bool isCanonicalExceptionSpecification(
const FunctionProtoType::ExceptionSpecInfo &ESI, bool NoexceptInType) {
if (ESI.Type == EST_None)
return true;
if (!NoexceptInType)
return false;
// C++17 onwards: exception specification is part of the type, as a simple
// boolean "can this function type throw".
if (ESI.Type == EST_BasicNoexcept)
return true;
// A dynamic exception specification is canonical if it only contains pack
// expansions (so we can't tell whether it's non-throwing).
if (ESI.Type == EST_Dynamic) {
for (QualType ET : ESI.Exceptions)
if (!ET->getAs<PackExpansionType>())
return false;
return true;
}
// A noexcept(expr) specification is canonical if expr is value-dependent.
if (ESI.Type == EST_ComputedNoexcept)
return ESI.NoexceptExpr && ESI.NoexceptExpr->isValueDependent();
return false;
}
QualType
ASTContext::getFunctionType(QualType ResultTy, ArrayRef<QualType> ArgArray,
const FunctionProtoType::ExtProtoInfo &EPI) const {
@ -3209,10 +3237,14 @@ ASTContext::getFunctionType(QualType ResultTy, ArrayRef<QualType> ArgArray,
FunctionProtoTypes.FindNodeOrInsertPos(ID, InsertPos))
return QualType(FTP, 0);
bool NoexceptInType = getLangOpts().CPlusPlus1z;
bool IsCanonicalExceptionSpec =
isCanonicalExceptionSpecification(EPI.ExceptionSpec, NoexceptInType);
// Determine whether the type being created is already canonical or not.
bool isCanonical =
EPI.ExceptionSpec.Type == EST_None && isCanonicalResultType(ResultTy) &&
!EPI.HasTrailingReturn;
bool isCanonical = IsCanonicalExceptionSpec &&
isCanonicalResultType(ResultTy) && !EPI.HasTrailingReturn;
for (unsigned i = 0; i != NumArgs && isCanonical; ++i)
if (!ArgArray[i].isCanonicalAsParam())
isCanonical = false;
@ -3228,7 +3260,45 @@ ASTContext::getFunctionType(QualType ResultTy, ArrayRef<QualType> ArgArray,
FunctionProtoType::ExtProtoInfo CanonicalEPI = EPI;
CanonicalEPI.HasTrailingReturn = false;
CanonicalEPI.ExceptionSpec = FunctionProtoType::ExceptionSpecInfo();
if (IsCanonicalExceptionSpec) {
// Exception spec is already OK.
} else if (NoexceptInType) {
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.
CanonicalEPI.ExceptionSpec.Type = EST_None;
break;
case EST_DynamicNone: case EST_BasicNoexcept:
CanonicalEPI.ExceptionSpec.Type = EST_BasicNoexcept;
break;
case EST_ComputedNoexcept:
llvm::APSInt Value(1);
auto *E = CanonicalEPI.ExceptionSpec.NoexceptExpr;
if (!E || !E->isIntegerConstantExpr(Value, *this, nullptr,
/*IsEvaluated*/false)) {
// This noexcept specification is invalid.
// FIXME: Should this be able to happen?
CanonicalEPI.ExceptionSpec.Type = EST_None;
break;
}
CanonicalEPI.ExceptionSpec.Type =
Value.getBoolValue() ? EST_BasicNoexcept : EST_None;
break;
}
assert(isCanonicalExceptionSpecification(CanonicalEPI.ExceptionSpec,
NoexceptInType));
} else {
CanonicalEPI.ExceptionSpec = FunctionProtoType::ExceptionSpecInfo();
}
// Adjust the canonical function result type.
CanQualType CanResultTy = getCanonicalFunctionResultType(ResultTy);

View File

@ -2717,8 +2717,9 @@ FunctionProtoType::FunctionProtoType(QualType result, ArrayRef<QualType> params,
QualType *exnSlot = argSlot + NumParams;
unsigned I = 0;
for (QualType ExceptionType : epi.ExceptionSpec.Exceptions) {
// Note that a dependent exception specification does *not* make
// a type dependent; it's not even part of the C++ type system.
// Note that, before C++17, a dependent exception specification does
// *not* make a type dependent; it's not even part of the C++ type
// system.
if (ExceptionType->isInstantiationDependentType())
setInstantiationDependent();
@ -2757,6 +2758,19 @@ FunctionProtoType::FunctionProtoType(QualType result, ArrayRef<QualType> params,
slot[0] = epi.ExceptionSpec.SourceDecl;
}
// If this is a canonical type, and its exception specification is dependent,
// then it's a dependent type. This only happens in C++17 onwards.
if (isCanonicalUnqualified()) {
if (getExceptionSpecType() == EST_Dynamic ||
getExceptionSpecType() == EST_ComputedNoexcept) {
assert(hasDependentExceptionSpec() && "type should not be canonical");
setDependent();
}
} else if (getCanonicalTypeInternal()->isDependentType()) {
// Ask our canonical type whether our exception specification was dependent.
setDependent();
}
if (epi.ExtParameterInfos) {
ExtParameterInfo *extParamInfos =
const_cast<ExtParameterInfo *>(getExtParameterInfosBuffer());
@ -2801,29 +2815,28 @@ FunctionProtoType::getNoexceptSpec(const ASTContext &ctx) const {
return value.getBoolValue() ? NR_Nothrow : NR_Throw;
}
bool FunctionProtoType::isNothrow(const ASTContext &Ctx,
bool ResultIfDependent) const {
CanThrowResult FunctionProtoType::canThrow(const ASTContext &Ctx) const {
ExceptionSpecificationType EST = getExceptionSpecType();
assert(EST != EST_Unevaluated && EST != EST_Uninstantiated);
if (EST == EST_DynamicNone || EST == EST_BasicNoexcept)
return true;
return CT_Cannot;
if (EST == EST_Dynamic && ResultIfDependent) {
if (EST == EST_Dynamic) {
// A dynamic exception specification is throwing unless every exception
// type is an (unexpanded) pack expansion type.
for (unsigned I = 0, N = NumExceptions; I != N; ++I)
if (!getExceptionType(I)->getAs<PackExpansionType>())
return false;
return ResultIfDependent;
return CT_Can;
return CT_Dependent;
}
if (EST != EST_ComputedNoexcept)
return false;
return CT_Can;
NoexceptResult NR = getNoexceptSpec(Ctx);
if (NR == NR_Dependent)
return ResultIfDependent;
return NR == NR_Nothrow;
return CT_Dependent;
return NR == NR_Nothrow ? CT_Cannot : CT_Can;
}
bool FunctionProtoType::isTemplateVariadic() const {

View File

@ -2933,10 +2933,20 @@ bool Sema::MergeFunctionDecl(FunctionDecl *New, NamedDecl *&OldD,
}
if (getLangOpts().CPlusPlus) {
// (C++98 13.1p2):
// C++1z [over.load]p2
// Certain function declarations cannot be overloaded:
// -- Function declarations that differ only in the return type
// cannot be overloaded.
// -- Function declarations that differ only in the return type,
// the exception specification, or both cannot be overloaded.
// Check the exception specifications match. This may recompute the type of
// both Old and New if it resolved exception specifications, so grab the
// types again after this. Because this updates the type, we do this before
// any of the other checks below, which may update the "de facto" NewQType
// but do not necessarily update the type of New.
if (CheckEquivalentExceptionSpec(Old, New))
return true;
OldQType = Context.getCanonicalType(Old->getType());
NewQType = Context.getCanonicalType(New->getType());
// Go back to the type source info to compare the declared return types,
// per C++1y [dcl.type.auto]p13:
@ -2951,10 +2961,10 @@ bool Sema::MergeFunctionDecl(FunctionDecl *New, NamedDecl *&OldD,
(New->getTypeSourceInfo()
? New->getTypeSourceInfo()->getType()->castAs<FunctionType>()
: NewType)->getReturnType();
QualType ResQT;
if (!Context.hasSameType(OldDeclaredReturnType, NewDeclaredReturnType) &&
!((NewQType->isDependentType() || OldQType->isDependentType()) &&
New->isLocalExternDecl())) {
QualType ResQT;
if (NewDeclaredReturnType->isObjCObjectPointerType() &&
OldDeclaredReturnType->isObjCObjectPointerType())
ResQT = Context.mergeObjCGCQualifiers(NewQType, OldQType);
@ -3092,7 +3102,7 @@ bool Sema::MergeFunctionDecl(FunctionDecl *New, NamedDecl *&OldD,
// noreturn should now match unless the old type info didn't have it.
QualType OldQTypeForComparison = OldQType;
if (!OldTypeInfo.getNoReturn() && NewTypeInfo.getNoReturn()) {
assert(OldQType == QualType(OldType, 0));
auto *OldType = OldQType->castAs<FunctionProtoType>();
const FunctionType *OldTypeForComparison
= Context.adjustFunctionType(OldType, OldTypeInfo.withNoReturn(true));
OldQTypeForComparison = QualType(OldTypeForComparison, 0);

View File

@ -659,9 +659,6 @@ bool Sema::MergeCXXFunctionDecl(FunctionDecl *New, FunctionDecl *Old,
Invalid = true;
}
if (CheckEquivalentExceptionSpec(Old, New))
Invalid = true;
return Invalid;
}

View File

@ -130,6 +130,11 @@ bool Sema::CheckSpecifiedExceptionType(QualType &T, SourceRange Range) {
/// to member to a function with an exception specification. This means that
/// it is invalid to add another level of indirection.
bool Sema::CheckDistantExceptionSpec(QualType T) {
// C++17 removes this rule in favor of putting exception specifications into
// the type system.
if (getLangOpts().CPlusPlus1z)
return false;
if (const PointerType *PT = T->getAs<PointerType>())
T = PT->getPointeeType();
else if (const MemberPointerType *PT = T->getAs<MemberPointerType>())

View File

@ -7288,7 +7288,7 @@ checkPointerTypesForAssignment(Sema &S, QualType LHSType, QualType RHSType) {
return Sema::IncompatiblePointer;
}
if (!S.getLangOpts().CPlusPlus &&
S.IsNoReturnConversion(ltrans, rtrans, ltrans))
S.IsFunctionConversion(ltrans, rtrans, ltrans))
return Sema::IncompatiblePointer;
return ConvTy;
}

View File

@ -3610,7 +3610,7 @@ Sema::PerformImplicitConversion(Expr *From, QualType ToType,
// Nothing else to do.
break;
case ICK_NoReturn_Adjustment:
case ICK_Function_Conversion:
// If both sides are functions (or pointers/references to them), there could
// be incompatible exception declarations.
if (CheckExceptionSpecCompatibility(From, ToType))

View File

@ -150,7 +150,7 @@ static const char* GetImplicitConversionName(ImplicitConversionKind Kind) {
"Lvalue-to-rvalue",
"Array-to-pointer",
"Function-to-pointer",
"Noreturn adjustment",
"Function pointer conversion",
"Qualification",
"Integral promotion",
"Floating point promotion",
@ -1390,13 +1390,15 @@ Sema::PerformImplicitConversion(Expr *From, QualType ToType,
}
/// \brief Determine whether the conversion from FromType to ToType is a valid
/// conversion that strips "noreturn" off the nested function type.
bool Sema::IsNoReturnConversion(QualType FromType, QualType ToType,
/// conversion that strips "noexcept" or "noreturn" off the nested function
/// type.
bool Sema::IsFunctionConversion(QualType FromType, QualType ToType,
QualType &ResultTy) {
if (Context.hasSameUnqualifiedType(FromType, ToType))
return false;
// Permit the conversion F(t __attribute__((noreturn))) -> F(t)
// or F(t noexcept) -> F(t)
// where F adds one of the following at most once:
// - a pointer
// - a member pointer
@ -1425,11 +1427,37 @@ bool Sema::IsNoReturnConversion(QualType FromType, QualType ToType,
return false;
}
const FunctionType *FromFn = cast<FunctionType>(CanFrom);
FunctionType::ExtInfo EInfo = FromFn->getExtInfo();
if (!EInfo.getNoReturn()) return false;
const auto *FromFn = cast<FunctionType>(CanFrom);
FunctionType::ExtInfo FromEInfo = FromFn->getExtInfo();
const auto *ToFn = dyn_cast<FunctionProtoType>(CanTo);
FunctionType::ExtInfo ToEInfo = ToFn->getExtInfo();
bool Changed = false;
// Drop 'noreturn' if not present in target type.
if (FromEInfo.getNoReturn() && !ToEInfo.getNoReturn()) {
FromFn = Context.adjustFunctionType(FromFn, FromEInfo.withNoReturn(false));
Changed = true;
}
// Drop 'noexcept' if not present in target type.
if (const auto *FromFPT = dyn_cast<FunctionProtoType>(FromFn)) {
const auto *ToFPT = dyn_cast<FunctionProtoType>(ToFn);
if (FromFPT->isNothrow(Context) && !ToFPT->isNothrow(Context)) {
FromFn = cast<FunctionType>(
Context.getFunctionType(FromFPT->getReturnType(),
FromFPT->getParamTypes(),
FromFPT->getExtProtoInfo().withExceptionSpec(
FunctionProtoType::ExceptionSpecInfo()))
.getTypePtr());
Changed = true;
}
}
if (!Changed)
return false;
FromFn = Context.adjustFunctionType(FromFn, EInfo.withNoReturn(false));
assert(QualType(FromFn, 0).isCanonical());
if (QualType(FromFn, 0) != CanTo) return false;
@ -1534,7 +1562,7 @@ static bool IsStandardConversion(Sema &S, Expr* From, QualType ToType,
S.ExtractUnqualifiedFunctionType(ToType), FromType)) {
QualType resultTy;
// if the function type matches except for [[noreturn]], it's ok
if (!S.IsNoReturnConversion(FromType,
if (!S.IsFunctionConversion(FromType,
S.ExtractUnqualifiedFunctionType(ToType), resultTy))
// otherwise, only a boolean conversion is standard
if (!ToType->isBooleanType())
@ -1727,9 +1755,10 @@ static bool IsStandardConversion(Sema &S, Expr* From, QualType ToType,
// Compatible conversions (Clang extension for C function overloading)
SCS.Second = ICK_Compatible_Conversion;
FromType = ToType.getUnqualifiedType();
} else if (S.IsNoReturnConversion(FromType, ToType, FromType)) {
// Treat a conversion that strips "noreturn" as an identity conversion.
SCS.Second = ICK_NoReturn_Adjustment;
} else if (S.IsFunctionConversion(FromType, ToType, FromType)) {
// Function pointer conversions (removing 'noexcept') including removal of
// 'noreturn' (Clang extension).
SCS.Second = ICK_Function_Conversion;
} else if (IsTransparentUnionStandardConversion(S, From, ToType,
InOverloadResolution,
SCS, CStyle)) {
@ -2615,7 +2644,8 @@ enum {
ft_parameter_arity,
ft_parameter_mismatch,
ft_return_type,
ft_qualifer_mismatch
ft_qualifer_mismatch,
ft_noexcept
};
/// Attempts to get the FunctionProtoType from a Type. Handles
@ -2715,6 +2745,16 @@ void Sema::HandleFunctionTypeMismatch(PartialDiagnostic &PDiag,
return;
}
// Handle exception specification differences on canonical type (in C++17
// onwards).
if (cast<FunctionProtoType>(FromFunction->getCanonicalTypeUnqualified())
->isNothrow(Context) !=
cast<FunctionProtoType>(ToFunction->getCanonicalTypeUnqualified())
->isNothrow(Context)) {
PDiag << ft_noexcept;
return;
}
// Unable to find a difference, so add no extra info.
PDiag << ft_default;
}
@ -5096,7 +5136,7 @@ static bool CheckConvertedConstantConversions(Sema &S,
// conversions are fine.
switch (SCS.Second) {
case ICK_Identity:
case ICK_NoReturn_Adjustment:
case ICK_Function_Conversion:
case ICK_Integral_Promotion:
case ICK_Integral_Conversion: // Narrowing conversions are checked elsewhere.
return true;
@ -10428,7 +10468,7 @@ private:
bool candidateHasExactlyCorrectType(const FunctionDecl *FD) {
QualType Discard;
return Context.hasSameUnqualifiedType(TargetFunctionType, FD->getType()) ||
S.IsNoReturnConversion(FD->getType(), TargetFunctionType, Discard);
S.IsFunctionConversion(FD->getType(), TargetFunctionType, Discard);
}
/// \return true if A is considered a better overload candidate for the

View File

@ -956,9 +956,9 @@ bool Sema::isSameOrCompatibleFunctionType(CanQualType Param,
if (!ParamFunction || !ArgFunction)
return Param == Arg;
// Noreturn adjustment.
// Noreturn and noexcept adjustment.
QualType AdjustedParam;
if (IsNoReturnConversion(Param, Arg, AdjustedParam))
if (IsFunctionConversion(Param, Arg, AdjustedParam))
return Arg == Context.getCanonicalType(AdjustedParam);
// FIXME: Compatible calling conventions.
@ -2757,18 +2757,17 @@ CheckOriginalCallArgDeduction(Sema &S, Sema::OriginalCallArg OriginalArg,
}
// - The transformed A can be another pointer or pointer to member
// type that can be converted to the deduced A via a qualification
// conversion.
// type that can be converted to the deduced A via a function pointer
// conversion and/or a qualification conversion.
//
// Also allow conversions which merely strip [[noreturn]] from function types
// (recursively) as an extension.
// FIXME: Currently, this doesn't play nicely with qualification conversions.
bool ObjCLifetimeConversion = false;
QualType ResultTy;
if ((A->isAnyPointerType() || A->isMemberPointerType()) &&
(S.IsQualificationConversion(A, DeducedA, false,
ObjCLifetimeConversion) ||
S.IsNoReturnConversion(A, DeducedA, ResultTy)))
S.IsFunctionConversion(A, DeducedA, ResultTy)))
return false;

View File

@ -4142,7 +4142,7 @@ static TypeSourceInfo *GetFullTypeForDeclarator(TypeProcessingState &state,
// Exception specs are not allowed in typedefs. Complain, but add it
// anyway.
if (IsTypedefName && FTI.getExceptionSpecType())
if (IsTypedefName && FTI.getExceptionSpecType() && !LangOpts.CPlusPlus1z)
S.Diag(FTI.getExceptionSpecLocBeg(),
diag::err_exception_spec_in_typedef)
<< (D.getContext() == Declarator::AliasDeclContext ||

View File

@ -0,0 +1,43 @@
// RUN: %clang_cc1 -std=c++1z -verify %s
struct S;
typedef void Nothrow() noexcept;
typedef void Throw();
Nothrow *a;
Throw *b;
Nothrow S::*c;
Throw S::*d;
void test() {
a = b; // expected-error {{assigning to 'Nothrow *' (aka 'void (*)() noexcept') from incompatible type 'Throw *' (aka 'void (*)()'): different exception specifications}}
b = a;
c = d; // expected-error {{assigning to 'Nothrow S::*' from incompatible type 'Throw S::*': different exception specifications}}
d = c;
// Function pointer conversions do not combine properly with qualification conversions.
// FIXME: This seems like a defect.
Nothrow *const *pa = b; // expected-error {{cannot initialize}}
Throw *const *pb = a; // expected-error {{cannot initialize}}
Nothrow *const S::*pc = d; // expected-error {{cannot initialize}}
Throw *const S::*pd = c; // expected-error {{cannot initialize}}
}
// ... The result is a pointer to the function.
void f() noexcept;
constexpr void (*p)() = &f;
static_assert(f == p);
struct S { void f() noexcept; };
constexpr void (S::*q)() = &S::f;
static_assert(q == &S::f);
namespace std_example {
void (*p)() throw(int);
void (**pp)() noexcept = &p; // expected-error {{cannot initialize a variable of type 'void (**)() noexcept' with an rvalue of type 'void (**)() throw(int)'}}
struct S { typedef void (*p)(); operator p(); }; // expected-note {{candidate}}
void (*q)() noexcept = S(); // expected-error {{no viable conversion from 'std_example::S' to 'void (*)() noexcept'}}
}

View File

@ -1,4 +1,5 @@
// RUN: %clang_cc1 -std=c++11 -fsyntax-only -verify %s
// RUN: %clang_cc1 -std=c++1z -fsyntax-only -verify %s
void f0() &; // expected-error {{non-member function cannot have '&' qualifier}}
void f1() &&; // expected-error {{non-member function cannot have '&&' qualifier}}
@ -58,3 +59,16 @@ template<typename T> struct pass {
};
pass<func_type_lvalue> pass0;
pass<func_type_lvalue> pass1;
template<typename T, typename U> struct is_same { static const bool value = false; };
template<typename T> struct is_same<T, T> { static const bool value = true; };
constexpr bool cxx1z = __cplusplus > 201402L;
void noexcept_true() noexcept(true);
void noexcept_false() noexcept(false);
using func_type_noexcept_true = wrap<decltype(noexcept_true)>;
using func_type_noexcept_false = wrap<decltype(noexcept_false)>;
static_assert(is_same<func_type_noexcept_false, func_type_noexcept_true>::value == !cxx1z, "");
static_assert(is_same<func_type_noexcept_false::val, func_type_noexcept_true::val>::value == !cxx1z, "");
static_assert(is_same<func_type_noexcept_false::ptr, func_type_noexcept_true::ptr>::value == !cxx1z, "");
static_assert(is_same<func_type_noexcept_false::ref, func_type_noexcept_true::ref>::value == !cxx1z, "");

View File

@ -279,16 +279,36 @@ namespace dr25 { // dr25: yes
void f() throw(int);
};
void (A::*f)() throw (int);
void (A::*g)() throw () = f; // expected-error {{is not superset of source}}
void (A::*g)() throw () = f;
#if __cplusplus <= 201402L
// expected-error@-2 {{is not superset of source}}
#else
// expected-error@-4 {{different exception specifications}}
#endif
void (A::*g2)() throw () = 0;
void (A::*h)() throw (int, char) = f;
void (A::*i)() throw () = &A::f; // expected-error {{is not superset of source}}
void (A::*i)() throw () = &A::f;
#if __cplusplus <= 201402L
// expected-error@-2 {{is not superset of source}}
#else
// expected-error@-4 {{different exception specifications}}
#endif
void (A::*i2)() throw () = 0;
void (A::*j)() throw (int, char) = &A::f;
void x() {
g2 = f; // expected-error {{is not superset}}
g2 = f;
#if __cplusplus <= 201402L
// expected-error@-2 {{is not superset of source}}
#else
// expected-error@-4 {{different exception specifications}}
#endif
h = f;
i2 = &A::f; // expected-error {{is not superset}}
i2 = &A::f;
#if __cplusplus <= 201402L
// expected-error@-2 {{is not superset of source}}
#else
// expected-error@-4 {{different exception specifications}}
#endif
j = &A::f;
}
}
@ -997,20 +1017,31 @@ namespace dr92 { // FIXME: Issue is still open.
void f() throw(int, float);
void (*p)() throw(int) = &f; // expected-error {{target exception specification is not superset of source}}
void (*q)() throw(int);
void (**pp)() throw() = &q; // expected-error {{exception specifications are not allowed}}
void (**pp)() throw() = &q;
#if __cplusplus <= 201402L
// expected-error@-2 {{exception specifications are not allowed}}
#else
// expected-error@-4 {{cannot initialize}}
#endif
void g(void() throw());
void h() {
g(f); // expected-error {{is not superset}}
g(q); // expected-error {{is not superset}}
void g(void() throw()); // expected-note 0-2 {{no known conversion}}
void h() throw() {
g(f); // expected-error-re {{{{is not superset|no matching function}}}}
g(q); // expected-error-re {{{{is not superset|no matching function}}}}
}
// Prior to C++17, this is OK because the exception specification is not
// considered in this context. In C++17, we *do* perform an implicit
// conversion (which performs initialization), but we convert to the type of
// the template parameter, which does not include the exception specification.
// conversion (which performs initialization), and the exception specification
// is part of the type of the parameter, so this is invalid.
template<void() throw()> struct X {};
X<&f> xp; // ok
X<&f> xp;
#if __cplusplus > 201402L
// expected-error@-2 {{not implicitly convertible}}
#endif
template<void() throw(int)> struct Y {};
Y<&h> yp; // ok
}
// dr93: na

View File

@ -235,9 +235,6 @@ namespace dr125 {
friend dr125_A (::dr125_B::dr125_C)(); // ok
friend dr125_A::dr125_B::dr125_C(); // expected-error {{did you mean the constructor name 'dr125_B'?}}
// expected-error@-1 {{missing exception specification}}
#if __cplusplus >= 201103L
// expected-error@-3 {{follows constexpr declaration}} expected-note@-10 {{here}}
#endif
};
}

View File

@ -984,10 +984,19 @@ namespace dr289 { // dr289: yes
namespace dr294 { // dr294: no
void f() throw(int);
int main() {
(void)static_cast<void (*)() throw()>(f); // FIXME: ill-formed
(void)static_cast<void (*)() throw(int)>(f); // FIXME: ill-formed
(void)static_cast<void (*)() throw()>(f); // FIXME: ill-formed in C++14 and before
#if __cplusplus > 201402L
// FIXME: expected-error@-2 {{not allowed}}
//
// Irony: the above is valid in C++17 and beyond, but that's exactly when
// we reject it. In C++14 and before, this is ill-formed because an
// exception-specification is not permitted in a type-id. In C++17, this is
// valid because it's the inverse of a standard conversion sequence
// containing a function pointer conversion.
#endif
(void)static_cast<void (*)() throw(int)>(f); // FIXME: ill-formed in C++14 and before
void (*p)() throw() = f; // expected-error {{not superset}}
void (*p)() throw() = f; // expected-error-re {{{{not superset|different exception specification}}}}
void (*q)() throw(int) = f;
}
}

View File

@ -0,0 +1,17 @@
// RUN: %clang_cc1 -std=c++1z -fexceptions -fcxx-exceptions -fsyntax-only -verify %s
// In C++1z, we can put an exception-specification on any function declarator; the
// corresponding paragraph from C++14 and before was deleted.
// expected-no-diagnostics
void f() noexcept;
void (*fp)() noexcept;
void (**fpp)() noexcept;
void g(void (**pfa)() noexcept);
void (**h())() noexcept;
template<typename T> struct A {};
template<void() noexcept> struct B {};
A<void() noexcept> a;
B<f> b;
auto *p = new decltype(f)**;

View File

@ -37,6 +37,8 @@ namespace dyn {
// Pointer to function returning pointer to pointer to function with spec
void (**(*h())())() throw(int); // expected-error {{not allowed beyond a single}}
// FIXME: Missing a lot of negative tests, primarily type-ids in various places
// We fail to diagnose all of those.
}
namespace noex {

View File

@ -1,4 +1,5 @@
// RUN: %clang_cc1 -fsyntax-only -std=c++11 -verify %s
// RUN: %clang_cc1 -fsyntax-only -std=c++1z -verify %s
// A converted constant expression of type T is a core constant expression,
int nonconst = 8; // expected-note 3 {{here}}
@ -40,10 +41,10 @@ const E e10 = E10;
template<E> struct T {};
T<e10> s10;
// integral promotions, and
// integral promotions,
enum class EE { EE32 = ' ', EE65 = 'A', EE1 = (short)1, EE5 = E5 };
// integral conversions other than narrowing conversions
// integral conversions other than narrowing conversions,
int b(unsigned n) {
switch (n) {
case E6:
@ -74,12 +75,22 @@ using Int = A<-3>; // expected-error {{template argument evaluates to -3, which
// Note, conversions from integral or unscoped enumeration types to bool are
// integral conversions as well as boolean conversions.
// FIXME: Per core issue 1407, this is not correct.
template<typename T, T v> struct Val { static constexpr T value = v; };
static_assert(Val<bool, E1>::value == 1, ""); // ok
static_assert(Val<bool, '\0'>::value == 0, ""); // ok
static_assert(Val<bool, U'\1'>::value == 1, ""); // ok
static_assert(Val<bool, E5>::value == 1, ""); // expected-error {{5, which cannot be narrowed to type 'bool'}}
// function pointer conversions [C++17]
void noexcept_false() noexcept(false);
void noexcept_true() noexcept(true);
Val<decltype(&noexcept_false), &noexcept_true> remove_noexcept;
Val<decltype(&noexcept_true), &noexcept_false> add_noexcept;
#if __cplusplus > 201402L
// expected-error@-2 {{value of type 'void (*)() noexcept(false)' is not implicitly convertible to 'void (*)() noexcept'}}
#endif
// (no other conversions are permitted)
using Int = A<1.0>; // expected-error {{conversion from 'double' to 'unsigned char' is not allowed in a converted constant expression}}
enum B : bool {

View File

@ -1,4 +1,5 @@
// RUN: %clang_cc1 -fsyntax-only -std=c++11 %s -verify
// RUN: %clang_cc1 -fsyntax-only -std=c++1z %s -verify
void test_conversion() {
int (*fp1)(int) = [](int x) { return x + 1; };
@ -9,6 +10,15 @@ void test_conversion() {
volatile const auto lambda2 = [](int x) { }; // expected-note{{but method is not marked volatile}}
void (*fp4)(int) = lambda2; // expected-error{{no viable conversion}}
void (*fp5)(int) noexcept = [](int x) { };
#if __cplusplus > 201402L
// expected-error@-2 {{no viable}} expected-note@-2 {{candidate}}
void (*fp5a)(int) noexcept = [](auto x) { };
// expected-error@-1 {{no viable}} expected-note@-1 {{candidate}}
void (*fp5b)(int) noexcept = [](auto x) noexcept { };
#endif
void (*fp6)(int) noexcept = [](int x) noexcept { };
}
void test_no_conversion() {

View File

@ -0,0 +1,12 @@
// RUN: %clang_cc1 -std=c++1z -verify %s
void f(void() noexcept); // expected-note {{no known conversion from 'void ()' to 'void (*)() noexcept'}}
void f(void()) = delete; // expected-note {{explicitly deleted}}
void g();
void h() noexcept;
void test() {
f(g); // expected-error {{call to deleted}}
f(h);
}

View File

@ -1,7 +1,9 @@
// RUN: %clang_cc1 -fsyntax-only %s
// RUN: %clang_cc1 -fsyntax-only -DNOEXCEPT= %s
// RUN: %clang_cc1 -fsyntax-only -std=c++1z -DNOEXCEPT= %s
// FIXME: %clang_cc1 -fsyntax-only -std=c++1z -DNOEXCEPT=noexcept %s
template<typename T> T f0(T);
int f0(int);
template<typename T> T f0(T) NOEXCEPT;
int f0(int) NOEXCEPT;
// -- an object or reference being initialized
struct S {

View File

@ -1,5 +1,5 @@
// RUN: %clang_cc1 -fsyntax-only -verify %s
// expected-no-diagnostics
// RUN: %clang_cc1 -fsyntax-only -verify -std=c++1z %s
namespace PR8598 {
template<class T> struct identity { typedef T type; };
@ -19,3 +19,28 @@ namespace PR12132 {
fun(&A::x);
}
}
#if __cplusplus > 201402L
namespace noexcept_conversion {
template<typename R> void foo(R());
template<typename R> void bar(R()) = delete;
template<typename R> void bar(R() noexcept) {}
void f() throw() {
foo(&f);
bar(&f);
}
// There is no corresponding rule for references.
// FIXME: This seems like a defect.
template<typename R> void baz(R(&)()); // expected-note {{does not match adjusted type}}
void g() {
baz(f); // expected-error {{no match}}
}
void g1() noexcept;
void g2();
template <class T> int h(T *, T *); // expected-note {{deduced conflicting types for parameter 'T' ('void () noexcept' vs. 'void ()')}}
int x = h(g1, g2); // expected-error {{no matching function}}
}
#else
// expected-no-diagnostics
#endif

View File

@ -0,0 +1,43 @@
// RUN: %clang_cc1 -std=c++1z -verify %s
template<typename T, bool B> using Fn = T () noexcept(B);
// - If the original A is a function pointer type, A can be "pointer to
// function" even if the deduced A is "pointer to noexcept function".
struct A {
template<typename T> operator Fn<T, false>*(); // expected-note {{candidate}}
};
struct B {
template<typename T> operator Fn<T, true>*();
};
void (*p1)() = A();
void (*p2)() = B();
void (*p3)() noexcept = A(); // expected-error {{no viable conversion}}
void (*p4)() noexcept = B();
// - If the original A is a pointer to member function type, A can be "pointer
// to member of type function" even if the deduced A is "pointer to member of
// type noexcept function".
struct C {
template<typename T> operator Fn<T, false> A::*(); // expected-note {{candidate}}
};
struct D {
template<typename T> operator Fn<T, true> A::*();
};
void (A::*q1)() = C();
void (A::*q2)() = D();
void (A::*q3)() noexcept = C(); // expected-error {{no viable conversion}}
void (A::*q4)() noexcept = D();
// There is no corresponding rule for references.
// FIXME: This seems like a defect.
struct E {
template<typename T> operator Fn<T, false>&(); // expected-note {{candidate}}
};
struct F {
template<typename T> operator Fn<T, true>&(); // expected-note {{candidate}}
};
void (&r1)() = E();
void (&r2)() = F(); // expected-error {{no viable conversion}}
void (&r3)() noexcept = E(); // expected-error {{no viable conversion}}
void (&r4)() noexcept = F();

View File

@ -142,11 +142,11 @@ namespace PR13527 {
Y &operator=(Y&&) = default;
~Y() = default;
};
Y::Y() = default; // expected-error {{definition of explicitly defaulted}}
Y::Y(const Y&) = default; // expected-error {{definition of explicitly defaulted}}
Y::Y(Y&&) = default; // expected-error {{definition of explicitly defaulted}}
Y &Y::operator=(const Y&) = default; // expected-error {{definition of explicitly defaulted}}
Y &Y::operator=(Y&&) = default; // expected-error {{definition of explicitly defaulted}}
Y::Y() noexcept = default; // expected-error {{definition of explicitly defaulted}}
Y::Y(const Y&) noexcept = default; // expected-error {{definition of explicitly defaulted}}
Y::Y(Y&&) noexcept = default; // expected-error {{definition of explicitly defaulted}}
Y &Y::operator=(const Y&) noexcept = default; // expected-error {{definition of explicitly defaulted}}
Y &Y::operator=(Y&&) noexcept = default; // expected-error {{definition of explicitly defaulted}}
Y::~Y() = default; // expected-error {{definition of explicitly defaulted}}
}

View File

@ -7,9 +7,9 @@
#include "Inputs/register.h"
void f() throw();
void g() throw(int);
void h() throw(...);
void g() throw();
void h() throw(int);
void i() throw(...);
#if __cplusplus >= 201103L
// expected-warning@-4 {{dynamic exception specifications are deprecated}} expected-note@-4 {{use 'noexcept' instead}}
// expected-warning@-4 {{dynamic exception specifications are deprecated}} expected-note@-4 {{use 'noexcept(false)' instead}}