diff options
29 files changed, 445 insertions, 85 deletions
diff --git a/clang/include/clang/AST/Type.h b/clang/include/clang/AST/Type.h index fb1b50f7aac..e9351a9e514 100644 --- a/clang/include/clang/AST/Type.h +++ b/clang/include/clang/AST/Type.h @@ -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; } diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index b92ca3d34b9..e274b464652 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -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 " diff --git a/clang/include/clang/Sema/Overload.h b/clang/include/clang/Sema/Overload.h index 1c2eb927895..b3447c58abb 100644 --- a/clang/include/clang/Sema/Overload.h +++ b/clang/include/clang/Sema/Overload.h @@ -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) diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h index a50dd66791e..6e0f3797643 100644 --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -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); diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp index 709de4368d8..34eced3f4b1 100644 --- a/clang/lib/AST/ASTContext.cpp +++ b/clang/lib/AST/ASTContext.cpp @@ -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); diff --git a/clang/lib/AST/Type.cpp b/clang/lib/AST/Type.cpp index 113974c4b60..2601fa44ccc 100644 --- a/clang/lib/AST/Type.cpp +++ b/clang/lib/AST/Type.cpp @@ -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 { diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp index efdf8a3226f..54b56b3b594 100644 --- a/clang/lib/Sema/SemaDecl.cpp +++ b/clang/lib/Sema/SemaDecl.cpp @@ -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); diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp index 93e626f7d32..f86b793df57 100644 --- a/clang/lib/Sema/SemaDeclCXX.cpp +++ b/clang/lib/Sema/SemaDeclCXX.cpp @@ -659,9 +659,6 @@ bool Sema::MergeCXXFunctionDecl(FunctionDecl *New, FunctionDecl *Old, Invalid = true; } - if (CheckEquivalentExceptionSpec(Old, New)) - Invalid = true; - return Invalid; } diff --git a/clang/lib/Sema/SemaExceptionSpec.cpp b/clang/lib/Sema/SemaExceptionSpec.cpp index f8e75b2fe79..a81ef517954 100644 --- a/clang/lib/Sema/SemaExceptionSpec.cpp +++ b/clang/lib/Sema/SemaExceptionSpec.cpp @@ -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>()) diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp index ec7f7bc02ce..206a704fdce 100644 --- a/clang/lib/Sema/SemaExpr.cpp +++ b/clang/lib/Sema/SemaExpr.cpp @@ -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; } diff --git a/clang/lib/Sema/SemaExprCXX.cpp b/clang/lib/Sema/SemaExprCXX.cpp index 4de7fc310b4..56f593439d7 100644 --- a/clang/lib/Sema/SemaExprCXX.cpp +++ b/clang/lib/Sema/SemaExprCXX.cpp @@ -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)) diff --git a/clang/lib/Sema/SemaOverload.cpp b/clang/lib/Sema/SemaOverload.cpp index 082a6f98ef2..460e53fc261 100644 --- a/clang/lib/Sema/SemaOverload.cpp +++ b/clang/lib/Sema/SemaOverload.cpp @@ -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 diff --git a/clang/lib/Sema/SemaTemplateDeduction.cpp b/clang/lib/Sema/SemaTemplateDeduction.cpp index 4f76a029dcd..2e4b9caa4ab 100644 --- a/clang/lib/Sema/SemaTemplateDeduction.cpp +++ b/clang/lib/Sema/SemaTemplateDeduction.cpp @@ -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; diff --git a/clang/lib/Sema/SemaType.cpp b/clang/lib/Sema/SemaType.cpp index cfbfc3b09f2..b1daab44095 100644 --- a/clang/lib/Sema/SemaType.cpp +++ b/clang/lib/Sema/SemaType.cpp @@ -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 || diff --git a/clang/test/CXX/conv/conv.fctptr/p1.cpp b/clang/test/CXX/conv/conv.fctptr/p1.cpp new file mode 100644 index 00000000000..810c7f5e803 --- /dev/null +++ b/clang/test/CXX/conv/conv.fctptr/p1.cpp @@ -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'}} +} diff --git a/clang/test/CXX/dcl.decl/dcl.meaning/dcl.fct/p6-0x.cpp b/clang/test/CXX/dcl.decl/dcl.meaning/dcl.fct/p6-0x.cpp index cec747e1d60..d93cc8b9087 100644 --- a/clang/test/CXX/dcl.decl/dcl.meaning/dcl.fct/p6-0x.cpp +++ b/clang/test/CXX/dcl.decl/dcl.meaning/dcl.fct/p6-0x.cpp @@ -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, ""); diff --git a/clang/test/CXX/drs/dr0xx.cpp b/clang/test/CXX/drs/dr0xx.cpp index d598cfa33bd..2e895ed3869 100644 --- a/clang/test/CXX/drs/dr0xx.cpp +++ b/clang/test/CXX/drs/dr0xx.cpp @@ -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 diff --git a/clang/test/CXX/drs/dr1xx.cpp b/clang/test/CXX/drs/dr1xx.cpp index 60d6ccaa875..58626c99780 100644 --- a/clang/test/CXX/drs/dr1xx.cpp +++ b/clang/test/CXX/drs/dr1xx.cpp @@ -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 }; } diff --git a/clang/test/CXX/drs/dr2xx.cpp b/clang/test/CXX/drs/dr2xx.cpp index 7372ecb26c8..0fa2500d260 100644 --- a/clang/test/CXX/drs/dr2xx.cpp +++ b/clang/test/CXX/drs/dr2xx.cpp @@ -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; } } diff --git a/clang/test/CXX/except/except.spec/p2-places-1z.cpp b/clang/test/CXX/except/except.spec/p2-places-1z.cpp new file mode 100644 index 00000000000..619ea33b534 --- /dev/null +++ b/clang/test/CXX/except/except.spec/p2-places-1z.cpp @@ -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)**; diff --git a/clang/test/CXX/except/except.spec/p2-places.cpp b/clang/test/CXX/except/except.spec/p2-places.cpp index 67647fb043a..ea842af8984 100644 --- a/clang/test/CXX/except/except.spec/p2-places.cpp +++ b/clang/test/CXX/except/except.spec/p2-places.cpp @@ -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 { diff --git a/clang/test/CXX/expr/expr.const/p3-0x.cpp b/clang/test/CXX/expr/expr.const/p3-0x.cpp index d9d84853eba..731e0c312fa 100644 --- a/clang/test/CXX/expr/expr.const/p3-0x.cpp +++ b/clang/test/CXX/expr/expr.const/p3-0x.cpp @@ -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 { diff --git a/clang/test/CXX/expr/expr.prim/expr.prim.lambda/p6.cpp b/clang/test/CXX/expr/expr.prim/expr.prim.lambda/p6.cpp index 8b43cefa92c..90a3aec50cb 100644 --- a/clang/test/CXX/expr/expr.prim/expr.prim.lambda/p6.cpp +++ b/clang/test/CXX/expr/expr.prim/expr.prim.lambda/p6.cpp @@ -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() { diff --git a/clang/test/CXX/over/over.match/over.match.best/over.best.ics/over.ics.scs/p3.cpp b/clang/test/CXX/over/over.match/over.match.best/over.best.ics/over.ics.scs/p3.cpp new file mode 100644 index 00000000000..df2fc2cd2e8 --- /dev/null +++ b/clang/test/CXX/over/over.match/over.match.best/over.best.ics/over.ics.scs/p3.cpp @@ -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); +} diff --git a/clang/test/CXX/over/over.over/p1.cpp b/clang/test/CXX/over/over.over/p1.cpp index 10c60da013c..430d1a2481d 100644 --- a/clang/test/CXX/over/over.over/p1.cpp +++ b/clang/test/CXX/over/over.over/p1.cpp @@ -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 { diff --git a/clang/test/CXX/temp/temp.fct.spec/temp.deduct/temp.deduct.call/p4.cpp b/clang/test/CXX/temp/temp.fct.spec/temp.deduct/temp.deduct.call/p4.cpp index 132d6181422..cf9003b889d 100644 --- a/clang/test/CXX/temp/temp.fct.spec/temp.deduct/temp.deduct.call/p4.cpp +++ b/clang/test/CXX/temp/temp.fct.spec/temp.deduct/temp.deduct.call/p4.cpp @@ -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 diff --git a/clang/test/CXX/temp/temp.fct.spec/temp.deduct/temp.deduct.conv/p5.cpp b/clang/test/CXX/temp/temp.fct.spec/temp.deduct/temp.deduct.conv/p5.cpp new file mode 100644 index 00000000000..ed7b00071eb --- /dev/null +++ b/clang/test/CXX/temp/temp.fct.spec/temp.deduct/temp.deduct.conv/p5.cpp @@ -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(); diff --git a/clang/test/SemaCXX/cxx0x-defaulted-functions.cpp b/clang/test/SemaCXX/cxx0x-defaulted-functions.cpp index 7ec9726095c..c9c9fcef565 100644 --- a/clang/test/SemaCXX/cxx0x-defaulted-functions.cpp +++ b/clang/test/SemaCXX/cxx0x-defaulted-functions.cpp @@ -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}} } diff --git a/clang/test/SemaCXX/deprecated.cpp b/clang/test/SemaCXX/deprecated.cpp index 4ce05896874..7a1723734db 100644 --- a/clang/test/SemaCXX/deprecated.cpp +++ b/clang/test/SemaCXX/deprecated.cpp @@ -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}} |