]> granicus.if.org Git - clang/commitdiff
P0012R1: Make exception specifications be part of the type system. This
authorRichard Smith <richard-llvm@metafoo.co.uk>
Sun, 16 Oct 2016 17:54:23 +0000 (17:54 +0000)
committerRichard Smith <richard-llvm@metafoo.co.uk>
Sun, 16 Oct 2016 17:54:23 +0000 (17:54 +0000)
implements the bulk of the change (modifying the type system to include
exception specifications), but not all the details just yet.

git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@284337 91177308-0d34-0410-b5e6-96231b3b80d8

29 files changed:
include/clang/AST/Type.h
include/clang/Basic/DiagnosticSemaKinds.td
include/clang/Sema/Overload.h
include/clang/Sema/Sema.h
lib/AST/ASTContext.cpp
lib/AST/Type.cpp
lib/Sema/SemaDecl.cpp
lib/Sema/SemaDeclCXX.cpp
lib/Sema/SemaExceptionSpec.cpp
lib/Sema/SemaExpr.cpp
lib/Sema/SemaExprCXX.cpp
lib/Sema/SemaOverload.cpp
lib/Sema/SemaTemplateDeduction.cpp
lib/Sema/SemaType.cpp
test/CXX/conv/conv.fctptr/p1.cpp [new file with mode: 0644]
test/CXX/dcl.decl/dcl.meaning/dcl.fct/p6-0x.cpp
test/CXX/drs/dr0xx.cpp
test/CXX/drs/dr1xx.cpp
test/CXX/drs/dr2xx.cpp
test/CXX/except/except.spec/p2-places-1z.cpp [new file with mode: 0644]
test/CXX/except/except.spec/p2-places.cpp
test/CXX/expr/expr.const/p3-0x.cpp
test/CXX/expr/expr.prim/expr.prim.lambda/p6.cpp
test/CXX/over/over.match/over.match.best/over.best.ics/over.ics.scs/p3.cpp [new file with mode: 0644]
test/CXX/over/over.over/p1.cpp
test/CXX/temp/temp.fct.spec/temp.deduct/temp.deduct.call/p4.cpp
test/CXX/temp/temp.fct.spec/temp.deduct/temp.deduct.conv/p5.cpp [new file with mode: 0644]
test/SemaCXX/cxx0x-defaulted-functions.cpp
test/SemaCXX/deprecated.cpp

index fb1b50f7aac714d28838bc9b3f359b4532776f43..e9351a9e514b1907d3c9c543db2ea622df0c63f8 100644 (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; }
 
