From e4e1e00f2fb40da558038e702a275de6203244f8 Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Tue, 14 Jul 2026 06:48:03 -0700 Subject: [PATCH] Ensure unsigned enum initializers cast to the signed underlying type An in-range unsigned initializer on an enum with a signed underlying type (e.g. `1U << 22` on the default `int`) was emitted without a cast, producing a `uint` assigned to an `int` member which fails to compile (CS0266). Emit an explicit cast to the underlying type in that case, matching the existing out-of-range `unchecked((int)...)` behavior. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../PInvokeGenerator.cs | 46 +++++++++++++++++++ ...nonymousEnumTest.CSharp.Compatible.Unix.cs | 2 +- ...thAnonymousEnumTest.CSharp.Default.Unix.cs | 2 +- ...ithAnonymousEnumTest.CSharp.Latest.Unix.cs | 2 +- ...thAnonymousEnumTest.CSharp.Preview.Unix.cs | 2 +- ...hAnonymousEnumTest.Xml.Compatible.Unix.xml | 7 ++- ...WithAnonymousEnumTest.Xml.Default.Unix.xml | 7 ++- .../WithAnonymousEnumTest.Xml.Latest.Unix.xml | 7 ++- ...WithAnonymousEnumTest.Xml.Preview.Unix.xml | 7 ++- .../WithUnsignedInitConversionTest.CSharp.cs | 10 ++++ .../WithUnsignedInitConversionTest.Xml.xml | 44 ++++++++++++++++++ .../Baseline/EnumDeclarationTest.cs | 11 +++++ 12 files changed, 139 insertions(+), 8 deletions(-) create mode 100644 tests/ClangSharp.PInvokeGenerator.UnitTests/Baseline/Baselines/EnumDeclaration/WithUnsignedInitConversionTest.CSharp.cs create mode 100644 tests/ClangSharp.PInvokeGenerator.UnitTests/Baseline/Baselines/EnumDeclaration/WithUnsignedInitConversionTest.Xml.xml diff --git a/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.cs b/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.cs index 61300722..f3d84dc7 100644 --- a/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.cs +++ b/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.cs @@ -2089,12 +2089,58 @@ private void UncheckStmt(string targetTypeName, Stmt stmt) _outputBuilder.EndUnchecked(); } + else if (EnumConstantInitNeedsCast(targetTypeName, stmt)) + { + _outputBuilder.BeginInnerValue(); + _outputBuilder.BeginInnerCast(); + _outputBuilder.WriteCastType(targetTypeName); + _outputBuilder.EndInnerCast(); + + ParenthesizeStmt(stmt); + + _outputBuilder.EndInnerValue(); + } else { VisitStmt(stmt); } } + private bool EnumConstantInitNeedsCast(string targetTypeName, Stmt stmt) + { + // A C# enum member initializer must be implicitly convertible to the enum's underlying + // type. The generator forces most enums to `int`, so an unsigned initializer expression + // (e.g. `1U << 22`) is not implicitly convertible and needs an explicit cast to the + // underlying type to compile. Out-of-range values are handled by the unchecked path. + + if (stmt.DeclContext is not EnumDecl) + { + return false; + } + + // Only cast the top-level initializer, not each nested subexpression, to avoid + // recursing back into this path while visiting the parenthesized initializer. + if (PreviousContext.Cursor is not EnumConstantDecl) + { + return false; + } + + var expr = stmt as Expr; + + if (expr is ImplicitCastExpr implicitCastExpr) + { + expr = implicitCastExpr.SubExprAsWritten; + } + + if (expr is null) + { + return false; + } + + var sourceTypeName = GetRemappedTypeName(expr, context: null, expr.Type, out _); + return IsUnsigned(sourceTypeName) && !IsUnsigned(targetTypeName); + } + private void Visit(Cursor cursor) { var currentContext = _context.AddLast((cursor, null)); diff --git a/tests/ClangSharp.PInvokeGenerator.UnitTests/Baseline/Baselines/EnumDeclaration/WithAnonymousEnumTest.CSharp.Compatible.Unix.cs b/tests/ClangSharp.PInvokeGenerator.UnitTests/Baseline/Baselines/EnumDeclaration/WithAnonymousEnumTest.CSharp.Compatible.Unix.cs index 1ba5fe8f..90dbb336 100644 --- a/tests/ClangSharp.PInvokeGenerator.UnitTests/Baseline/Baselines/EnumDeclaration/WithAnonymousEnumTest.CSharp.Compatible.Unix.cs +++ b/tests/ClangSharp.PInvokeGenerator.UnitTests/Baseline/Baselines/EnumDeclaration/WithAnonymousEnumTest.CSharp.Compatible.Unix.cs @@ -4,7 +4,7 @@ namespace ClangSharp.Test { public enum MyEnum2 { - MyEnum2_Value1 = MyEnum1_Value1, + MyEnum2_Value1 = ((int)(MyEnum1_Value1)), } public static partial class Methods diff --git a/tests/ClangSharp.PInvokeGenerator.UnitTests/Baseline/Baselines/EnumDeclaration/WithAnonymousEnumTest.CSharp.Default.Unix.cs b/tests/ClangSharp.PInvokeGenerator.UnitTests/Baseline/Baselines/EnumDeclaration/WithAnonymousEnumTest.CSharp.Default.Unix.cs index 1ba5fe8f..90dbb336 100644 --- a/tests/ClangSharp.PInvokeGenerator.UnitTests/Baseline/Baselines/EnumDeclaration/WithAnonymousEnumTest.CSharp.Default.Unix.cs +++ b/tests/ClangSharp.PInvokeGenerator.UnitTests/Baseline/Baselines/EnumDeclaration/WithAnonymousEnumTest.CSharp.Default.Unix.cs @@ -4,7 +4,7 @@ namespace ClangSharp.Test { public enum MyEnum2 { - MyEnum2_Value1 = MyEnum1_Value1, + MyEnum2_Value1 = ((int)(MyEnum1_Value1)), } public static partial class Methods diff --git a/tests/ClangSharp.PInvokeGenerator.UnitTests/Baseline/Baselines/EnumDeclaration/WithAnonymousEnumTest.CSharp.Latest.Unix.cs b/tests/ClangSharp.PInvokeGenerator.UnitTests/Baseline/Baselines/EnumDeclaration/WithAnonymousEnumTest.CSharp.Latest.Unix.cs index 1ba5fe8f..90dbb336 100644 --- a/tests/ClangSharp.PInvokeGenerator.UnitTests/Baseline/Baselines/EnumDeclaration/WithAnonymousEnumTest.CSharp.Latest.Unix.cs +++ b/tests/ClangSharp.PInvokeGenerator.UnitTests/Baseline/Baselines/EnumDeclaration/WithAnonymousEnumTest.CSharp.Latest.Unix.cs @@ -4,7 +4,7 @@ namespace ClangSharp.Test { public enum MyEnum2 { - MyEnum2_Value1 = MyEnum1_Value1, + MyEnum2_Value1 = ((int)(MyEnum1_Value1)), } public static partial class Methods diff --git a/tests/ClangSharp.PInvokeGenerator.UnitTests/Baseline/Baselines/EnumDeclaration/WithAnonymousEnumTest.CSharp.Preview.Unix.cs b/tests/ClangSharp.PInvokeGenerator.UnitTests/Baseline/Baselines/EnumDeclaration/WithAnonymousEnumTest.CSharp.Preview.Unix.cs index 1ba5fe8f..90dbb336 100644 --- a/tests/ClangSharp.PInvokeGenerator.UnitTests/Baseline/Baselines/EnumDeclaration/WithAnonymousEnumTest.CSharp.Preview.Unix.cs +++ b/tests/ClangSharp.PInvokeGenerator.UnitTests/Baseline/Baselines/EnumDeclaration/WithAnonymousEnumTest.CSharp.Preview.Unix.cs @@ -4,7 +4,7 @@ namespace ClangSharp.Test { public enum MyEnum2 { - MyEnum2_Value1 = MyEnum1_Value1, + MyEnum2_Value1 = ((int)(MyEnum1_Value1)), } public static partial class Methods diff --git a/tests/ClangSharp.PInvokeGenerator.UnitTests/Baseline/Baselines/EnumDeclaration/WithAnonymousEnumTest.Xml.Compatible.Unix.xml b/tests/ClangSharp.PInvokeGenerator.UnitTests/Baseline/Baselines/EnumDeclaration/WithAnonymousEnumTest.Xml.Compatible.Unix.xml index 26f7ce06..985c34e4 100644 --- a/tests/ClangSharp.PInvokeGenerator.UnitTests/Baseline/Baselines/EnumDeclaration/WithAnonymousEnumTest.Xml.Compatible.Unix.xml +++ b/tests/ClangSharp.PInvokeGenerator.UnitTests/Baseline/Baselines/EnumDeclaration/WithAnonymousEnumTest.Xml.Compatible.Unix.xml @@ -6,7 +6,12 @@ int - MyEnum1_Value1 + + int + + MyEnum1_Value1 + + diff --git a/tests/ClangSharp.PInvokeGenerator.UnitTests/Baseline/Baselines/EnumDeclaration/WithAnonymousEnumTest.Xml.Default.Unix.xml b/tests/ClangSharp.PInvokeGenerator.UnitTests/Baseline/Baselines/EnumDeclaration/WithAnonymousEnumTest.Xml.Default.Unix.xml index 26f7ce06..985c34e4 100644 --- a/tests/ClangSharp.PInvokeGenerator.UnitTests/Baseline/Baselines/EnumDeclaration/WithAnonymousEnumTest.Xml.Default.Unix.xml +++ b/tests/ClangSharp.PInvokeGenerator.UnitTests/Baseline/Baselines/EnumDeclaration/WithAnonymousEnumTest.Xml.Default.Unix.xml @@ -6,7 +6,12 @@ int - MyEnum1_Value1 + + int + + MyEnum1_Value1 + + diff --git a/tests/ClangSharp.PInvokeGenerator.UnitTests/Baseline/Baselines/EnumDeclaration/WithAnonymousEnumTest.Xml.Latest.Unix.xml b/tests/ClangSharp.PInvokeGenerator.UnitTests/Baseline/Baselines/EnumDeclaration/WithAnonymousEnumTest.Xml.Latest.Unix.xml index 26f7ce06..985c34e4 100644 --- a/tests/ClangSharp.PInvokeGenerator.UnitTests/Baseline/Baselines/EnumDeclaration/WithAnonymousEnumTest.Xml.Latest.Unix.xml +++ b/tests/ClangSharp.PInvokeGenerator.UnitTests/Baseline/Baselines/EnumDeclaration/WithAnonymousEnumTest.Xml.Latest.Unix.xml @@ -6,7 +6,12 @@ int - MyEnum1_Value1 + + int + + MyEnum1_Value1 + + diff --git a/tests/ClangSharp.PInvokeGenerator.UnitTests/Baseline/Baselines/EnumDeclaration/WithAnonymousEnumTest.Xml.Preview.Unix.xml b/tests/ClangSharp.PInvokeGenerator.UnitTests/Baseline/Baselines/EnumDeclaration/WithAnonymousEnumTest.Xml.Preview.Unix.xml index 26f7ce06..985c34e4 100644 --- a/tests/ClangSharp.PInvokeGenerator.UnitTests/Baseline/Baselines/EnumDeclaration/WithAnonymousEnumTest.Xml.Preview.Unix.xml +++ b/tests/ClangSharp.PInvokeGenerator.UnitTests/Baseline/Baselines/EnumDeclaration/WithAnonymousEnumTest.Xml.Preview.Unix.xml @@ -6,7 +6,12 @@ int - MyEnum1_Value1 + + int + + MyEnum1_Value1 + + diff --git a/tests/ClangSharp.PInvokeGenerator.UnitTests/Baseline/Baselines/EnumDeclaration/WithUnsignedInitConversionTest.CSharp.cs b/tests/ClangSharp.PInvokeGenerator.UnitTests/Baseline/Baselines/EnumDeclaration/WithUnsignedInitConversionTest.CSharp.cs new file mode 100644 index 00000000..5ad0029a --- /dev/null +++ b/tests/ClangSharp.PInvokeGenerator.UnitTests/Baseline/Baselines/EnumDeclaration/WithUnsignedInitConversionTest.CSharp.cs @@ -0,0 +1,10 @@ +namespace ClangSharp.Test +{ + public enum MyEnum + { + MyEnum_Value0, + MyEnum_Value1 = ((int)(1U << 22)), + MyEnum_Value2 = ((int)((1U << 22) | (1 << 12))), + MyEnum_Value3 = unchecked((int)(0x80000000)), + } +} diff --git a/tests/ClangSharp.PInvokeGenerator.UnitTests/Baseline/Baselines/EnumDeclaration/WithUnsignedInitConversionTest.Xml.xml b/tests/ClangSharp.PInvokeGenerator.UnitTests/Baseline/Baselines/EnumDeclaration/WithUnsignedInitConversionTest.Xml.xml new file mode 100644 index 00000000..0aba1ef2 --- /dev/null +++ b/tests/ClangSharp.PInvokeGenerator.UnitTests/Baseline/Baselines/EnumDeclaration/WithUnsignedInitConversionTest.Xml.xml @@ -0,0 +1,44 @@ + + + + + int + + int + + + int + + + int + (1U << 22) + + + + + int + + + int + + (1U << 22) | (1 << 12) + + + + + + int + + + + int + + 0x80000000 + + + + + + + + diff --git a/tests/ClangSharp.PInvokeGenerator.UnitTests/Baseline/EnumDeclarationTest.cs b/tests/ClangSharp.PInvokeGenerator.UnitTests/Baseline/EnumDeclarationTest.cs index 2ec592a8..7867464a 100644 --- a/tests/ClangSharp.PInvokeGenerator.UnitTests/Baseline/EnumDeclarationTest.cs +++ b/tests/ClangSharp.PInvokeGenerator.UnitTests/Baseline/EnumDeclarationTest.cs @@ -231,6 +231,17 @@ public Task WithImplicitConversionTest() MyEnum_Value1, MyEnum_Value2 = 0x80000000, }; +"); + + [Test] + public Task WithUnsignedInitConversionTest() + => ValidateAsync(nameof(WithUnsignedInitConversionTest), @"enum MyEnum : int +{ + MyEnum_Value0, + MyEnum_Value1 = (1U << 22), + MyEnum_Value2 = (1U << 22) | (1 << 12), + MyEnum_Value3 = 0x80000000, +}; "); [Test]