[clang] [Clang][Sema] Suppress -Wconstant-conversion in unselected conditional operands (PR #226775)
via cfe-commits
cfe-commits at lists.llvm.org
Sun Sep 27 02:44:04 PDT 2026
https://github.com/filaka771 created https://github.com/llvm/llvm-project/pull/226775
```c
signed char c = 0 ? 128 : 1;
short s = 0 ? 32768 : 1;
int i = 0 ? 2147483648LL : 1;
```
Before the fix, Clang emits these warnings with `-Wconstant-conversion`:
```text
<stdin>:1:21: warning: implicit conversion from 'int' to 'signed char' changes value from 128 to -128 [-Wconstant-conversion]
1 | signed char c = 0 ? 128 : 1;
| ~ ^~~
<stdin>:2:15: warning: implicit conversion from 'int' to 'short' changes value from 32768 to -32768 [-Wconstant-conversion]
2 | short s = 0 ? 32768 : 1;
| ~ ^~~~~
<stdin>:3:13: warning: implicit conversion from 'long long' to 'int' changes value from 2147483648 to -2147483648 [-Wconstant-conversion]
3 | int i = 0 ? 2147483648LL : 1;
| ~ ^~~~~~~~~~~~
3 warnings generated.
```
After the fix, the same code compiles without diagnostics.
As explained in the [issue discussion](https://github.com/llvm/llvm-project/issues/223923#issuecomment-5731266470), exposed an existing problem when it enabled warnings for signed-char arrays. The underlying bug also affects scalar signed integer conversions: Clang diagnoses a value from an operand that is never selected ([Compiler Explorer](https://godbolt.org/z/bn1TTshxh)).
This patch tracks unselected conditional operands during implicit-conversion analysis and suppresses their integer `-Wconstant-conversion` diagnostics. It preserves warnings for selected or potentially selected operands.
When runtime and constant evaluation could select different operands, the warning checks remain enabled for both.
Regression tests cover the original reproducer, multiple integer widths, nested expressions, `constexpr` initializers, and GNU `?:`, alongside cases that must continue to warn.
Fixes #223923.
>From 15c92c19426f06b3b1a7a85015637380a0ca0ed5 Mon Sep 17 00:00:00 2001
From: Alex Filak <filaka771 at gmail.com>
Date: Fri, 18 Sep 2026 22:56:35 +0300
Subject: [PATCH 1/3] [clang][Sema] Avoid -Wconstant-conversion in unselected
conditional operands
---
clang/include/clang/Sema/Sema.h | 5 +-
clang/lib/Sema/SemaChecking.cpp | 186 +++++++++++++++++++++-----------
2 files changed, 128 insertions(+), 63 deletions(-)
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index eb3b908351448..7444661182266 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -2923,9 +2923,12 @@ class Sema final : public SemaBase {
/// since all 1s are not contiguous.
bool ValueIsRunOfOnes(CallExpr *TheCall, unsigned ArgNum);
+ /// Check the implicit conversion of E to T. IsKnownUnreachable indicates that
+ /// E is within an unselected operand of a constant conditional expression.
void CheckImplicitConversion(Expr *E, QualType T, SourceLocation CC,
bool *ICContext = nullptr,
- bool IsListInit = false);
+ bool IsListInit = false,
+ bool IsKnownUnreachable = false);
/// Check for overflow behavior type related implicit conversion diagnostics.
/// Returns true if OBT-related diagnostic was issued, false otherwise.
diff --git a/clang/lib/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp
index 687af75c1496b..140b0be3b4448 100644
--- a/clang/lib/Sema/SemaChecking.cpp
+++ b/clang/lib/Sema/SemaChecking.cpp
@@ -12365,7 +12365,8 @@ static bool IsSameFloatAfterCast(const APValue &value,
}
static void AnalyzeImplicitConversions(Sema &S, Expr *E, SourceLocation CC,
- bool IsListInit = false);
+ bool IsListInit = false,
+ bool IsKnownUnreachable = false);
static bool IsEnumConstOrFromMacro(Sema &S, const Expr *E) {
// Suppress cases where we are comparing against an enum constant.
@@ -12700,26 +12701,30 @@ static bool CheckTautologicalComparison(Sema &S, BinaryOperator *E,
/// Analyze the operands of the given comparison. Implements the
/// fallback case from AnalyzeComparison.
-static void AnalyzeImpConvsInComparison(Sema &S, BinaryOperator *E) {
- AnalyzeImplicitConversions(S, E->getLHS(), E->getOperatorLoc());
- AnalyzeImplicitConversions(S, E->getRHS(), E->getOperatorLoc());
+static void AnalyzeImpConvsInComparison(Sema &S, BinaryOperator *E,
+ bool IsKnownUnreachable) {
+ AnalyzeImplicitConversions(S, E->getLHS(), E->getOperatorLoc(),
+ /*IsListInit=*/false, IsKnownUnreachable);
+ AnalyzeImplicitConversions(S, E->getRHS(), E->getOperatorLoc(),
+ /*IsListInit=*/false, IsKnownUnreachable);
}
/// Implements -Wsign-compare.
///
/// \param E the binary operator to check for warnings
-static void AnalyzeComparison(Sema &S, BinaryOperator *E) {
+static void AnalyzeComparison(Sema &S, BinaryOperator *E,
+ bool IsKnownUnreachable) {
// The type the comparison is being performed in.
QualType T = E->getLHS()->getType();
// Only analyze comparison operators where both sides have been converted to
// the same type.
if (!S.Context.hasSameUnqualifiedType(T, E->getRHS()->getType()))
- return AnalyzeImpConvsInComparison(S, E);
+ return AnalyzeImpConvsInComparison(S, E, IsKnownUnreachable);
// Don't analyze value-dependent comparisons directly.
if (E->isValueDependent())
- return AnalyzeImpConvsInComparison(S, E);
+ return AnalyzeImpConvsInComparison(S, E, IsKnownUnreachable);
Expr *LHS = E->getLHS();
Expr *RHS = E->getRHS();
@@ -12732,7 +12737,7 @@ static void AnalyzeComparison(Sema &S, BinaryOperator *E) {
// We don't care about expressions whose result is a constant.
if (RHSValue && LHSValue)
- return AnalyzeImpConvsInComparison(S, E);
+ return AnalyzeImpConvsInComparison(S, E, IsKnownUnreachable);
// We only care about expressions where just one side is literal
if ((bool)RHSValue ^ (bool)LHSValue) {
@@ -12745,7 +12750,7 @@ static void AnalyzeComparison(Sema &S, BinaryOperator *E) {
// Check whether an integer constant comparison results in a value
// of 'true' or 'false'.
if (CheckTautologicalComparison(S, E, Const, Other, Value, RhsConstant))
- return AnalyzeImpConvsInComparison(S, E);
+ return AnalyzeImpConvsInComparison(S, E, IsKnownUnreachable);
}
}
@@ -12753,7 +12758,7 @@ static void AnalyzeComparison(Sema &S, BinaryOperator *E) {
// We don't do anything special if this isn't an unsigned integral
// comparison: we're only interested in integral comparisons, and
// signed comparisons only happen in cases we don't care to warn about.
- return AnalyzeImpConvsInComparison(S, E);
+ return AnalyzeImpConvsInComparison(S, E, IsKnownUnreachable);
}
LHS = LHS->IgnoreParenImpCasts();
@@ -12781,7 +12786,7 @@ static void AnalyzeComparison(Sema &S, BinaryOperator *E) {
signedOperand = RHS;
unsignedOperand = LHS;
} else {
- return AnalyzeImpConvsInComparison(S, E);
+ return AnalyzeImpConvsInComparison(S, E, IsKnownUnreachable);
}
// Otherwise, calculate the effective range of the signed operand.
@@ -12793,8 +12798,10 @@ static void AnalyzeComparison(Sema &S, BinaryOperator *E) {
// Go ahead and analyze implicit conversions in the operands. Note
// that we skip the implicit conversions on both sides.
- AnalyzeImplicitConversions(S, LHS, E->getOperatorLoc());
- AnalyzeImplicitConversions(S, RHS, E->getOperatorLoc());
+ AnalyzeImplicitConversions(S, LHS, E->getOperatorLoc(), /*IsListInit=*/false,
+ IsKnownUnreachable);
+ AnalyzeImplicitConversions(S, RHS, E->getOperatorLoc(), /*IsListInit=*/false,
+ IsKnownUnreachable);
// If the signed range is non-negative, -Wsign-compare won't fire.
if (signedRange->NonNegative)
@@ -12988,9 +12995,11 @@ static bool AnalyzeBitFieldAssignment(Sema &S, FieldDecl *Bitfield, Expr *Init,
/// Analyze the given simple or compound assignment for warning-worthy
/// operations.
-static void AnalyzeAssignment(Sema &S, BinaryOperator *E) {
+static void AnalyzeAssignment(Sema &S, BinaryOperator *E,
+ bool IsKnownUnreachable) {
// Just recurse on the LHS.
- AnalyzeImplicitConversions(S, E->getLHS(), E->getOperatorLoc());
+ AnalyzeImplicitConversions(S, E->getLHS(), E->getOperatorLoc(),
+ /*IsListInit=*/false, IsKnownUnreachable);
// We want to recurse on the RHS as normal unless we're assigning to
// a bitfield.
@@ -12998,8 +13007,9 @@ static void AnalyzeAssignment(Sema &S, BinaryOperator *E) {
if (AnalyzeBitFieldAssignment(S, Bitfield, E->getRHS(),
E->getOperatorLoc())) {
// Recurse, ignoring any implicit conversions on the RHS.
- return AnalyzeImplicitConversions(S, E->getRHS()->IgnoreParenImpCasts(),
- E->getOperatorLoc());
+ return AnalyzeImplicitConversions(
+ S, E->getRHS()->IgnoreParenImpCasts(), E->getOperatorLoc(),
+ /*IsListInit=*/false, IsKnownUnreachable);
}
}
@@ -13008,7 +13018,8 @@ static void AnalyzeAssignment(Sema &S, BinaryOperator *E) {
llvm::SaveAndRestore OBTAssignmentContext(
S.InOverflowBehaviorAssignmentContext, true);
- AnalyzeImplicitConversions(S, E->getRHS(), E->getOperatorLoc());
+ AnalyzeImplicitConversions(S, E->getRHS(), E->getOperatorLoc(),
+ /*IsListInit=*/false, IsKnownUnreachable);
// Diagnose implicitly sequentially-consistent atomic assignment.
if (E->getLHS()->getType()->isAtomicType())
@@ -13160,12 +13171,15 @@ static void DiagnoseFloatingImpCast(Sema &S, const Expr *E, QualType T,
/// Analyze the given compound assignment for the possible losing of
/// floating-point precision.
-static void AnalyzeCompoundAssignment(Sema &S, BinaryOperator *E) {
+static void AnalyzeCompoundAssignment(Sema &S, BinaryOperator *E,
+ bool IsKnownUnreachable) {
assert(isa<CompoundAssignOperator>(E) &&
"Must be compound assignment operation");
// Recurse on the LHS and RHS in here
- AnalyzeImplicitConversions(S, E->getLHS(), E->getOperatorLoc());
- AnalyzeImplicitConversions(S, E->getRHS(), E->getOperatorLoc());
+ AnalyzeImplicitConversions(S, E->getLHS(), E->getOperatorLoc(),
+ /*IsListInit=*/false, IsKnownUnreachable);
+ AnalyzeImplicitConversions(S, E->getRHS(), E->getOperatorLoc(),
+ /*IsListInit=*/false, IsKnownUnreachable);
if (E->getLHS()->getType()->isAtomicType())
S.Diag(E->getOperatorLoc(), diag::warn_atomic_implicit_seq_cst);
@@ -13442,7 +13456,8 @@ bool Sema::DiscardingCFIUncheckedCallee(QualType From, QualType To) const {
}
void Sema::CheckImplicitConversion(Expr *E, QualType T, SourceLocation CC,
- bool *ICContext, bool IsListInit) {
+ bool *ICContext, bool IsListInit,
+ bool IsKnownUnreachable) {
if (E->isTypeDependent() || E->isValueDependent()) return;
const Type *Source = Context.getCanonicalType(E->getType()).getTypePtr();
@@ -13879,11 +13894,12 @@ void Sema::CheckImplicitConversion(Expr *E, QualType T, SourceLocation CC,
std::string PrettySourceValue = toString(Value, 10);
std::string PrettyTargetValue = PrettyPrintInRange(Value, TargetRange);
- DiagRuntimeBehavior(E->getExprLoc(), E,
- PDiag(diag::warn_impcast_integer_precision_constant)
- << PrettySourceValue << PrettyTargetValue
- << E->getType() << T << E->getSourceRange()
- << SourceRange(CC));
+ if (!IsKnownUnreachable)
+ DiagRuntimeBehavior(E->getExprLoc(), E,
+ PDiag(diag::warn_impcast_integer_precision_constant)
+ << PrettySourceValue << PrettyTargetValue
+ << E->getType() << T << E->getSourceRange()
+ << SourceRange(CC));
return;
}
@@ -13932,10 +13948,12 @@ void Sema::CheckImplicitConversion(Expr *E, QualType T, SourceLocation CC,
std::string PrettySourceValue = toString(Value, 10);
std::string PrettyTargetValue = PrettyPrintInRange(Value, TargetRange);
- Diag(E->getExprLoc(),
- PDiag(diag::warn_impcast_integer_precision_constant)
- << PrettySourceValue << PrettyTargetValue << E->getType() << T
- << E->getSourceRange() << SourceRange(CC));
+ if (!IsKnownUnreachable && !isUnevaluatedContext() &&
+ !currentEvaluationContext().isDiscardedStatementContext())
+ Diag(E->getExprLoc(),
+ PDiag(diag::warn_impcast_integer_precision_constant)
+ << PrettySourceValue << PrettyTargetValue << E->getType()
+ << T << E->getSourceRange() << SourceRange(CC));
return;
}
}
@@ -13997,34 +14015,65 @@ void Sema::CheckImplicitConversion(Expr *E, QualType T, SourceLocation CC,
}
static void CheckConditionalOperator(Sema &S, AbstractConditionalOperator *E,
- SourceLocation CC, QualType T);
+ SourceLocation CC, QualType T,
+ bool IsKnownUnreachable);
static void CheckConditionalOperand(Sema &S, Expr *E, QualType T,
- SourceLocation CC, bool &ICContext) {
+ SourceLocation CC, bool &ICContext,
+ bool IsKnownUnreachable) {
E = E->IgnoreParenImpCasts();
// Diagnose incomplete type for second or third operand in C.
if (!S.getLangOpts().CPlusPlus && E->getType()->isRecordType())
S.RequireCompleteExprType(E, diag::err_incomplete_type);
if (auto *CO = dyn_cast<AbstractConditionalOperator>(E))
- return CheckConditionalOperator(S, CO, CC, T);
+ return CheckConditionalOperator(S, CO, CC, T, IsKnownUnreachable);
- AnalyzeImplicitConversions(S, E, CC);
+ AnalyzeImplicitConversions(S, E, CC, /*IsListInit=*/false,
+ IsKnownUnreachable);
if (E->getType() != T)
- return S.CheckImplicitConversion(E, T, CC, &ICContext);
+ return S.CheckImplicitConversion(E, T, CC, &ICContext,
+ /*IsListInit=*/false, IsKnownUnreachable);
}
static void CheckConditionalOperator(Sema &S, AbstractConditionalOperator *E,
- SourceLocation CC, QualType T) {
- AnalyzeImplicitConversions(S, E->getCond(), E->getQuestionLoc());
+ SourceLocation CC, QualType T,
+ bool IsKnownUnreachable) {
+ AnalyzeImplicitConversions(S, E->getCond(), E->getQuestionLoc(),
+ /*IsListInit=*/false, IsKnownUnreachable);
+
+ bool CondValue;
+ bool CondIsKnown = E->getCond()->EvaluateAsBooleanCondition(
+ CondValue, S.Context, S.isConstantEvaluatedContext());
+ if (CondIsKnown && !S.isConstantEvaluatedContext()) {
+ // The condition may also be evaluated at compile time, for example in a
+ // constexpr function. Only prune an operand if both evaluations agree;
+ // __builtin_is_constant_evaluated() can produce different results.
+ bool ConstantCondValue;
+ CondIsKnown =
+ E->getCond()->EvaluateAsBooleanCondition(ConstantCondValue, S.Context,
+ /*InConstantContext=*/true) &&
+ ConstantCondValue == CondValue;
+ }
Expr *TrueExpr = E->getTrueExpr();
- if (auto *BCO = dyn_cast<BinaryConditionalOperator>(E))
+ bool TrueIsKnownUnreachable =
+ IsKnownUnreachable || (CondIsKnown && !CondValue);
+ bool FalseIsKnownUnreachable =
+ IsKnownUnreachable || (CondIsKnown && CondValue);
+
+ if (auto *BCO = dyn_cast<BinaryConditionalOperator>(E)) {
+ // The common expression supplies the condition as well as the true value,
+ // so its subexpressions are evaluated even when the condition is false.
TrueExpr = BCO->getCommon();
+ TrueIsKnownUnreachable = IsKnownUnreachable;
+ }
bool Suspicious = false;
- CheckConditionalOperand(S, TrueExpr, T, CC, Suspicious);
- CheckConditionalOperand(S, E->getFalseExpr(), T, CC, Suspicious);
+ CheckConditionalOperand(S, TrueExpr, T, CC, Suspicious,
+ TrueIsKnownUnreachable);
+ CheckConditionalOperand(S, E->getFalseExpr(), T, CC, Suspicious,
+ FalseIsKnownUnreachable);
if (T->isBooleanType())
DiagnoseIntInBoolContext(S, E);
@@ -14043,10 +14092,12 @@ static void CheckConditionalOperator(Sema &S, AbstractConditionalOperator *E,
Suspicious = false;
S.CheckImplicitConversion(TrueExpr->IgnoreParenImpCasts(), E->getType(), CC,
- &Suspicious);
+ &Suspicious, /*IsListInit=*/false,
+ TrueIsKnownUnreachable);
if (!Suspicious)
S.CheckImplicitConversion(E->getFalseExpr()->IgnoreParenImpCasts(),
- E->getType(), CC, &Suspicious);
+ E->getType(), CC, &Suspicious,
+ /*IsListInit=*/false, FalseIsKnownUnreachable);
}
/// Check conversion of given expression to boolean.
@@ -14067,18 +14118,22 @@ struct AnalyzeImplicitConversionsWorkItem {
Expr *E;
SourceLocation CC;
bool IsListInit;
+ // Track unselected conditional operands without skipping other diagnostics
+ // that do not depend on whether the expression is evaluated.
+ bool IsKnownUnreachable;
};
}
static void CheckCommaOperand(
Sema &S, Expr *E, QualType T, SourceLocation CC,
- bool ExtraCheckForImplicitConversion,
+ bool ExtraCheckForImplicitConversion, bool IsKnownUnreachable,
llvm::SmallVectorImpl<AnalyzeImplicitConversionsWorkItem> &WorkList) {
E = E->IgnoreParenImpCasts();
- WorkList.push_back({E, CC, false});
+ WorkList.push_back({E, CC, false, IsKnownUnreachable});
if (ExtraCheckForImplicitConversion && E->getType() != T)
- S.CheckImplicitConversion(E, T, CC);
+ S.CheckImplicitConversion(E, T, CC, /*ICContext=*/nullptr,
+ /*IsListInit=*/false, IsKnownUnreachable);
}
/// Data recursive variant of AnalyzeImplicitConversions. Subexpressions
@@ -14156,9 +14211,11 @@ static void AnalyzeImplicitConversions(
/// how CheckConditionalOperand behaves; it's as-if the correct operand
/// were directly used for the implicit conversion check.
CheckCommaOperand(S, BO->getLHS(), T, BO->getOperatorLoc(),
- /*ExtraCheckForImplicitConversion=*/false, WorkList);
+ /*ExtraCheckForImplicitConversion=*/false,
+ Item.IsKnownUnreachable, WorkList);
CheckCommaOperand(S, BO->getRHS(), T, BO->getOperatorLoc(),
- /*ExtraCheckForImplicitConversion=*/true, WorkList);
+ /*ExtraCheckForImplicitConversion=*/true,
+ Item.IsKnownUnreachable, WorkList);
return;
}
}
@@ -14166,7 +14223,7 @@ static void AnalyzeImplicitConversions(
// For conditional operators, we analyze the arguments as if they
// were being fed directly into the output.
if (auto *CO = dyn_cast<AbstractConditionalOperator>(SourceExpr)) {
- CheckConditionalOperator(S, CO, CC, T);
+ CheckConditionalOperator(S, CO, CC, T, Item.IsKnownUnreachable);
return;
}
@@ -14178,7 +14235,8 @@ static void AnalyzeImplicitConversions(
// The non-canonical typecheck is just an optimization;
// CheckImplicitConversion will filter out dead implicit conversions.
if (SourceExpr->getType() != T)
- S.CheckImplicitConversion(SourceExpr, T, CC, nullptr, IsListInit);
+ S.CheckImplicitConversion(SourceExpr, T, CC, nullptr, IsListInit,
+ Item.IsKnownUnreachable);
// Now continue drilling into this expression.
@@ -14188,7 +14246,8 @@ static void AnalyzeImplicitConversions(
// FIXME: Use a more uniform representation for this.
for (auto *SE : POE->semantics())
if (auto *OVE = dyn_cast<OpaqueValueExpr>(SE))
- WorkList.push_back({OVE->getSourceExpr(), CC, IsListInit});
+ WorkList.push_back(
+ {OVE->getSourceExpr(), CC, IsListInit, Item.IsKnownUnreachable});
}
// Skip past explicit casts.
@@ -14208,33 +14267,35 @@ static void AnalyzeImplicitConversions(
E = E->IgnoreParenImpCasts();
if (!CE->getType()->isVoidType() && E->getType()->isAtomicType())
S.Diag(E->getBeginLoc(), diag::warn_atomic_implicit_seq_cst);
- WorkList.push_back({E, CC, IsListInit});
+ WorkList.push_back({E, CC, IsListInit, Item.IsKnownUnreachable});
return;
}
if (auto *OutArgE = dyn_cast<HLSLOutArgExpr>(E)) {
- WorkList.push_back({OutArgE->getArgLValue(), CC, IsListInit});
+ WorkList.push_back(
+ {OutArgE->getArgLValue(), CC, IsListInit, Item.IsKnownUnreachable});
// The base expression is only used to initialize the parameter for
// arguments to `inout` parameters, so we only traverse down the base
// expression for `inout` cases.
if (OutArgE->isInOut())
- WorkList.push_back(
- {OutArgE->getCastedTemporary()->getSourceExpr(), CC, IsListInit});
- WorkList.push_back({OutArgE->getWritebackCast(), CC, IsListInit});
+ WorkList.push_back({OutArgE->getCastedTemporary()->getSourceExpr(), CC,
+ IsListInit, Item.IsKnownUnreachable});
+ WorkList.push_back(
+ {OutArgE->getWritebackCast(), CC, IsListInit, Item.IsKnownUnreachable});
return;
}
if (BinaryOperator *BO = dyn_cast<BinaryOperator>(E)) {
// Do a somewhat different check with comparison operators.
if (BO->isComparisonOp())
- return AnalyzeComparison(S, BO);
+ return AnalyzeComparison(S, BO, Item.IsKnownUnreachable);
// And with simple assignments.
if (BO->getOpcode() == BO_Assign)
- return AnalyzeAssignment(S, BO);
+ return AnalyzeAssignment(S, BO, Item.IsKnownUnreachable);
// And with compound assignments.
if (BO->isAssignmentOp())
- return AnalyzeCompoundAssignment(S, BO);
+ return AnalyzeCompoundAssignment(S, BO, Item.IsKnownUnreachable);
}
// These break the otherwise-useful invariant below. Fortunately,
@@ -14267,7 +14328,7 @@ static void AnalyzeImplicitConversions(
// Ignore checking string literals that are in logical and operators.
// This is a common pattern for asserts.
continue;
- WorkList.push_back({ChildExpr, CC, IsListInit});
+ WorkList.push_back({ChildExpr, CC, IsListInit, Item.IsKnownUnreachable});
}
if (BO && BO->isLogicalOp()) {
@@ -14295,9 +14356,10 @@ static void AnalyzeImplicitConversions(
/// implicit conversions in the given expression. There are a couple
/// of competing diagnostics here, -Wconversion and -Wsign-compare.
static void AnalyzeImplicitConversions(Sema &S, Expr *OrigE, SourceLocation CC,
- bool IsListInit/*= false*/) {
+ bool IsListInit /*= false*/,
+ bool IsKnownUnreachable /*= false*/) {
llvm::SmallVector<AnalyzeImplicitConversionsWorkItem, 16> WorkList;
- WorkList.push_back({OrigE, CC, IsListInit});
+ WorkList.push_back({OrigE, CC, IsListInit, IsKnownUnreachable});
while (!WorkList.empty())
AnalyzeImplicitConversions(S, WorkList.pop_back_val(), WorkList);
}
>From 1bf751999f6269638fd8a01558ac584f7aada503 Mon Sep 17 00:00:00 2001
From: Alex Filak <filaka771 at gmail.com>
Date: Fri, 18 Sep 2026 22:57:58 +0300
Subject: [PATCH 2/3] [clang][Sema] Add tests for unselected constant
conversions
---
clang/test/Sema/constant-conversion.c | 80 ++++++++++++
clang/test/SemaCXX/constant-conversion.cpp | 134 +++++++++++++++++++++
2 files changed, 214 insertions(+)
diff --git a/clang/test/Sema/constant-conversion.c b/clang/test/Sema/constant-conversion.c
index adba3315cc9b4..a92eec6773c43 100644
--- a/clang/test/Sema/constant-conversion.c
+++ b/clang/test/Sema/constant-conversion.c
@@ -69,6 +69,86 @@ void test5(void) {
a.b = 100;
}
+// GH223923: Do not diagnose conversions in unselected operands of constant
+// conditional expressions.
+#define BASE64_INIT_1(v, ch_62, ch_63) \
+ [v] = (v) >= 'A' && (v) <= 'Z' ? (v) - 'A' \
+ : (v) >= 'a' && (v) <= 'z' ? (v) - 'a' + 26 \
+ : (v) >= '0' && (v) <= '9' ? (v) - '0' + 52 \
+ : (v) == (ch_62) ? 62 : (v) == (ch_63) ? 63 : -1
+
+#define BASE64_INIT_2(v, ...) \
+ BASE64_INIT_1(v, __VA_ARGS__), BASE64_INIT_1((v) + 1, __VA_ARGS__)
+#define BASE64_INIT_4(v, ...) \
+ BASE64_INIT_2(v, __VA_ARGS__), BASE64_INIT_2((v) + 2, __VA_ARGS__)
+#define BASE64_INIT_8(v, ...) \
+ BASE64_INIT_4(v, __VA_ARGS__), BASE64_INIT_4((v) + 4, __VA_ARGS__)
+#define BASE64_INIT_16(v, ...) \
+ BASE64_INIT_8(v, __VA_ARGS__), BASE64_INIT_8((v) + 8, __VA_ARGS__)
+#define BASE64_INIT_32(v, ...) \
+ BASE64_INIT_16(v, __VA_ARGS__), BASE64_INIT_16((v) + 16, __VA_ARGS__)
+
+#define BASE64_REV_INIT(ch_62, ch_63) { \
+ [0 ... 0x1f] = -1, \
+ BASE64_INIT_32(0x20, ch_62, ch_63), \
+ BASE64_INIT_32(0x40, ch_62, ch_63), \
+ BASE64_INIT_32(0x60, ch_62, ch_63), \
+ [0x80 ... 0xff] = -1 \
+}
+
+enum base64_variant {
+ BASE64_STD,
+ BASE64_URLSAFE,
+ BASE64_IMAP,
+};
+
+static const signed char base64_rev_maps[][256] __attribute__((unused)) = {
+ [BASE64_STD] = BASE64_REV_INIT('+', '/'),
+ [BASE64_URLSAFE] = BASE64_REV_INIT('-', '_'),
+ [BASE64_IMAP] = BASE64_REV_INIT('+', ',')
+};
+
+#undef BASE64_REV_INIT
+#undef BASE64_INIT_32
+#undef BASE64_INIT_16
+#undef BASE64_INIT_8
+#undef BASE64_INIT_4
+#undef BASE64_INIT_2
+#undef BASE64_INIT_1
+
+static const signed char signed_char_array_live[] __attribute__((unused)) = {
+ 1 ? 128 : -1 // expected-warning {{implicit conversion from 'int' to 'signed char' changes value from 128 to -128}}
+};
+
+void GH223923(int condition) {
+ signed char signed_char_dead = 0 ? 128 : -1;
+ signed char signed_char_live = 1 ? 128 : -1;
+ // expected-warning at -1 {{implicit conversion from 'int' to 'signed char' changes value from 128 to -128}}
+ signed char signed_char_maybe = condition ? 128 : -1;
+ // expected-warning at -1 {{implicit conversion from 'int' to 'signed char' changes value from 128 to -128}}
+
+ short short_dead = 0 ? 32768 : 1;
+ short short_live = 1 ? 32768 : 1;
+ // expected-warning at -1 {{implicit conversion from 'int' to 'short' changes value from 32768 to -32768}}
+
+ int int_dead = 0 ? 2147483648LL : 1;
+ int int_live = 1 ? 2147483648LL : 1;
+ // expected-warning at -1 {{implicit conversion from 'long long' to 'int' changes value from 2147483648 to -2147483648}}
+
+ signed char truncation_dead = 0 ? 256 : 1;
+ signed char truncation_live = 1 ? 256 : 1;
+ // expected-warning at -1 {{implicit conversion from 'int' to 'signed char' changes value from 256 to 0}}
+
+ signed char sink;
+ int assignment_dead = 0 ? (sink = 128) : 1;
+ int assignment_live = 1 ? (sink = 128) : 1;
+ // expected-warning at -1 {{implicit conversion from 'int' to 'signed char' changes value from 128 to -128}}
+
+ signed char binary_dead = 1 ?: 128;
+ signed char binary_live = 0 ?: 128;
+ // expected-warning at -1 {{implicit conversion from 'int' to 'signed char' changes value from 128 to -128}}
+}
+
void test6(void) {
// Test that unreachable code doesn't trigger the truncation warning.
unsigned char x = 0 ? 65535 : 1; // no-warning
diff --git a/clang/test/SemaCXX/constant-conversion.cpp b/clang/test/SemaCXX/constant-conversion.cpp
index 9be8b139e79e2..3f38310aa8c3a 100644
--- a/clang/test/SemaCXX/constant-conversion.cpp
+++ b/clang/test/SemaCXX/constant-conversion.cpp
@@ -1,4 +1,5 @@
// RUN: %clang_cc1 -fsyntax-only -verify -triple x86_64-apple-darwin %s
+// RUN: %clang_cc1 -fsyntax-only -verify -triple x86_64-apple-darwin -fexperimental-new-constant-interpreter %s
// This file tests -Wconstant-conversion, a subcategory of -Wconversion
// which is on by default.
@@ -23,6 +24,139 @@ void too_big_for_char(int param) {
char ok4 = true ? 0 : nines() + 1;
}
+namespace GH223923 {
+
+void local_initializers(bool condition) {
+ constexpr signed char constexpr_dead = false ? 128 : 1;
+ constexpr signed char constexpr_live = true ? 128 : 1;
+ // expected-warning at -1 {{implicit conversion from 'int' to 'const signed char' changes value from 128 to -128}}
+
+ short short_dead = false ? 32768 : 1;
+ int int_dead = false ? 2147483648LL : 1;
+
+ short short_live = true ? 32768 : 1;
+ // expected-warning at -1 {{implicit conversion from 'int' to 'short' changes value from 32768 to -32768}}
+ int int_maybe = condition ? 2147483648LL : 1;
+ // expected-warning at -1 {{implicit conversion from 'long long' to 'int' changes value from 2147483648 to -2147483648}}
+
+ signed char nested_dead = true ? 1 : ((true ? 128 : 1) + 0);
+ signed char nested_live = false ? 1 : ((true ? 128 : 1) + 0);
+ // expected-warning at -1 {{implicit conversion from 'int' to 'signed char' changes value from 128 to -128}}
+}
+
+constexpr signed char global_dead = false ? 128 : 1;
+constexpr signed char global_live = true ? 128 : 1;
+// expected-warning at -1 {{implicit conversion from 'int' to 'const signed char' changes value from 128 to -128}}
+
+struct MemberInitializers {
+ signed char member_dead = false ? 128 : 1;
+ signed char member_live = true ? 128 : 1;
+ // expected-warning at -1 {{implicit conversion from 'int' to 'signed char' changes value from 128 to -128}}
+
+ static const signed char static_dead = false ? 128 : 1;
+ static const signed char static_live = true ? 128 : 1;
+ // expected-warning at -1 {{implicit conversion from 'int' to 'const signed char' changes value from 128 to -128}}
+};
+
+void default_arg_dead(signed char = false ? 128 : 1);
+void default_arg_live(signed char = true ? 128 : 1);
+// expected-warning at -1 {{implicit conversion from 'int' to 'signed char' changes value from 128 to -128}}
+
+// The larger truncation path must also respect the unselected operand, even
+// in contexts where there is no function body to analyze for reachability.
+void default_arg_truncation_dead(signed char = false ? 256 : 1);
+void default_arg_truncation_live(signed char = true ? 256 : 1);
+// expected-warning at -1 {{implicit conversion from 'int' to 'signed char' changes value from 256 to 0}}
+
+int consume(signed char);
+signed char sink;
+int total;
+
+// Preserve the outer conditional's reachability when recursing through
+// calls, comparisons, assignments, and compound assignments.
+void call_dead(int = false ? consume(128) : 1);
+void call_live(int = true ? consume(128) : 1);
+// expected-warning at -1 {{implicit conversion from 'int' to 'signed char' changes value from 128 to -128}}
+void comparison_dead(bool = false ? (consume(128) == 0) : false);
+void comparison_live(bool = true ? (consume(128) == 0) : false);
+// expected-warning at -1 {{implicit conversion from 'int' to 'signed char' changes value from 128 to -128}}
+void assignment_dead(int = false ? (sink = 128) : 1);
+void assignment_live(int = true ? (sink = 128) : 1);
+// expected-warning at -1 {{implicit conversion from 'int' to 'signed char' changes value from 128 to -128}}
+void compound_assignment_dead(int = false ? (total += consume(128)) : 1);
+void compound_assignment_live(int = true ? (total += consume(128)) : 1);
+// expected-warning at -1 {{implicit conversion from 'int' to 'signed char' changes value from 128 to -128}}
+
+struct NestedInitializers {
+ bool comparison_dead = false ? (consume(128) == 0) : false;
+ int assignment_dead = false ? (sink = 128) : 1;
+ int compound_assignment_dead = false ? (total += consume(128)) : 1;
+ signed char truncation_dead = false ? 256 : 1;
+ signed char truncation_live = true ? 256 : 1;
+ // expected-warning at -1 {{implicit conversion from 'int' to 'signed char' changes value from 256 to 0}}
+};
+
+constexpr bool in_constant_evaluation() {
+ return __builtin_is_constant_evaluated();
+}
+
+// Runtime reachability must not suppress conversions that take place during
+// constant evaluation.
+constexpr signed char constant_global_live = in_constant_evaluation() ? 128 : 1;
+// expected-warning at -1 {{implicit conversion from 'int' to 'const signed char' changes value from 128 to -128}}
+constexpr signed char constant_global_dead = in_constant_evaluation() ? 1 : 128;
+static_assert(constant_global_live == -128, "");
+static_assert(constant_global_dead == 1, "");
+
+void constant_initializers() {
+ constexpr signed char live = in_constant_evaluation() ? 128 : 1;
+ // expected-warning at -1 {{implicit conversion from 'int' to 'const signed char' changes value from 128 to -128}}
+ constexpr signed char dead = in_constant_evaluation() ? 1 : 128;
+ static_assert(live == -128, "");
+ static_assert(dead == 1, "");
+}
+
+// Neither operand can be ruled out when the same function can be evaluated
+// both at runtime and at compile time.
+constexpr signed char constant_function() {
+ return in_constant_evaluation() ? 128 : 1;
+ // expected-warning at -1 {{implicit conversion from 'int' to 'signed char' changes value from 128 to -128}}
+}
+constexpr signed char runtime_function() {
+ return in_constant_evaluation() ? 1 : 128;
+ // expected-warning at -1 {{implicit conversion from 'int' to 'signed char' changes value from 128 to -128}}
+}
+static_assert(constant_function() == -128, "");
+static_assert(runtime_function() == 1, "");
+
+constexpr int zero(signed char) { return 0; }
+
+void binary_conditionals() {
+ // The common expression of GNU's x ?: y is evaluated even when it is zero.
+ int common_live = zero(128) ?: 1;
+ // expected-warning at -1 {{implicit conversion from 'int' to 'signed char' changes value from 128 to -128}}
+ int common_dead = false ? (zero(128) ?: 1) : 1;
+
+ signed char true_live = 128 ?: 1;
+ // expected-warning at -1 {{implicit conversion from 'int' to 'signed char' changes value from 128 to -128}}
+ signed char false_live = 0 ?: 128;
+ // expected-warning at -1 {{implicit conversion from 'int' to 'signed char' changes value from 128 to -128}}
+ signed char false_dead = 1 ?: 128;
+}
+
+template <bool Condition> void template_conditional() {
+ signed char value = Condition ? 128 : 1;
+ // expected-warning at -1 {{implicit conversion from 'int' to 'signed char' changes value from 128 to -128}}
+}
+
+void instantiate_conditionals() {
+ template_conditional<false>();
+ template_conditional<true>();
+ // expected-note at -1 {{in instantiation of function template specialization 'GH223923::template_conditional<true>' requested here}}
+}
+
+} // namespace GH223923
+
void test_bitfield() {
struct S {
int one_bit : 1;
>From 77223076ab486f8a1f1eba430719e3aa35d94ced Mon Sep 17 00:00:00 2001
From: Alex Filak <filaka771 at gmail.com>
Date: Fri, 18 Sep 2026 22:59:21 +0300
Subject: [PATCH 3/3] [clang][docs] Document the conditional conversion warning
fix
---
clang/docs/ReleaseNotes.md | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md
index 3c6acf353f93f..9a3962c622413 100644
--- a/clang/docs/ReleaseNotes.md
+++ b/clang/docs/ReleaseNotes.md
@@ -503,6 +503,10 @@ features cannot lower the translation-unit ABI level;
- Fixed a missing `-Wconstant-conversion` diagnostic for signed `char` arrays.
+- Fixed false-positive `-Wconstant-conversion` diagnostics for values in
+ unreachable branches of conditional expressions, including signed `char`
+ array initializers. (#GH223923)
+
- Clang now diagnoses passing wrong vector type as a mask to `__builtin_shufflevector`. (#GH218132)
- `-Wdelete-abstract-non-virtual-dtor` and `-Wdelete-non-abstract-non-virtual-dtor`
More information about the cfe-commits
mailing list