index b92ca3d34b9c1556eef26e58657cf2cb1b4ba72c..e274b464652c0c45137bd2de94c4486798ae9cf8 100644 (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 "
index 1c2eb927895c5e90eee13d3aad4a6737727bf7e4..b3447c58abb2e035ce040128f1e1d1b5c3a6d94a 100644 (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)
index a50dd66791e9f6967275c82dfb8c1103fb5e7cdb..6e0f379764308f14c8c841a90d02bb0acbdfa783 100644 (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);
index 709de4368d8b241f4574da7b0b1a4345a2e70972..34eced3f4b1c9c681a67aff52997994c7174e15e 100644 (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);
index 113974c4b60f87a64b6bf94eb0f7d1c42123c3ac..2601fa44ccc268d448e91e2eb0396a0822232f15 100644 (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 {
index efdf8a3226f221bacd6a8eab5cdc0fbfafec36a6..54b56b3b594ae98dfff85bfd99831f464d67e8ed 100644 (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);
index 93e626f7d3251ae8d7fe25740e55ba9afbe113f6..f86b793df570070898ef17502d9265befa5fd63a 100644 (file)
@@ -659,9 +659,6 @@ bool Sema::MergeCXXFunctionDecl(FunctionDecl *New, FunctionDecl *Old,
     Invalid = true;
   }
 
-  if (CheckEquivalentExceptionSpec(Old, New))
-    Invalid = true;
-
   return Invalid;
 }
 
index f8e75b2fe7926af4a319fa91c8a1fe116ad01600..a81ef5179549163651367e2ae01b0500abc8918c 100644 (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>())
index ec7f7bc02ce1766c3a6206728835d1e35fd0b976..206a704fdce7e46e8e598c963ce2aefee0cfd159 100644 (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;
 }
index 4de7fc310b4460e5bb01d37eb5a95dc69edddc05..56f593439d7a99410cf9e8376f0bcce9111d552d 100644 (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))
index 082a6f98ef25c4114c9fe92aae18949bdd90a7c0..460e53fc261643b5569fef0837b87f37857f6a83 100644 (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
index 4f76a029dcd6b365847a21b503e28127b249ff94..2e4b9caa4aba451a9e1212f5eb47eec1b765257d 100644 (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;
 
 
index cfbfc3b09f2e60c38f0ba89964893b2fb2dc7ab5..b1daab4409515dd7edcf4b6afaf4c959db90f998 100644 (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 ||
diff --git a/test/CXX/conv/conv.fctptr/p1.cpp b/test/CXX/conv/conv.fctptr/p1.cpp
new file mode 100644 (file)
index 0000000..810c7f5
--- /dev/null
@@ -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'}}
+}
index cec747e1d60548c5abe84c693b9683f74f03fbcd..d93cc8b90874d5c1a89166995f3b9e2fde24b1a6 100644 (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, "");
index d598cfa33bd448bdee045637f5d3aa44a3bd07ca..2e895ed3869ba2f74e4c194dccff21847720191b 100644 (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
index 60d6ccaa875644889535934b7f527110e46f5e4c..58626c9978008f9d2f710a312ca07cedf9f42e0c 100644 (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
   };
 }
 
index 7372ecb26c88d65fdac85299e93b4fa8bce2b5c7..0fa2500d260500a6abae712bac24d302f1aa5184 100644 (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;
   }
 }
diff --git a/test/CXX/except/except.spec/p2-places-1z.cpp b/test/CXX/except/except.spec/p2-places-1z.cpp
new file mode 100644 (file)
index 0000000..619ea33
--- /dev/null
@@ -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)**;
index 67647fb043a9830e1af28e304fd266544c16fc77..ea842af898459fead0d45ce1cf5c01bb4d3aa4f0 100644 (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 {
index d9d84853ebae92fff3b456ed0ac0377a27da2aa7..731e0c312fa13f1a284ca9c727f05101ad3e01a9 100644 (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 {
index 8b43cefa92c0676a103054608a7144e12bb4f174..90a3aec50cb380127d67ffd7f3e305fe72a06cc9 100644 (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() { 
diff --git a/test/CXX/over/over.match/over.match.best/over.best.ics/over.ics.scs/p3.cpp b/test/CXX/over/over.match/over.match.best/over.best.ics/over.ics.scs/p3.cpp
new file mode 100644 (file)
index 0000000..df2fc2c
--- /dev/null
@@ -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);
+}
index 10c60da013cbc5c674b53172b80fd9fc8bf14699..430d1a2481da764d476ef0a3578f51df97eb0d30 100644 (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 {
index 132d61814227aeb96375c0289e5896a9900ef7ef..cf9003b889d0e90bf430a4949068e7a362452cec 100644 (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
diff --git a/test/CXX/temp/temp.fct.spec/temp.deduct/temp.deduct.conv/p5.cpp b/test/CXX/temp/temp.fct.spec/temp.deduct/temp.deduct.conv/p5.cpp
new file mode 100644 (file)
index 0000000..ed7b000
--- /dev/null
@@ -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();
index 7ec9726095cba79d4673c292896778e1c1a6532c..c9c9fcef565bf2f2e666428e6305cda9ec1e77c9 100644 (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}}
 }
 
index 4ce0589687421add3af88f30e4e0851e95fc1de3..7a1723734db7ddf3cd1ce62ee510b45d276d108a 100644 (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}}