From 03410c4455e98ae285d022ca8909711982b34661 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Sun, 6 Sep 2026 20:28:00 +0200 Subject: [PATCH] TransformExpressionTrees: keep a constant's declared type A constant narrower than its stack type builds as a plain ldc.i4, which infers as int, so a conditional whose other branch really is a char saw two different types and the whole tree was left as the Expression calls that built it - EF Core's StringCharConverter.ToChar is one. Bool and enum constants were already wrapped for this reason; the wrap now applies wherever the built value does not infer as the declared type. Assisted-by: Claude:claude-opus-5:Claude Code --- .../TestCases/Pretty/ExpressionTrees.cs | 7 +++++++ .../IL/Transforms/TransformExpressionTrees.cs | 13 ++++++++++--- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/ExpressionTrees.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/ExpressionTrees.cs index d6a768dce..a379899b2 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/ExpressionTrees.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/ExpressionTrees.cs @@ -416,6 +416,13 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty ToCode(X(), () => "abc"[1] == 'b'); } + public void ConditionalWithNarrowConstant(string s) + { + ToCode(X(), () => (s.Length < 1) ? '\0' : s[0]); + ToCode(X(), () => (s.Length < 1) ? ((byte)0) : ((byte)s[0])); + ToCode(X(), () => (s.Length < 1) ? ((short)0) : ((short)s[0])); + } + public void StringsImplicitCast() { int i = 1; diff --git a/ICSharpCode.Decompiler/IL/Transforms/TransformExpressionTrees.cs b/ICSharpCode.Decompiler/IL/Transforms/TransformExpressionTrees.cs index 8d593935d..96dbe32de 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/TransformExpressionTrees.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/TransformExpressionTrees.cs @@ -1156,9 +1156,16 @@ namespace ICSharpCode.Decompiler.IL.Transforms return null; if (value.MatchBox(out var arg, out var boxType)) { - if (boxType.Kind == TypeKind.Enum || boxType.IsKnownType(KnownTypeCode.Boolean)) - return () => new ExpressionTreeCast(boxType, ConvertValue(arg, invocation), false); - return () => ConvertValue(arg, invocation); + return () => { + // A constant narrower than its stack type - a bool, a char, an enum, one of + // the small integers - builds as a plain ldc.i4 that infers as int, and + // consumers compare inferred types. The cast keeps the type the tree + // declared for it. + var constantValue = ConvertValue(arg, invocation); + if (!NormalizeTypeVisitor.TypeErasure.EquivalentTypes(constantValue.InferType(context.TypeSystem), boxType)) + return new ExpressionTreeCast(boxType, constantValue, false); + return constantValue; + }; } return () => ConvertValue(value, invocation);