]> granicus.if.org Git - clang/commitdiff
[c++1z] Teach composite pointer type computation how to compute the composite
authorRichard Smith <richard-llvm@metafoo.co.uk>
Thu, 20 Oct 2016 17:57:33 +0000 (17:57 +0000)
committerRichard Smith <richard-llvm@metafoo.co.uk>
Thu, 20 Oct 2016 17:57:33 +0000 (17:57 +0000)
pointer type of two function pointers with different noexcept specifications.
While I'm here, also teach it how to merge dynamic exception specifications.

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

include/clang/Sema/Overload.h
include/clang/Sema/Sema.h
lib/Sema/SemaExprCXX.cpp
lib/Sema/SemaOverload.cpp
test/CXX/expr/p13.cpp [new file with mode: 0644]

index 83c6554c64d7d8f8aa353207f9de7dae2a752272..f677afe8e26fb04e6b8dd0e1d23ce0b82a733502 100644 (file)
@@ -145,7 +145,8 @@ namespace clang {
     /// pointer-to-member conversion, or boolean conversion.
     ImplicitConversionKind Second : 8;
 
-    /// Third - The third conversion can be a qualification conversion.
+    /// Third - The third conversion can be a qualification conversion
+    /// or a function conversion.
     ImplicitConversionKind Third : 8;
 
     /// \brief Whether this is the deprecated conversion of a
index 1dd415d3497e9eede0eccdd8470aabe272e08370..08e6a29ca11e31124a06ec168f3e1d42a9e73171 100644 (file)
@@ -8954,13 +8954,15 @@ public:
     ExprResult &cond, ExprResult &lhs, ExprResult &rhs,
     ExprValueKind &VK, ExprObjectKind &OK, SourceLocation questionLoc);
   QualType FindCompositePointerType(SourceLocation Loc, Expr *&E1, Expr *&E2,
-                                    bool *NonStandardCompositeType = nullptr);
+                                    bool *NonStandardCompositeType = nullptr,
+                                    bool ConvertArgs = true);
   QualType FindCompositePointerType(SourceLocation Loc,
                                     ExprResult &E1, ExprResult &E2,
-                                    bool *NonStandardCompositeType = nullptr) {
+                                    bool *NonStandardCompositeType = nullptr,
+                                    bool ConvertArgs = true) {
     Expr *E1Tmp = E1.get(), *E2Tmp = E2.get();
-    QualType Composite = FindCompositePointerType(Loc, E1Tmp, E2Tmp,
-                                                  NonStandardCompositeType);
+    QualType Composite = FindCompositePointerType(
+        Loc, E1Tmp, E2Tmp, NonStandardCompositeType, ConvertArgs);
     E1 = E1Tmp;
     E2 = E2Tmp;
     return Composite;
index b660bd348652707cf963e85aeecb7227457158c5..ba38049bcab9154513257c7cd23ca23c0b9e75eb 100644 (file)
@@ -3610,16 +3610,6 @@ Sema::PerformImplicitConversion(Expr *From, QualType ToType,
     // Nothing else to do.
     break;
 
-  case ICK_Function_Conversion:
-    // If both sides are functions (or pointers/references to them), there could
-    // be incompatible exception declarations.
-    if (CheckExceptionSpecCompatibility(From, ToType))
-      return ExprError();
-
-    From = ImpCastExprToType(From, ToType, CK_NoOp,
-                             VK_RValue, /*BasePath=*/nullptr, CCK).get();
-    break;
-
   case ICK_Integral_Promotion:
   case ICK_Integral_Conversion:
     if (ToType->isBooleanType()) {
@@ -3866,6 +3856,7 @@ Sema::PerformImplicitConversion(Expr *From, QualType ToType,
   case ICK_Lvalue_To_Rvalue:
   case ICK_Array_To_Pointer:
   case ICK_Function_To_Pointer:
+  case ICK_Function_Conversion:
   case ICK_Qualification:
   case ICK_Num_Conversion_Kinds:
   case ICK_C_Only_Conversion:
@@ -3878,6 +3869,16 @@ Sema::PerformImplicitConversion(Expr *From, QualType ToType,
     // Nothing to do.
     break;
 
+  case ICK_Function_Conversion:
+    // If both sides are functions (or pointers/references to them), there could
+    // be incompatible exception declarations.
+    if (CheckExceptionSpecCompatibility(From, ToType))
+      return ExprError();
+
+    From = ImpCastExprToType(From, ToType, CK_NoOp,
+                             VK_RValue, /*BasePath=*/nullptr, CCK).get();
+    break;
+
   case ICK_Qualification: {
     // The qualification keeps the category of the inner expression, unless the
     // target type isn't a reference.
@@ -5393,6 +5394,17 @@ QualType Sema::CXXCheckConditionalOperands(ExprResult &Cond, ExprResult &LHS,
     if (LHS.get()->getObjectKind() == OK_BitField ||
         RHS.get()->getObjectKind() == OK_BitField)
       OK = OK_BitField;
+
+    // If we have function pointer types, unify them anyway to unify their
+    // exception specifications, if any.
+    if (LTy->isFunctionPointerType() || LTy->isMemberFunctionPointerType()) {
+      LTy = FindCompositePointerType(QuestionLoc, LHS, RHS, nullptr,
+                                     /*ConvertArgs*/false);
+      assert(!LTy.isNull() && "failed to find composite pointer type for "
+                              "canonically equivalent function ptr types");
+      assert(Context.hasSameType(LTy, RTy) && "bad composite pointer type");
+    }
+
     return LTy;
   }
 
@@ -5447,6 +5459,14 @@ QualType Sema::CXXCheckConditionalOperands(ExprResult &Cond, ExprResult &LHS,
       RHS = RHSCopy;
     }
 
+    // If we have function pointer types, unify them anyway to unify their
+    // exception specifications, if any.
+    if (LTy->isFunctionPointerType() || LTy->isMemberFunctionPointerType()) {
+      LTy = FindCompositePointerType(QuestionLoc, LHS, RHS);
+      assert(!LTy.isNull() && "failed to find composite pointer type for "
+                              "canonically equivalent function ptr types");
+    }
+
     return LTy;
   }
 
@@ -5517,6 +5537,78 @@ QualType Sema::CXXCheckConditionalOperands(ExprResult &Cond, ExprResult &LHS,
   return QualType();
 }
 
+static FunctionProtoType::ExceptionSpecInfo
+mergeExceptionSpecs(Sema &S, FunctionProtoType::ExceptionSpecInfo ESI1,
+                    FunctionProtoType::ExceptionSpecInfo ESI2,
+                    SmallVectorImpl<QualType> &ExceptionTypeStorage) {
+  ExceptionSpecificationType EST1 = ESI1.Type;
+  ExceptionSpecificationType EST2 = ESI2.Type;
+
+  // If either of them can throw anything, that is the result.
+  if (EST1 == EST_None) return ESI1;
+  if (EST2 == EST_None) return ESI2;
+  if (EST1 == EST_MSAny) return ESI1;
+  if (EST2 == EST_MSAny) return ESI2;
+
+  // If either of them is non-throwing, the result is the other.
+  if (EST1 == EST_DynamicNone) return ESI2;
+  if (EST2 == EST_DynamicNone) return ESI1;
+  if (EST1 == EST_BasicNoexcept) return ESI2;
+  if (EST2 == EST_BasicNoexcept) return ESI1;
+
+  // If either of them is a non-value-dependent computed noexcept, that
+  // determines the result.
+  if (EST2 == EST_ComputedNoexcept && ESI2.NoexceptExpr &&
+      !ESI2.NoexceptExpr->isValueDependent())
+    return !ESI2.NoexceptExpr->EvaluateKnownConstInt(S.Context) ? ESI2 : ESI1;
+  if (EST1 == EST_ComputedNoexcept && ESI1.NoexceptExpr &&
+      !ESI1.NoexceptExpr->isValueDependent())
+    return !ESI1.NoexceptExpr->EvaluateKnownConstInt(S.Context) ? ESI1 : ESI2;
+  // If we're left with value-dependent computed noexcept expressions, we're
+  // stuck. Before C++17, we can just drop the exception specification entirely,
+  // since it's not actually part of the canonical type. And this should never
+  // happen in C++17, because it would mean we were computing the composite
+  // pointer type of dependent types, which should never happen.
+  if (EST1 == EST_ComputedNoexcept || EST2 == EST_ComputedNoexcept) {
+    assert(!S.getLangOpts().CPlusPlus1z &&
+           "computing composite pointer type of dependent types");
+    return FunctionProtoType::ExceptionSpecInfo();
+  }
+
+  // Switch over the possibilities so that people adding new values know to
+  // update this function.
+  switch (EST1) {
+  case EST_None:
+  case EST_DynamicNone:
+  case EST_MSAny:
+  case EST_BasicNoexcept:
+  case EST_ComputedNoexcept:
+    llvm_unreachable("handled above");
+
+  case EST_Dynamic: {
+    // This is the fun case: both exception specifications are dynamic. Form
+    // the union of the two lists.
+    assert(EST2 == EST_Dynamic && "other cases should already be handled");
+    llvm::SmallPtrSet<QualType, 8> Found;
+    for (auto &Exceptions : {ESI1.Exceptions, ESI2.Exceptions})
+      for (QualType E : Exceptions)
+        if (Found.insert(S.Context.getCanonicalType(E)).second)
+          ExceptionTypeStorage.push_back(E);
+
+    FunctionProtoType::ExceptionSpecInfo Result(EST_Dynamic);
+    Result.Exceptions = ExceptionTypeStorage;
+    return Result;
+  }
+
+  case EST_Unevaluated:
+  case EST_Uninstantiated:
+  case EST_Unparsed:
+    llvm_unreachable("shouldn't see unresolved exception specifications here");
+  }
+
+  llvm_unreachable("invalid ExceptionSpecificationType");
+}
+
 /// \brief Find a merged pointer type and convert the two expressions to it.
 ///
 /// This finds the composite pointer type (or member pointer type) for @p E1
@@ -5531,9 +5623,12 @@ QualType Sema::CXXCheckConditionalOperands(ExprResult &Cond, ExprResult &LHS,
 /// a non-standard (but still sane) composite type to which both expressions
 /// can be converted. When such a type is chosen, \c *NonStandardCompositeType
 /// will be set true.
+///
+/// \param ConvertArgs If \c false, do not convert E1 and E2 to the target type.
 QualType Sema::FindCompositePointerType(SourceLocation Loc,
                                         Expr *&E1, Expr *&E2,
-                                        bool *NonStandardCompositeType) {
+                                        bool *NonStandardCompositeType,
+                                        bool ConvertArgs) {
   if (NonStandardCompositeType)
     *NonStandardCompositeType = false;
 
@@ -5560,16 +5655,18 @@ QualType Sema::FindCompositePointerType(SourceLocation Loc,
   //   - if either p1 or p2 is a null pointer constant, T2 or T1, respectively;
   if (T1IsPointerLike &&
       E2->isNullPointerConstant(Context, Expr::NPC_ValueDependentIsNull)) {
-    E2 = ImpCastExprToType(E2, T1, T1->isMemberPointerType()
-                                       ? CK_NullToMemberPointer
-                                       : CK_NullToPointer).get();
+    if (ConvertArgs)
+      E2 = ImpCastExprToType(E2, T1, T1->isMemberPointerType()
+                                         ? CK_NullToMemberPointer
+                                         : CK_NullToPointer).get();
     return T1;
   }
   if (T2IsPointerLike &&
       E1->isNullPointerConstant(Context, Expr::NPC_ValueDependentIsNull)) {
-    E1 = ImpCastExprToType(E1, T2, T2->isMemberPointerType()
-                                       ? CK_NullToMemberPointer
-                                       : CK_NullToPointer).get();
+    if (ConvertArgs)
+      E1 = ImpCastExprToType(E1, T2, T2->isMemberPointerType()
+                                         ? CK_NullToMemberPointer
+                                         : CK_NullToPointer).get();
     return T2;
   }
 
@@ -5615,8 +5712,8 @@ QualType Sema::FindCompositePointerType(SourceLocation Loc,
   // exists.
   SmallVector<unsigned, 4> QualifierUnion;
   SmallVector<std::pair<const Type *, const Type *>, 4> MemberOfClass;
-  QualType Composite1 = Context.getCanonicalType(T1);
-  QualType Composite2 = Context.getCanonicalType(T2);
+  QualType Composite1 = T1;
+  QualType Composite2 = T2;
   unsigned NeedConstBefore = 0;
   while (true) {
     const PointerType *Ptr1, *Ptr2;
@@ -5662,6 +5759,41 @@ QualType Sema::FindCompositePointerType(SourceLocation Loc,
     break;
   }
 
+  // Apply the function pointer conversion to unify the types. We've already
+  // unwrapped down to the function types, and we want to merge rather than
+  // just convert, so do this ourselves rather than calling
+  // IsFunctionConversion.
+  //
+  // FIXME: In order to match the standard wording as closely as possible, we
+  // currently only do this under a single level of pointers. Ideally, we would
+  // allow this in general, and set NeedConstBefore to the relevant depth on
+  // the side(s) where we changed anything.
+  if (QualifierUnion.size() == 1) {
+    if (auto *FPT1 = Composite1->getAs<FunctionProtoType>()) {
+      if (auto *FPT2 = Composite2->getAs<FunctionProtoType>()) {
+        FunctionProtoType::ExtProtoInfo EPI1 = FPT1->getExtProtoInfo();
+        FunctionProtoType::ExtProtoInfo EPI2 = FPT2->getExtProtoInfo();
+
+        // The result is noreturn if both operands are.
+        bool Noreturn =
+            EPI1.ExtInfo.getNoReturn() && EPI2.ExtInfo.getNoReturn();
+        EPI1.ExtInfo = EPI1.ExtInfo.withNoReturn(Noreturn);
+        EPI2.ExtInfo = EPI2.ExtInfo.withNoReturn(Noreturn);
+
+        // The result is nothrow if both operands are.
+        SmallVector<QualType, 8> ExceptionTypeStorage;
+        EPI1.ExceptionSpec = EPI2.ExceptionSpec =
+            mergeExceptionSpecs(*this, EPI1.ExceptionSpec, EPI2.ExceptionSpec,
+                                ExceptionTypeStorage);
+
+        Composite1 = Context.getFunctionType(FPT1->getReturnType(),
+                                             FPT1->getParamTypes(), EPI1);
+        Composite2 = Context.getFunctionType(FPT2->getReturnType(),
+                                             FPT2->getParamTypes(), EPI2);
+      }
+    }
+  }
+
   if (NeedConstBefore && NonStandardCompositeType) {
     // Extension: Add 'const' to qualifiers that come before the first qualifier
     // mismatch, so that our (non-standard!) composite type meets the
@@ -5711,25 +5843,28 @@ QualType Sema::FindCompositePointerType(SourceLocation Loc,
           E1ToC(S, Entity, Kind, E1), E2ToC(S, Entity, Kind, E2),
           Viable(E1ToC && E2ToC) {}
 
-    QualType perform() {
+    bool perform() {
       ExprResult E1Result = E1ToC.Perform(S, Entity, Kind, E1);
       if (E1Result.isInvalid())
-        return QualType();
+        return true;
       E1 = E1Result.getAs<Expr>();
 
       ExprResult E2Result = E2ToC.Perform(S, Entity, Kind, E2);
       if (E2Result.isInvalid())
-        return QualType();
+        return true;
       E2 = E2Result.getAs<Expr>();
 
-      return Composite;
+      return false;
     }
   };
 
   // Try to convert to each composite pointer type.
   Conversion C1(*this, Loc, E1, E2, Composite1);
-  if (C1.Viable && Context.hasSameType(Composite1, Composite2))
-    return C1.perform();
+  if (C1.Viable && Context.hasSameType(Composite1, Composite2)) {
+    if (ConvertArgs && C1.perform())
+      return QualType();
+    return C1.Composite;
+  }
   Conversion C2(*this, Loc, E1, E2, Composite2);
 
   if (C1.Viable == C2.Viable) {
@@ -5740,7 +5875,10 @@ QualType Sema::FindCompositePointerType(SourceLocation Loc,
   }
 
   // Convert to the chosen type.
-  return (C1.Viable ? C1 : C2).perform();
+  if (ConvertArgs && (C1.Viable ? C1 : C2).perform())
+    return QualType();
+
+  return C1.Viable ? C1.Composite : C2.Composite;
 }
 
 ExprResult Sema::MaybeBindToTemporary(Expr *E) {
index db97f3d57f55117d2541a3402669d8b9e0a5101f..8cc592e21e531b83a40b583c13d368ddc37e8db2 100644 (file)
@@ -1405,6 +1405,7 @@ bool Sema::IsFunctionConversion(QualType FromType, QualType ToType,
   //   - a pointer
   //   - a member pointer
   //   - a block pointer
+  // Changes here need matching changes in FindCompositePointerType.
   CanQualType CanTo = Context.getCanonicalType(ToType);
   CanQualType CanFrom = Context.getCanonicalType(FromType);
   Type::TypeClass TyClass = CanTo->getTypeClass();
@@ -1417,8 +1418,13 @@ bool Sema::IsFunctionConversion(QualType FromType, QualType ToType,
       CanTo = CanTo.getAs<BlockPointerType>()->getPointeeType();
       CanFrom = CanFrom.getAs<BlockPointerType>()->getPointeeType();
     } else if (TyClass == Type::MemberPointer) {
-      CanTo = CanTo.getAs<MemberPointerType>()->getPointeeType();
-      CanFrom = CanFrom.getAs<MemberPointerType>()->getPointeeType();
+      auto ToMPT = CanTo.getAs<MemberPointerType>();
+      auto FromMPT = CanFrom.getAs<MemberPointerType>();
+      // A function pointer conversion cannot change the class of the function.
+      if (ToMPT->getClass() != FromMPT->getClass())
+        return false;
+      CanTo = ToMPT->getPointeeType();
+      CanFrom = FromMPT->getPointeeType();
     } else {
       return false;
     }
@@ -1757,10 +1763,6 @@ 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.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)) {
@@ -1782,34 +1784,36 @@ static bool IsStandardConversion(Sema &S, Expr* From, QualType ToType,
   }
   SCS.setToType(1, FromType);
 
-  QualType CanonFrom;
-  QualType CanonTo;
-  // The third conversion can be a qualification conversion (C++ 4p1).
+  // The third conversion can be a function pointer conversion or a
+  // qualification conversion (C++ [conv.fctptr], [conv.qual]).
   bool ObjCLifetimeConversion;
-  if (S.IsQualificationConversion(FromType, ToType, CStyle, 
-                                  ObjCLifetimeConversion)) {
+  if (S.IsFunctionConversion(FromType, ToType, FromType)) {
+    // Function pointer conversions (removing 'noexcept') including removal of
+    // 'noreturn' (Clang extension).
+    SCS.Third = ICK_Function_Conversion;
+  } else if (S.IsQualificationConversion(FromType, ToType, CStyle,
+                                         ObjCLifetimeConversion)) {
     SCS.Third = ICK_Qualification;
     SCS.QualificationIncludesObjCLifetime = ObjCLifetimeConversion;
     FromType = ToType;
-    CanonFrom = S.Context.getCanonicalType(FromType);
-    CanonTo = S.Context.getCanonicalType(ToType);
   } else {
     // No conversion required
     SCS.Third = ICK_Identity;
+  }
 
-    // C++ [over.best.ics]p6:
-    //   [...] Any difference in top-level cv-qualification is
-    //   subsumed by the initialization itself and does not constitute
-    //   a conversion. [...]
-    CanonFrom = S.Context.getCanonicalType(FromType);
-    CanonTo = S.Context.getCanonicalType(ToType);
-    if (CanonFrom.getLocalUnqualifiedType()
-                                       == CanonTo.getLocalUnqualifiedType() &&
-        CanonFrom.getLocalQualifiers() != CanonTo.getLocalQualifiers()) {
-      FromType = ToType;
-      CanonFrom = CanonTo;
-    }
+  // C++ [over.best.ics]p6:
+  //   [...] Any difference in top-level cv-qualification is
+  //   subsumed by the initialization itself and does not constitute
+  //   a conversion. [...]
+  QualType CanonFrom = S.Context.getCanonicalType(FromType);
+  QualType CanonTo = S.Context.getCanonicalType(ToType);
+  if (CanonFrom.getLocalUnqualifiedType()
+                                     == CanonTo.getLocalUnqualifiedType() &&
+      CanonFrom.getLocalQualifiers() != CanonTo.getLocalQualifiers()) {
+    FromType = ToType;
+    CanonFrom = CanonTo;
   }
+
   SCS.setToType(2, FromType);
 
   if (CanonFrom == CanonTo)
diff --git a/test/CXX/expr/p13.cpp b/test/CXX/expr/p13.cpp
new file mode 100644 (file)
index 0000000..b3489fa
--- /dev/null
@@ -0,0 +1,45 @@
+// RUN: %clang_cc1 -std=c++1z -verify %s -fexceptions -fcxx-exceptions
+
+struct X {};
+struct Y : X {};
+
+using A = void (*)() noexcept;
+using B = void (*)();
+using C = void (X::*)() noexcept;
+using D = void (X::*)();
+using E = void (Y::*)() noexcept;
+using F = void (Y::*)();
+
+void f(A a, B b, C c, D d, E e, F f, bool k) {
+  a = k ? a : b; // expected-error {{different exception specifications}}
+  b = k ? a : b;
+
+  c = k ? c : d; // expected-error {{different exception specifications}}
+  d = k ? c : d;
+
+  e = k ? c : f; // expected-error {{different exception specifications}}
+  e = k ? d : e; // expected-error {{different exception specifications}}
+  f = k ? c : f;
+  f = k ? d : e;
+}
+
+namespace dynamic_exception_spec {
+  // Prior to P0012, we had:
+  //   "[...] the target entity shall allow at least the exceptions allowed
+  //   by the source value in the assignment or initialization"
+  //
+  // There's really only one way we can coherently apply this to conditional
+  // expressions: this must hold no matter which branch was taken.
+  using X = void (*)() throw(int);
+  using Y = void (*)() throw(float);
+  using Z = void (*)() throw(int, float);
+  void g(X x, Y y, Z z, bool k) {
+    x = k ? X() : Y(); // expected-error {{not superset}}
+    y = k ? X() : Y(); // expected-error {{not superset}}
+    z = k ? X() : Y();
+
+    x = k ? x : y; // expected-error {{not superset}}
+    y = k ? x : y; // expected-error {{not superset}}
+    z = k ? x : y;
+  }
+}