diff --git a/clang/include/clang/AST/ASTContext.h b/clang/include/clang/AST/ASTContext.h --- a/clang/include/clang/AST/ASTContext.h +++ b/clang/include/clang/AST/ASTContext.h @@ -2865,10 +2865,12 @@ bool canBindObjCObjectType(QualType To, QualType From); // Functions for calculating composite types - QualType mergeTypes(QualType, QualType, bool OfBlockPointer=false, - bool Unqualified = false, bool BlockReturnType = false); - QualType mergeFunctionTypes(QualType, QualType, bool OfBlockPointer=false, - bool Unqualified = false, bool AllowCXX = false); + QualType mergeTypes(QualType, QualType, bool OfBlockPointer = false, + bool Unqualified = false, bool BlockReturnType = false, + bool IsConditionalOperator = false); + QualType mergeFunctionTypes(QualType, QualType, bool OfBlockPointer = false, + bool Unqualified = false, bool AllowCXX = false, + bool IsConditionalOperator = false); QualType mergeFunctionParameterTypes(QualType, QualType, bool OfBlockPointer = false, bool Unqualified = false); diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp --- a/clang/lib/AST/ASTContext.cpp +++ b/clang/lib/AST/ASTContext.cpp @@ -10191,7 +10191,8 @@ QualType ASTContext::mergeFunctionTypes(QualType lhs, QualType rhs, bool OfBlockPointer, bool Unqualified, - bool AllowCXX) { + bool AllowCXX, + bool IsConditionalOperator) { const auto *lbase = lhs->castAs(); const auto *rbase = rhs->castAs(); const auto *lproto = dyn_cast(lbase); @@ -10254,9 +10255,27 @@ if (lbaseInfo.getNoCfCheck() != rbaseInfo.getNoCfCheck()) return {}; - // FIXME: some uses, e.g. conditional exprs, really want this to be 'both'. - bool NoReturn = lbaseInfo.getNoReturn() || rbaseInfo.getNoReturn(); - + // When merging declarations, it's common for supplemental information like + // attributes to only be present in one of the declarations, and we generally + // want type merging to preserve the union of information. So a merged + // function type should be noreturn if it was noreturn in *either* operand + // type. + // + // But for the conditional operator, this is backwards. The result of the + // operator could be either operand, and its type should conservatively + // reflect that. So a function type in a composite type is noreturn only + // if it's noreturn in *both* operand types. + // + // Arguably, noreturn is a kind of subtype, and the conditional operator + // ought to produce the most specific common supertype of its operand types. + // That would differ from this rule in contravariant positions. However, + // neither C nor C++ generally uses this kind of subtype reasoning. Also, + // as a practical matter, it would only affect C code that does abstraction of + // higher-order functions (taking noreturn callbacks!), which is uncommon to + // say the least. So we use the simpler rule. + bool NoReturn = IsConditionalOperator + ? lbaseInfo.getNoReturn() && rbaseInfo.getNoReturn() + : lbaseInfo.getNoReturn() || rbaseInfo.getNoReturn(); if (lbaseInfo.getNoReturn() != NoReturn) allLTypes = false; if (rbaseInfo.getNoReturn() != NoReturn) @@ -10389,9 +10408,9 @@ return {}; } -QualType ASTContext::mergeTypes(QualType LHS, QualType RHS, - bool OfBlockPointer, - bool Unqualified, bool BlockReturnType) { +QualType ASTContext::mergeTypes(QualType LHS, QualType RHS, bool OfBlockPointer, + bool Unqualified, bool BlockReturnType, + bool IsConditionalOperator) { // For C++ we will not reach this code with reference types (see below), // for OpenMP variant call overloading we might. // @@ -10684,7 +10703,8 @@ ArrayType::ArraySizeModifier(), 0); } case Type::FunctionNoProto: - return mergeFunctionTypes(LHS, RHS, OfBlockPointer, Unqualified); + return mergeFunctionTypes(LHS, RHS, OfBlockPointer, Unqualified, + /*AllowCXX=*/false, IsConditionalOperator); case Type::Record: case Type::Enum: return {}; diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp --- a/clang/lib/Sema/SemaExpr.cpp +++ b/clang/lib/Sema/SemaExpr.cpp @@ -8262,7 +8262,9 @@ lhptee = S.Context.getQualifiedType(lhptee.getUnqualifiedType(), lhQual); rhptee = S.Context.getQualifiedType(rhptee.getUnqualifiedType(), rhQual); - QualType CompositeTy = S.Context.mergeTypes(lhptee, rhptee); + QualType CompositeTy = S.Context.mergeTypes( + lhptee, rhptee, /*OfBlockPointer=*/false, /*Unqualified=*/false, + /*BlockReturnType=*/false, /*IsConditionalOperator=*/true); if (CompositeTy.isNull()) { // In this situation, we assume void* type. No especially good diff --git a/clang/test/CodeGen/attr-noreturn.c b/clang/test/CodeGen/attr-noreturn.c --- a/clang/test/CodeGen/attr-noreturn.c +++ b/clang/test/CodeGen/attr-noreturn.c @@ -1,4 +1,5 @@ -// RUN: %clang_cc1 -S -emit-llvm %s -o - | FileCheck %s +// RUN: %clang_cc1 -emit-llvm %s -o - | FileCheck %s +// RUN: %clang_cc1 -triple %itanium_abi_triple -x c++ -emit-llvm %s -o - | FileCheck %s --check-prefix=CHECK-CXX typedef void (*fptrs_t[4])(void); fptrs_t p __attribute__((noreturn)); @@ -8,3 +9,24 @@ } // CHECK: call void // CHECK-NEXT: unreachable + +// CHECK-LABEL: @test_conditional( +// CHECK: %cond = select i1 %tobool, ptr @t1, ptr @t2 +// CHECK: call void %cond( +// CHECK: call void %cond2( +// CHECK-NEXT: unreachable + +// CHECK-CXX-LABEL: @_Z16test_conditionali( +// CHECK-CXX: %cond{{.*}} = phi ptr [ @_Z2t1i, %{{.*}} ], [ @_Z2t2i, %{{.*}} ] +// CHECK-CXX: call void %cond{{.*}}( +// CHECK-CXX: %cond{{.*}} = phi ptr [ @_Z2t1i, %{{.*}} ], [ @_Z2t1i, %{{.*}} ] +// CHECK-CXX: call void %cond{{.*}}( +// CHECK-CXX-NEXT: unreachable +void t1(int) __attribute__((noreturn)); +void t2(int); +__attribute__((noreturn)) void test_conditional(int a) { + // The conditional operator isn't noreturn because t2 isn't. + (a ? t1 : t2)(a); + // The conditional operator is noreturn. + (a ? t1 : t1)(a); +}