From 1b734e7f8a5dc1b34f4df137d0d07f5d088921ec Mon Sep 17 00:00:00 2001 From: William Chen Date: Sun, 28 Jun 2026 04:55:34 -0400 Subject: [PATCH 01/19] Add LongDefinesTest with expected set to incorrect, current output --- .../CTest.cs | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/tests/ClangSharp.PInvokeGenerator.UnitTests/CTest.cs b/tests/ClangSharp.PInvokeGenerator.UnitTests/CTest.cs index 22d264a7..abb24e40 100644 --- a/tests/ClangSharp.PInvokeGenerator.UnitTests/CTest.cs +++ b/tests/ClangSharp.PInvokeGenerator.UnitTests/CTest.cs @@ -1002,4 +1002,42 @@ readonly get { "Flags", "Flags" } }); } + + [Test] + public Task LongDefinesTest() + { + var inputContents = @" +// stdint.h +#define SIZE_MAX (18446744073709551615UL) + +// cl_ext.h from OpenCL +#define CL_IMPORT_MEMORY_WHOLE_ALLOCATION_ARM SIZE_MAX + +// limits.h +#define LONG_MAX __LONG_MAX__ +#define ULONG_MAX (__LONG_MAX__ *2UL+1UL) +"; + + // TODO: This is the current output on Linux, not the expected + var expectedOutputContents = @"namespace ClangSharp.Test +{ + public static partial class Methods + { + [NativeTypeName(""#define SIZE_MAX (18446744073709551615UL)"")] + public const uint SIZE_MAX = (18446744073709551615U); + + [NativeTypeName(""#define CL_IMPORT_MEMORY_WHOLE_ALLOCATION_ARM SIZE_MAX"")] + public const uint CL_IMPORT_MEMORY_WHOLE_ALLOCATION_ARM = (18446744073709551615U); + + [NativeTypeName(""#define LONG_MAX __LONG_MAX__"")] + public const int LONG_MAX = unchecked(9223372036854775807); + + [NativeTypeName(""#define ULONG_MAX (__LONG_MAX__ *2UL+1UL)"")] + public const uint ULONG_MAX = (9223372036854775807 * 2U + 1U); + } +} +"; + + return ValidateGeneratedCSharpLatestWindowsBindingsAsync(inputContents, expectedOutputContents, commandLineArgs: DefaultCClangCommandLineArgs, language: "c", languageStandard: DefaultCStandard); + } } From 7a2fa90b99f39f4388479035ee8b40ef5d4f5e34 Mon Sep 17 00:00:00 2001 From: William Chen Date: Sun, 28 Jun 2026 05:00:15 -0400 Subject: [PATCH 02/19] Set expected output This is my initial guess for what the output should look like. --- tests/ClangSharp.PInvokeGenerator.UnitTests/CTest.cs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/tests/ClangSharp.PInvokeGenerator.UnitTests/CTest.cs b/tests/ClangSharp.PInvokeGenerator.UnitTests/CTest.cs index abb24e40..32a21380 100644 --- a/tests/ClangSharp.PInvokeGenerator.UnitTests/CTest.cs +++ b/tests/ClangSharp.PInvokeGenerator.UnitTests/CTest.cs @@ -1018,22 +1018,21 @@ public Task LongDefinesTest() #define ULONG_MAX (__LONG_MAX__ *2UL+1UL) "; - // TODO: This is the current output on Linux, not the expected var expectedOutputContents = @"namespace ClangSharp.Test { public static partial class Methods { [NativeTypeName(""#define SIZE_MAX (18446744073709551615UL)"")] - public const uint SIZE_MAX = (18446744073709551615U); + public const ulong SIZE_MAX = (18446744073709551615U); [NativeTypeName(""#define CL_IMPORT_MEMORY_WHOLE_ALLOCATION_ARM SIZE_MAX"")] - public const uint CL_IMPORT_MEMORY_WHOLE_ALLOCATION_ARM = (18446744073709551615U); + public const ulong CL_IMPORT_MEMORY_WHOLE_ALLOCATION_ARM = (18446744073709551615U); [NativeTypeName(""#define LONG_MAX __LONG_MAX__"")] - public const int LONG_MAX = unchecked(9223372036854775807); + public const long LONG_MAX = unchecked(9223372036854775807); [NativeTypeName(""#define ULONG_MAX (__LONG_MAX__ *2UL+1UL)"")] - public const uint ULONG_MAX = (9223372036854775807 * 2U + 1U); + public const ulong ULONG_MAX = unchecked((ulong)(unchecked(9223372036854775807 * 2U) + 1U)); } } "; From e0a33b57d597d9dd50808979ae7dc21533de22b1 Mon Sep 17 00:00:00 2001 From: William Chen Date: Mon, 29 Jun 2026 01:28:43 -0400 Subject: [PATCH 03/19] Adjust and rename CLongDefinesTestUnix --- .../CTest.cs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/tests/ClangSharp.PInvokeGenerator.UnitTests/CTest.cs b/tests/ClangSharp.PInvokeGenerator.UnitTests/CTest.cs index 32a21380..9695a626 100644 --- a/tests/ClangSharp.PInvokeGenerator.UnitTests/CTest.cs +++ b/tests/ClangSharp.PInvokeGenerator.UnitTests/CTest.cs @@ -1004,8 +1004,11 @@ readonly get } [Test] - public Task LongDefinesTest() + [Platform("unix")] // This test has slight platform-specific differences + public Task CLongDefinesTestUnix() { + // C longs differ based on platform + // This test was added due to some missing type casts var inputContents = @" // stdint.h #define SIZE_MAX (18446744073709551615UL) @@ -1023,20 +1026,20 @@ public Task LongDefinesTest() public static partial class Methods { [NativeTypeName(""#define SIZE_MAX (18446744073709551615UL)"")] - public const ulong SIZE_MAX = (18446744073709551615U); + public const nuint SIZE_MAX = (18446744073709551615U); [NativeTypeName(""#define CL_IMPORT_MEMORY_WHOLE_ALLOCATION_ARM SIZE_MAX"")] - public const ulong CL_IMPORT_MEMORY_WHOLE_ALLOCATION_ARM = (18446744073709551615U); + public const nuint CL_IMPORT_MEMORY_WHOLE_ALLOCATION_ARM = (18446744073709551615U); [NativeTypeName(""#define LONG_MAX __LONG_MAX__"")] - public const long LONG_MAX = unchecked(9223372036854775807); + public const nint LONG_MAX = unchecked(9223372036854775807); [NativeTypeName(""#define ULONG_MAX (__LONG_MAX__ *2UL+1UL)"")] - public const ulong ULONG_MAX = unchecked((ulong)(unchecked(9223372036854775807 * 2U) + 1U)); + public const nuint ULONG_MAX = unchecked((ulong)(unchecked(9223372036854775807 * 2U) + 1U)); } } "; - return ValidateGeneratedCSharpLatestWindowsBindingsAsync(inputContents, expectedOutputContents, commandLineArgs: DefaultCClangCommandLineArgs, language: "c", languageStandard: DefaultCStandard); + return ValidateGeneratedCSharpLatestUnixBindingsAsync(inputContents, expectedOutputContents, commandLineArgs: DefaultCClangCommandLineArgs, language: "c", languageStandard: DefaultCStandard); } } From 415ed4e1401a9a0244ccf4bd8a60581d168c87bd Mon Sep 17 00:00:00 2001 From: William Chen Date: Mon, 29 Jun 2026 01:47:17 -0400 Subject: [PATCH 04/19] Define what the probably correct output looks like --- .../CTest.cs | 51 +++++++++++++++++-- 1 file changed, 46 insertions(+), 5 deletions(-) diff --git a/tests/ClangSharp.PInvokeGenerator.UnitTests/CTest.cs b/tests/ClangSharp.PInvokeGenerator.UnitTests/CTest.cs index 9695a626..fb84f371 100644 --- a/tests/ClangSharp.PInvokeGenerator.UnitTests/CTest.cs +++ b/tests/ClangSharp.PInvokeGenerator.UnitTests/CTest.cs @@ -1008,7 +1008,7 @@ readonly get public Task CLongDefinesTestUnix() { // C longs differ based on platform - // This test was added due to some missing type casts + // These values are taken from the Linux headers var inputContents = @" // stdint.h #define SIZE_MAX (18446744073709551615UL) @@ -1021,25 +1021,66 @@ public Task CLongDefinesTestUnix() #define ULONG_MAX (__LONG_MAX__ *2UL+1UL) "; + // We use "static readonly" instead of "const" because nint/nuint differ on 32/64-bit platforms var expectedOutputContents = @"namespace ClangSharp.Test { public static partial class Methods { [NativeTypeName(""#define SIZE_MAX (18446744073709551615UL)"")] - public const nuint SIZE_MAX = (18446744073709551615U); + public static readonly nuint SIZE_MAX = (nuint)(18446744073709551615U); [NativeTypeName(""#define CL_IMPORT_MEMORY_WHOLE_ALLOCATION_ARM SIZE_MAX"")] - public const nuint CL_IMPORT_MEMORY_WHOLE_ALLOCATION_ARM = (18446744073709551615U); + public static readonly nuint CL_IMPORT_MEMORY_WHOLE_ALLOCATION_ARM = (nuint)(18446744073709551615U); [NativeTypeName(""#define LONG_MAX __LONG_MAX__"")] - public const nint LONG_MAX = unchecked(9223372036854775807); + public static readonly nint LONG_MAX = (nint)(9223372036854775807); [NativeTypeName(""#define ULONG_MAX (__LONG_MAX__ *2UL+1UL)"")] - public const nuint ULONG_MAX = unchecked((ulong)(unchecked(9223372036854775807 * 2U) + 1U)); + public static readonly nuint ULONG_MAX = unchecked((nuint)(9223372036854775807 * 2U + 1U)); } } "; return ValidateGeneratedCSharpLatestUnixBindingsAsync(inputContents, expectedOutputContents, commandLineArgs: DefaultCClangCommandLineArgs, language: "c", languageStandard: DefaultCStandard); } + + [Test] + [Platform("win")] // This test has slight platform-specific differences + public Task CLongDefinesTestWindows() + { + // C longs differ based on platform + // These values are taken from the Linux headers + var inputContents = @" +// stdint.h +#define SIZE_MAX (18446744073709551615UL) + +// cl_ext.h from OpenCL +#define CL_IMPORT_MEMORY_WHOLE_ALLOCATION_ARM SIZE_MAX + +// limits.h +#define LONG_MAX __LONG_MAX__ +#define ULONG_MAX (__LONG_MAX__ *2UL+1UL) +"; + + var expectedOutputContents = @"namespace ClangSharp.Test +{ + public static partial class Methods + { + [NativeTypeName(""#define SIZE_MAX (18446744073709551615UL)"")] + public const ulong SIZE_MAX = (18446744073709551615U); + + [NativeTypeName(""#define CL_IMPORT_MEMORY_WHOLE_ALLOCATION_ARM SIZE_MAX"")] + public const ulong CL_IMPORT_MEMORY_WHOLE_ALLOCATION_ARM = (18446744073709551615U); + + [NativeTypeName(""#define LONG_MAX __LONG_MAX__"")] + public const long LONG_MAX = unchecked(9223372036854775807); + + [NativeTypeName(""#define ULONG_MAX (__LONG_MAX__ *2UL+1UL)"")] + public const ulong ULONG_MAX = unchecked((ulong)(9223372036854775807 * 2U + 1U)); + } +} +"; + + return ValidateGeneratedCSharpLatestWindowsBindingsAsync(inputContents, expectedOutputContents, commandLineArgs: DefaultCClangCommandLineArgs, language: "c", languageStandard: DefaultCStandard); + } } From 9fdd364db90e5437c08930ad1dc92dc515a61af8 Mon Sep 17 00:00:00 2001 From: William Chen Date: Mon, 29 Jun 2026 02:07:24 -0400 Subject: [PATCH 05/19] Adjust tests where needed --- .../CTest.cs | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/tests/ClangSharp.PInvokeGenerator.UnitTests/CTest.cs b/tests/ClangSharp.PInvokeGenerator.UnitTests/CTest.cs index fb84f371..b991ffe0 100644 --- a/tests/ClangSharp.PInvokeGenerator.UnitTests/CTest.cs +++ b/tests/ClangSharp.PInvokeGenerator.UnitTests/CTest.cs @@ -1004,7 +1004,7 @@ readonly get } [Test] - [Platform("unix")] // This test has slight platform-specific differences + [Platform("unix")] // This test has slight platform-specific differences (on Windows, __LONG_MAX__ is 32-bit) public Task CLongDefinesTestUnix() { // C longs differ based on platform @@ -1049,34 +1049,34 @@ public static partial class Methods public Task CLongDefinesTestWindows() { // C longs differ based on platform - // These values are taken from the Linux headers + // These values are taken from the Windows headers var inputContents = @" -// stdint.h -#define SIZE_MAX (18446744073709551615UL) +// limits.h +#define SIZE_MAX 0xffffffffffffffffui64 // cl_ext.h from OpenCL #define CL_IMPORT_MEMORY_WHOLE_ALLOCATION_ARM SIZE_MAX // limits.h -#define LONG_MAX __LONG_MAX__ -#define ULONG_MAX (__LONG_MAX__ *2UL+1UL) +#define LONG_MAX 2147483647L +#define ULONG_MAX 0xffffffffUL "; var expectedOutputContents = @"namespace ClangSharp.Test { public static partial class Methods { - [NativeTypeName(""#define SIZE_MAX (18446744073709551615UL)"")] - public const ulong SIZE_MAX = (18446744073709551615U); + [NativeTypeName(""#define SIZE_MAX 0xffffffffffffffffui64"")] + public const ulong SIZE_MAX = 0xffffffffffffffffUL; [NativeTypeName(""#define CL_IMPORT_MEMORY_WHOLE_ALLOCATION_ARM SIZE_MAX"")] - public const ulong CL_IMPORT_MEMORY_WHOLE_ALLOCATION_ARM = (18446744073709551615U); + public const ulong CL_IMPORT_MEMORY_WHOLE_ALLOCATION_ARM = 0xffffffffffffffffUL; - [NativeTypeName(""#define LONG_MAX __LONG_MAX__"")] - public const long LONG_MAX = unchecked(9223372036854775807); + [NativeTypeName(""#define LONG_MAX 2147483647L"")] + public const int LONG_MAX = 2147483647; - [NativeTypeName(""#define ULONG_MAX (__LONG_MAX__ *2UL+1UL)"")] - public const ulong ULONG_MAX = unchecked((ulong)(9223372036854775807 * 2U + 1U)); + [NativeTypeName(""#define ULONG_MAX 0xffffffffUL"")] + public const uint ULONG_MAX = 0xffffffffU; } } "; From 892a54515cdf456d1190abd014fd36c50533a462 Mon Sep 17 00:00:00 2001 From: William Chen Date: Mon, 29 Jun 2026 02:13:37 -0400 Subject: [PATCH 06/19] Also note down which compiler I took the values from --- tests/ClangSharp.PInvokeGenerator.UnitTests/CTest.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/ClangSharp.PInvokeGenerator.UnitTests/CTest.cs b/tests/ClangSharp.PInvokeGenerator.UnitTests/CTest.cs index b991ffe0..664daf28 100644 --- a/tests/ClangSharp.PInvokeGenerator.UnitTests/CTest.cs +++ b/tests/ClangSharp.PInvokeGenerator.UnitTests/CTest.cs @@ -1008,7 +1008,7 @@ readonly get public Task CLongDefinesTestUnix() { // C longs differ based on platform - // These values are taken from the Linux headers + // These values are taken from the Linux headers when using Clang var inputContents = @" // stdint.h #define SIZE_MAX (18446744073709551615UL) @@ -1049,7 +1049,7 @@ public static partial class Methods public Task CLongDefinesTestWindows() { // C longs differ based on platform - // These values are taken from the Windows headers + // These values are taken from the Windows headers when using MSVC var inputContents = @" // limits.h #define SIZE_MAX 0xffffffffffffffffui64 From 96a8dcaf17b4ca6e67e795eaa79b0b071b461d72 Mon Sep 17 00:00:00 2001 From: William Chen Date: Thu, 2 Jul 2026 06:10:08 -0400 Subject: [PATCH 07/19] Implement potential partial fix and add todos containing my notes and other observations --- .../PInvokeGenerator.VisitDecl.cs | 30 +++++++++++++++---- 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.VisitDecl.cs b/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.VisitDecl.cs index bd29843b..5977b39c 100644 --- a/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.VisitDecl.cs +++ b/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.VisitDecl.cs @@ -2587,7 +2587,7 @@ void VisitBitfieldDecl(FieldDecl fieldDecl, BitfieldDesc[] bitfieldDescs, Record case CXType_ULongLong: { - if (typeNameBacking.Equals("nuint", StringComparison.Ordinal)) + if (typeNameBacking.Equals("nuint", StringComparison.Ordinal)) // TODO: Shouldn't this also check UIntPtr? { goto case CXType_UInt; } @@ -2620,7 +2620,7 @@ void VisitBitfieldDecl(FieldDecl fieldDecl, BitfieldDesc[] bitfieldDescs, Record { isTypeBackingSigned = true; - if (typeNameBacking.Equals("nint", StringComparison.Ordinal)) + if (typeNameBacking.Equals("nint", StringComparison.Ordinal)) // TODO: Shouldn't this also check IntPtr? { goto case CXType_Int; } @@ -2686,7 +2686,7 @@ void VisitBitfieldDecl(FieldDecl fieldDecl, BitfieldDesc[] bitfieldDescs, Record case CXType_ULongLong: { - if (typeNameBacking.Equals("nuint", StringComparison.Ordinal)) + if (typeNameBacking.Equals("nuint", StringComparison.Ordinal)) // TODO: Shouldn't this also check UIntPtr? { goto case CXType_UInt; } @@ -2719,7 +2719,7 @@ void VisitBitfieldDecl(FieldDecl fieldDecl, BitfieldDesc[] bitfieldDescs, Record { isTypeSigned = true; - if (typeNameBacking.Equals("nint", StringComparison.Ordinal)) + if (typeNameBacking.Equals("nint", StringComparison.Ordinal)) // TODO: Shouldn't this also check IntPtr? { goto case CXType_Int; } @@ -2773,7 +2773,7 @@ void VisitBitfieldDecl(FieldDecl fieldDecl, BitfieldDesc[] bitfieldDescs, Record // Signed types are sign extended when shifted var isUnsignedToSigned = !isTypeBackingSigned && isTypeSigned; - + // Check if type is directly shiftable/maskable // Remapped types are not guaranteed to be shiftable or maskable // Enums are maskable, but not shiftable @@ -3981,7 +3981,25 @@ private bool IsConstant(string targetTypeName, Expr initExpr) case CX_StmtClass_IntegerLiteral: { - return true; + var notConstant = false; + + // TODO: This is incorrect because C#'s compiler only checks for the range when casting from a const expr ulong/long to a nuint/nint. + // TODO: The ulong/long literal itself is otherwise constant. Eg: const nuint N = (nuint)(4294967296 - 10) compiles just fine. + // TODO: This means we need to track the value of the const expr, which might require a decent amount of work and restructuring + // Constants for native integers must be in range: https://github.com/dotnet/csharplang/blob/main/proposals/csharp-9.0/native-integers.md + + // TODO: This condition is stricter than it should be. + // TODO: Apparently unchecked operations involving a source expression of any 32-bit value is fine. See constant folding in the native-integers proposal. + // TODO: Eg: nuint = unchecked(4294967295 + 10) is fine. Eg: nint = unchecked((nint)4294967295) is also fine. + // TODO: This causes existing tests to fail. Eg: ClangSharp.UnitTests.VarDeclarationTest.UncheckedConversionMacroTest. "const nint MyMacro1 = unchecked((nint)(0x80000000))" incorrectly becomes "static readonly nint MyMacro1 = unchecked((nint)(0x80000000))" + // notConstant |= targetTypeName is "nuint" or "UIntPtr" && initExpr is IntegerLiteral { Value: < 0 or > uint.MaxValue }; + // notConstant |= targetTypeName is "nint" or "IntPtr" && initExpr is IntegerLiteral { Value: < int.MinValue or > int.MaxValue }; + + // TODO: This condition is looser than it should be, but avoids causing existing tests to fail. This might be the best we can do without proper expression value evaluation. + notConstant |= targetTypeName is "nuint" or "UIntPtr" && initExpr is IntegerLiteral { Value: < 0 or > uint.MaxValue }; + notConstant |= targetTypeName is "nint" or "IntPtr" && initExpr is IntegerLiteral { Value: < int.MinValue or > uint.MaxValue }; + + return !notConstant; } case CX_StmtClass_LambdaExpr: From 95e6f87379c368e3c86ff55df2244ad7a813539a Mon Sep 17 00:00:00 2001 From: William Chen Date: Thu, 2 Jul 2026 06:34:53 -0400 Subject: [PATCH 08/19] Cleanup my comments --- .../PInvokeGenerator.VisitDecl.cs | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.VisitDecl.cs b/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.VisitDecl.cs index 5977b39c..d5d8f4fc 100644 --- a/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.VisitDecl.cs +++ b/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.VisitDecl.cs @@ -3983,21 +3983,23 @@ private bool IsConstant(string targetTypeName, Expr initExpr) { var notConstant = false; - // TODO: This is incorrect because C#'s compiler only checks for the range when casting from a const expr ulong/long to a nuint/nint. - // TODO: The ulong/long literal itself is otherwise constant. Eg: const nuint N = (nuint)(4294967296 - 10) compiles just fine. - // TODO: This means we need to track the value of the const expr, which might require a decent amount of work and restructuring - // Constants for native integers must be in range: https://github.com/dotnet/csharplang/blob/main/proposals/csharp-9.0/native-integers.md - - // TODO: This condition is stricter than it should be. - // TODO: Apparently unchecked operations involving a source expression of any 32-bit value is fine. See constant folding in the native-integers proposal. - // TODO: Eg: nuint = unchecked(4294967295 + 10) is fine. Eg: nint = unchecked((nint)4294967295) is also fine. + // Constant expressions for native integers must be in range: https://github.com/dotnet/csharplang/blob/main/proposals/csharp-9.0/native-integers.md + + // TODO: C#'s compiler only checks for the range when outputting as a nuint/nint. See constant folding in the native-integers proposal. + // These compile fine as const fields: + // Eg: nuint = (nuint)(4294967296 - 10) // The integer literal itself is constant + // Eg: nuint = unchecked(4294967295 + 10) + // Eg: nint = unchecked((nint)4294967295) + // TODO: This means for proper analysis we need to evaluate the value of the expression, which seems out of scope + + // TODO: This condition leads to more change in output than ideal due to the behavior described above. // TODO: This causes existing tests to fail. Eg: ClangSharp.UnitTests.VarDeclarationTest.UncheckedConversionMacroTest. "const nint MyMacro1 = unchecked((nint)(0x80000000))" incorrectly becomes "static readonly nint MyMacro1 = unchecked((nint)(0x80000000))" // notConstant |= targetTypeName is "nuint" or "UIntPtr" && initExpr is IntegerLiteral { Value: < 0 or > uint.MaxValue }; // notConstant |= targetTypeName is "nint" or "IntPtr" && initExpr is IntegerLiteral { Value: < int.MinValue or > int.MaxValue }; - // TODO: This condition is looser than it should be, but avoids causing existing tests to fail. This might be the best we can do without proper expression value evaluation. + // TODO: This condition leads to less change in output than ideal, but avoids incorrectly failing existing tests. This might be the best we can do without proper expression value evaluation. notConstant |= targetTypeName is "nuint" or "UIntPtr" && initExpr is IntegerLiteral { Value: < 0 or > uint.MaxValue }; - notConstant |= targetTypeName is "nint" or "IntPtr" && initExpr is IntegerLiteral { Value: < int.MinValue or > uint.MaxValue }; + notConstant |= targetTypeName is "nint" or "IntPtr" && initExpr is IntegerLiteral { Value: < int.MinValue or > uint.MaxValue }; // Note the uint.MaxValue here return !notConstant; } From c466ee1e23e93eccc1b4bbbfad984ac300d8e4a0 Mon Sep 17 00:00:00 2001 From: William Chen Date: Thu, 2 Jul 2026 13:50:35 -0400 Subject: [PATCH 09/19] Update expected output --- tests/ClangSharp.PInvokeGenerator.UnitTests/CTest.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/ClangSharp.PInvokeGenerator.UnitTests/CTest.cs b/tests/ClangSharp.PInvokeGenerator.UnitTests/CTest.cs index 664daf28..15ddbab1 100644 --- a/tests/ClangSharp.PInvokeGenerator.UnitTests/CTest.cs +++ b/tests/ClangSharp.PInvokeGenerator.UnitTests/CTest.cs @@ -1027,13 +1027,13 @@ public Task CLongDefinesTestUnix() public static partial class Methods { [NativeTypeName(""#define SIZE_MAX (18446744073709551615UL)"")] - public static readonly nuint SIZE_MAX = (nuint)(18446744073709551615U); + public static readonly nuint SIZE_MAX = unchecked((nuint)(18446744073709551615U)); [NativeTypeName(""#define CL_IMPORT_MEMORY_WHOLE_ALLOCATION_ARM SIZE_MAX"")] - public static readonly nuint CL_IMPORT_MEMORY_WHOLE_ALLOCATION_ARM = (nuint)(18446744073709551615U); + public static readonly nuint CL_IMPORT_MEMORY_WHOLE_ALLOCATION_ARM = unchecked((nuint)(18446744073709551615U)); [NativeTypeName(""#define LONG_MAX __LONG_MAX__"")] - public static readonly nint LONG_MAX = (nint)(9223372036854775807); + public static readonly nint LONG_MAX = unchecked((nint)(9223372036854775807)); [NativeTypeName(""#define ULONG_MAX (__LONG_MAX__ *2UL+1UL)"")] public static readonly nuint ULONG_MAX = unchecked((nuint)(9223372036854775807 * 2U + 1U)); From 8451745052e01482c8c722d8ea0f551579bf95c8 Mon Sep 17 00:00:00 2001 From: William Chen Date: Thu, 2 Jul 2026 15:03:17 -0400 Subject: [PATCH 10/19] Get all tests to pass Will keep investigating the conditions a bit more since I'm not quite convinced this is optimal. --- .../PInvokeGenerator.VisitDecl.cs | 4 ++-- .../PInvokeGenerator.cs | 14 ++++++++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.VisitDecl.cs b/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.VisitDecl.cs index d5d8f4fc..e2b3b16d 100644 --- a/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.VisitDecl.cs +++ b/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.VisitDecl.cs @@ -3994,11 +3994,11 @@ private bool IsConstant(string targetTypeName, Expr initExpr) // TODO: This condition leads to more change in output than ideal due to the behavior described above. // TODO: This causes existing tests to fail. Eg: ClangSharp.UnitTests.VarDeclarationTest.UncheckedConversionMacroTest. "const nint MyMacro1 = unchecked((nint)(0x80000000))" incorrectly becomes "static readonly nint MyMacro1 = unchecked((nint)(0x80000000))" - // notConstant |= targetTypeName is "nuint" or "UIntPtr" && initExpr is IntegerLiteral { Value: < 0 or > uint.MaxValue }; + // notConstant |= targetTypeName is "nuint" or "UIntPtr" && initExpr is IntegerLiteral { UnsignedValue: > uint.MaxValue }; // notConstant |= targetTypeName is "nint" or "IntPtr" && initExpr is IntegerLiteral { Value: < int.MinValue or > int.MaxValue }; // TODO: This condition leads to less change in output than ideal, but avoids incorrectly failing existing tests. This might be the best we can do without proper expression value evaluation. - notConstant |= targetTypeName is "nuint" or "UIntPtr" && initExpr is IntegerLiteral { Value: < 0 or > uint.MaxValue }; + notConstant |= targetTypeName is "nuint" or "UIntPtr" && initExpr is IntegerLiteral { UnsignedValue: > uint.MaxValue }; notConstant |= targetTypeName is "nint" or "IntPtr" && initExpr is IntegerLiteral { Value: < int.MinValue or > uint.MaxValue }; // Note the uint.MaxValue here return !notConstant; diff --git a/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.cs b/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.cs index a96353b2..84575c78 100644 --- a/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.cs +++ b/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.cs @@ -5847,6 +5847,13 @@ private bool IsUnchecked(string targetTypeName, Stmt stmt) { var integerLiteral = (IntegerLiteral)stmt; var signedValue = integerLiteral.Value; + + if ((targetTypeName is "nuint" or "UIntPtr" && integerLiteral is { UnsignedValue: > uint.MaxValue }) + || (targetTypeName is "nint" or "IntPtr" && integerLiteral is { Value: < int.MinValue or > int.MaxValue })) + { + return true; + } + return IsUnchecked(targetTypeName, signedValue, integerLiteral.IsNegative, isHex: integerLiteral.ValueString.StartsWith("0x", StringComparison.Ordinal)); } @@ -6780,6 +6787,13 @@ private void UncheckStmt(string targetTypeName, Stmt stmt) } } + if (IsPrevContextDecl(out _, out _) + && ((targetTypeName is "nuint" or "UIntPtr" && stmt.Handle.Evaluate.AsUnsigned > uint.MaxValue) + || (targetTypeName is "nint" or "IntPtr" && stmt.Handle.Evaluate.AsLongLong is < int.MinValue or > uint.MaxValue))) + { + needsCast = true; + } + if (needsCast) { _outputBuilder.BeginInnerValue(); From 9b20e734a3eea8c316b85c30c65f0af08365bd34 Mon Sep 17 00:00:00 2001 From: William Chen Date: Sun, 5 Jul 2026 17:31:08 -0400 Subject: [PATCH 11/19] Add IntptrDefineTest --- .../CTest.cs | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/tests/ClangSharp.PInvokeGenerator.UnitTests/CTest.cs b/tests/ClangSharp.PInvokeGenerator.UnitTests/CTest.cs index 15ddbab1..0c2149ef 100644 --- a/tests/ClangSharp.PInvokeGenerator.UnitTests/CTest.cs +++ b/tests/ClangSharp.PInvokeGenerator.UnitTests/CTest.cs @@ -1083,4 +1083,51 @@ public static partial class Methods return ValidateGeneratedCSharpLatestWindowsBindingsAsync(inputContents, expectedOutputContents, commandLineArgs: DefaultCClangCommandLineArgs, language: "c", languageStandard: DefaultCStandard); } + + [Test] + public Task IntptrDefineTest() + { + string inputContents; + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + // These values are taken from the Windows headers when using MSVC + inputContents = @" +// vcruntime.h +typedef __int64 intptr_t; + +// cl_ext.h from OpenCL +#define CL_ICD2_TAG_KHR ((intptr_t)0x4F50454E434C3331) +"; + } + else + { + // These values are taken from the Linux headers when using Clang + inputContents = @" +// stdint.h +typedef long int intptr_t; + +// cl_ext.h from OpenCL +#define CL_ICD2_TAG_KHR ((intptr_t)0x4F50454E434C3331) +"; + } + + var expectedOutputContents = @"namespace ClangSharp.Test +{ + public static partial class Methods + { + [NativeTypeName(""#define CL_ICD2_TAG_KHR ((intptr_t)0x4F50454E434C3331)"")] + public static readonly nint CL_ICD2_TAG_KHR = unchecked((nint)((nint)(0x4F50454E434C3331))); + } +} +"; + + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + return ValidateGeneratedCSharpLatestWindowsBindingsAsync(inputContents, expectedOutputContents, commandLineArgs: DefaultCClangCommandLineArgs, language: "c", languageStandard: DefaultCStandard); + } + else + { + return ValidateGeneratedCSharpLatestUnixBindingsAsync(inputContents, expectedOutputContents, commandLineArgs: DefaultCClangCommandLineArgs, language: "c", languageStandard: DefaultCStandard); + } + } } From 782f33c63ba71d0946f52de8cc223252cacf98c0 Mon Sep 17 00:00:00 2001 From: William Chen Date: Sun, 5 Jul 2026 18:31:37 -0400 Subject: [PATCH 12/19] Add additional test cases for CLongDefinesTestUnix --- .../CTest.cs | 28 +++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/tests/ClangSharp.PInvokeGenerator.UnitTests/CTest.cs b/tests/ClangSharp.PInvokeGenerator.UnitTests/CTest.cs index 0c2149ef..1a91dc8c 100644 --- a/tests/ClangSharp.PInvokeGenerator.UnitTests/CTest.cs +++ b/tests/ClangSharp.PInvokeGenerator.UnitTests/CTest.cs @@ -1004,7 +1004,7 @@ readonly get } [Test] - [Platform("unix")] // This test has slight platform-specific differences (on Windows, __LONG_MAX__ is 32-bit) + [Platform("unix")] // This test has platform-specific differences (on Windows, __LONG_MAX__ is 32-bit) public Task CLongDefinesTestUnix() { // C longs differ based on platform @@ -1019,6 +1019,15 @@ public Task CLongDefinesTestUnix() // limits.h #define LONG_MAX __LONG_MAX__ #define ULONG_MAX (__LONG_MAX__ *2UL+1UL) + +// These expressions never exceed the allowed range, so should remain const +#define CONST_IN_RANGE_S1 ((long)2147483647) +#define CONST_IN_RANGE_U1 ((unsigned long)4294967295) + +// These expressions exceed the allowed range, so should become static readonly +#define READONLY_OUT_OF_RANGE_S1 ((long)4294967295) +#define READONLY_OUT_OF_RANGE_S2 ((long)4294967296) +#define READONLY_OUT_OF_RANGE_U1 ((unsigned long)4294967296) "; // We use "static readonly" instead of "const" because nint/nuint differ on 32/64-bit platforms @@ -1037,6 +1046,21 @@ public static partial class Methods [NativeTypeName(""#define ULONG_MAX (__LONG_MAX__ *2UL+1UL)"")] public static readonly nuint ULONG_MAX = unchecked((nuint)(9223372036854775807 * 2U + 1U)); + + [NativeTypeName(""#define CONST_IN_RANGE_S1 ((long)2147483647)"")] + public const nint CONST_IN_RANGE_S1 = ((nint)(2147483647)); + + [NativeTypeName(""#define CONST_IN_RANGE_U1 ((unsigned long)4294967295)"")] + public const nuint CONST_IN_RANGE_U1 = ((nuint)(4294967295)); + + [NativeTypeName(""#define READONLY_OUT_OF_RANGE_S1 ((long)4294967295)"")] + public static readonly nint READONLY_OUT_OF_RANGE_S1 = unchecked((nint)(4294967295)); + + [NativeTypeName(""#define READONLY_OUT_OF_RANGE_S2 ((long)4294967296)"")] + public static readonly nint READONLY_OUT_OF_RANGE_S2 = unchecked((nint)((nint)(4294967296))); + + [NativeTypeName(""#define READONLY_OUT_OF_RANGE_U1 ((unsigned long)4294967296)"")] + public static readonly nuint READONLY_OUT_OF_RANGE_U1 = unchecked((nuint)((nuint)(4294967296))); } } "; @@ -1045,7 +1069,7 @@ public static partial class Methods } [Test] - [Platform("win")] // This test has slight platform-specific differences + [Platform("win")] // This test has platform-specific differences public Task CLongDefinesTestWindows() { // C longs differ based on platform From 1f4419b17f397446b07b08f757af7c01f5b6cdb8 Mon Sep 17 00:00:00 2001 From: William Chen Date: Sun, 5 Jul 2026 18:46:21 -0400 Subject: [PATCH 13/19] Fix existing UncheckedConversionMacroTest cases These apparently error during compile time, but not edit time. I.e., there's no red underline displayed in the editor, but will fail with "The expression being assigned to 'MyMacro1' must be constant". --- .../CSharpCompatibleUnix/VarDeclarationTest.cs | 2 +- .../CSharpDefaultUnix/VarDeclarationTest.cs | 2 +- .../CSharpLatestUnix/VarDeclarationTest.cs | 2 +- .../CSharpPreviewUnix/VarDeclarationTest.cs | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/ClangSharp.PInvokeGenerator.UnitTests/CSharpCompatibleUnix/VarDeclarationTest.cs b/tests/ClangSharp.PInvokeGenerator.UnitTests/CSharpCompatibleUnix/VarDeclarationTest.cs index a068c33f..5bfb7be2 100644 --- a/tests/ClangSharp.PInvokeGenerator.UnitTests/CSharpCompatibleUnix/VarDeclarationTest.cs +++ b/tests/ClangSharp.PInvokeGenerator.UnitTests/CSharpCompatibleUnix/VarDeclarationTest.cs @@ -267,7 +267,7 @@ namespace ClangSharp.Test public static partial class Methods {{ [NativeTypeName(""#define MyMacro1 (long)0x80000000L"")] - public const IntPtr MyMacro1 = unchecked((nint)(0x80000000)); + public static readonly IntPtr MyMacro1 = unchecked((nint)(0x80000000)); [NativeTypeName(""#define MyMacro2 (int)0x80000000"")] public const int MyMacro2 = unchecked((int)(0x80000000)); diff --git a/tests/ClangSharp.PInvokeGenerator.UnitTests/CSharpDefaultUnix/VarDeclarationTest.cs b/tests/ClangSharp.PInvokeGenerator.UnitTests/CSharpDefaultUnix/VarDeclarationTest.cs index 3ec691d8..4b06c22c 100644 --- a/tests/ClangSharp.PInvokeGenerator.UnitTests/CSharpDefaultUnix/VarDeclarationTest.cs +++ b/tests/ClangSharp.PInvokeGenerator.UnitTests/CSharpDefaultUnix/VarDeclarationTest.cs @@ -268,7 +268,7 @@ protected override Task UncheckedConversionMacroTestImpl() public static partial class Methods {{ [NativeTypeName(""#define MyMacro1 (long)0x80000000L"")] - public const nint MyMacro1 = unchecked((nint)(0x80000000)); + public static readonly nint MyMacro1 = unchecked((nint)(0x80000000)); [NativeTypeName(""#define MyMacro2 (int)0x80000000"")] public const int MyMacro2 = unchecked((int)(0x80000000)); diff --git a/tests/ClangSharp.PInvokeGenerator.UnitTests/CSharpLatestUnix/VarDeclarationTest.cs b/tests/ClangSharp.PInvokeGenerator.UnitTests/CSharpLatestUnix/VarDeclarationTest.cs index c72f00c1..09a0fc74 100644 --- a/tests/ClangSharp.PInvokeGenerator.UnitTests/CSharpLatestUnix/VarDeclarationTest.cs +++ b/tests/ClangSharp.PInvokeGenerator.UnitTests/CSharpLatestUnix/VarDeclarationTest.cs @@ -268,7 +268,7 @@ protected override Task UncheckedConversionMacroTestImpl() public static partial class Methods {{ [NativeTypeName(""#define MyMacro1 (long)0x80000000L"")] - public const nint MyMacro1 = unchecked((nint)(0x80000000)); + public static readonly nint MyMacro1 = unchecked((nint)(0x80000000)); [NativeTypeName(""#define MyMacro2 (int)0x80000000"")] public const int MyMacro2 = unchecked((int)(0x80000000)); diff --git a/tests/ClangSharp.PInvokeGenerator.UnitTests/CSharpPreviewUnix/VarDeclarationTest.cs b/tests/ClangSharp.PInvokeGenerator.UnitTests/CSharpPreviewUnix/VarDeclarationTest.cs index 0ab01dfb..1bad9672 100644 --- a/tests/ClangSharp.PInvokeGenerator.UnitTests/CSharpPreviewUnix/VarDeclarationTest.cs +++ b/tests/ClangSharp.PInvokeGenerator.UnitTests/CSharpPreviewUnix/VarDeclarationTest.cs @@ -270,7 +270,7 @@ protected override Task UncheckedConversionMacroTestImpl() public static partial class Methods {{ [NativeTypeName(""#define MyMacro1 (long)0x80000000L"")] - public const nint MyMacro1 = unchecked((nint)(0x80000000)); + public static readonly nint MyMacro1 = unchecked((nint)(0x80000000)); [NativeTypeName(""#define MyMacro2 (int)0x80000000"")] public const int MyMacro2 = unchecked((int)(0x80000000)); From 80d4e10c644eb8e8990c686df4d686d99623c5fc Mon Sep 17 00:00:00 2001 From: William Chen Date: Sun, 5 Jul 2026 19:12:07 -0400 Subject: [PATCH 14/19] Widen IsConstant check and fix incorrect ranges used Looks like the ranges are consistent, but I didn't realize since the existing UncheckedConversionMacroTest cases were wrong (see previous commit). --- .../PInvokeGenerator.VisitDecl.cs | 30 ++++++------------- .../PInvokeGenerator.cs | 4 ++- 2 files changed, 12 insertions(+), 22 deletions(-) diff --git a/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.VisitDecl.cs b/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.VisitDecl.cs index e2b3b16d..14b9b5e1 100644 --- a/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.VisitDecl.cs +++ b/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.VisitDecl.cs @@ -3761,6 +3761,14 @@ void ForDeclStmt(VarDecl varDecl, DeclStmt declStmt) private bool IsConstant(string targetTypeName, Expr initExpr) { + // Constant expressions for native integers must be in range of the corresponding 32-bit integer type + // Also see: https://github.com/dotnet/csharplang/blob/main/proposals/csharp-9.0/native-integers.md + if ((targetTypeName is "nuint" or "UIntPtr" && initExpr.Handle.Evaluate is { Kind: CXEval_Int, AsUnsigned: > uint.MaxValue }) + || (targetTypeName is "nint" or "IntPtr" && initExpr.Handle.Evaluate is { Kind: CXEval_Int, AsLongLong: < int.MinValue or > int.MaxValue })) + { + return false; + } + if (IsTypePointerOrReference(initExpr) && !targetTypeName.Equals("string", StringComparison.Ordinal)) { return false; @@ -3981,27 +3989,7 @@ private bool IsConstant(string targetTypeName, Expr initExpr) case CX_StmtClass_IntegerLiteral: { - var notConstant = false; - - // Constant expressions for native integers must be in range: https://github.com/dotnet/csharplang/blob/main/proposals/csharp-9.0/native-integers.md - - // TODO: C#'s compiler only checks for the range when outputting as a nuint/nint. See constant folding in the native-integers proposal. - // These compile fine as const fields: - // Eg: nuint = (nuint)(4294967296 - 10) // The integer literal itself is constant - // Eg: nuint = unchecked(4294967295 + 10) - // Eg: nint = unchecked((nint)4294967295) - // TODO: This means for proper analysis we need to evaluate the value of the expression, which seems out of scope - - // TODO: This condition leads to more change in output than ideal due to the behavior described above. - // TODO: This causes existing tests to fail. Eg: ClangSharp.UnitTests.VarDeclarationTest.UncheckedConversionMacroTest. "const nint MyMacro1 = unchecked((nint)(0x80000000))" incorrectly becomes "static readonly nint MyMacro1 = unchecked((nint)(0x80000000))" - // notConstant |= targetTypeName is "nuint" or "UIntPtr" && initExpr is IntegerLiteral { UnsignedValue: > uint.MaxValue }; - // notConstant |= targetTypeName is "nint" or "IntPtr" && initExpr is IntegerLiteral { Value: < int.MinValue or > int.MaxValue }; - - // TODO: This condition leads to less change in output than ideal, but avoids incorrectly failing existing tests. This might be the best we can do without proper expression value evaluation. - notConstant |= targetTypeName is "nuint" or "UIntPtr" && initExpr is IntegerLiteral { UnsignedValue: > uint.MaxValue }; - notConstant |= targetTypeName is "nint" or "IntPtr" && initExpr is IntegerLiteral { Value: < int.MinValue or > uint.MaxValue }; // Note the uint.MaxValue here - - return !notConstant; + return true; } case CX_StmtClass_LambdaExpr: diff --git a/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.cs b/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.cs index 84575c78..97a0889e 100644 --- a/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.cs +++ b/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.cs @@ -5848,6 +5848,7 @@ private bool IsUnchecked(string targetTypeName, Stmt stmt) var integerLiteral = (IntegerLiteral)stmt; var signedValue = integerLiteral.Value; + // Casts to native integers may overflow if out of range of corresponding 32-bit integer type if ((targetTypeName is "nuint" or "UIntPtr" && integerLiteral is { UnsignedValue: > uint.MaxValue }) || (targetTypeName is "nint" or "IntPtr" && integerLiteral is { Value: < int.MinValue or > int.MaxValue })) { @@ -6787,9 +6788,10 @@ private void UncheckStmt(string targetTypeName, Stmt stmt) } } + // Need to cast to output type if out of range of corresponding 32-bit integer type if (IsPrevContextDecl(out _, out _) && ((targetTypeName is "nuint" or "UIntPtr" && stmt.Handle.Evaluate.AsUnsigned > uint.MaxValue) - || (targetTypeName is "nint" or "IntPtr" && stmt.Handle.Evaluate.AsLongLong is < int.MinValue or > uint.MaxValue))) + || (targetTypeName is "nint" or "IntPtr" && stmt.Handle.Evaluate.AsLongLong is < int.MinValue or > int.MaxValue))) { needsCast = true; } From a0e948d976a8d1a37ae49604136300532320c0df Mon Sep 17 00:00:00 2001 From: William Chen Date: Sun, 5 Jul 2026 19:26:00 -0400 Subject: [PATCH 15/19] Avoid duplicate cast when the native code already casts to the output type --- sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.cs | 2 +- tests/ClangSharp.PInvokeGenerator.UnitTests/CTest.cs | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.cs b/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.cs index 97a0889e..8e17740f 100644 --- a/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.cs +++ b/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.cs @@ -6789,7 +6789,7 @@ private void UncheckStmt(string targetTypeName, Stmt stmt) } // Need to cast to output type if out of range of corresponding 32-bit integer type - if (IsPrevContextDecl(out _, out _) + if (IsPrevContextDecl(out _, out _) && !IsStmtAsWritten(stmt, out _, removeParens: true) && ((targetTypeName is "nuint" or "UIntPtr" && stmt.Handle.Evaluate.AsUnsigned > uint.MaxValue) || (targetTypeName is "nint" or "IntPtr" && stmt.Handle.Evaluate.AsLongLong is < int.MinValue or > int.MaxValue))) { diff --git a/tests/ClangSharp.PInvokeGenerator.UnitTests/CTest.cs b/tests/ClangSharp.PInvokeGenerator.UnitTests/CTest.cs index 1a91dc8c..bd17103a 100644 --- a/tests/ClangSharp.PInvokeGenerator.UnitTests/CTest.cs +++ b/tests/ClangSharp.PInvokeGenerator.UnitTests/CTest.cs @@ -1057,10 +1057,10 @@ public static partial class Methods public static readonly nint READONLY_OUT_OF_RANGE_S1 = unchecked((nint)(4294967295)); [NativeTypeName(""#define READONLY_OUT_OF_RANGE_S2 ((long)4294967296)"")] - public static readonly nint READONLY_OUT_OF_RANGE_S2 = unchecked((nint)((nint)(4294967296))); + public static readonly nint READONLY_OUT_OF_RANGE_S2 = unchecked((nint)(4294967296)); [NativeTypeName(""#define READONLY_OUT_OF_RANGE_U1 ((unsigned long)4294967296)"")] - public static readonly nuint READONLY_OUT_OF_RANGE_U1 = unchecked((nuint)((nuint)(4294967296))); + public static readonly nuint READONLY_OUT_OF_RANGE_U1 = unchecked((nuint)(4294967296)); } } "; @@ -1140,7 +1140,7 @@ public Task IntptrDefineTest() public static partial class Methods { [NativeTypeName(""#define CL_ICD2_TAG_KHR ((intptr_t)0x4F50454E434C3331)"")] - public static readonly nint CL_ICD2_TAG_KHR = unchecked((nint)((nint)(0x4F50454E434C3331))); + public static readonly nint CL_ICD2_TAG_KHR = unchecked((nint)(0x4F50454E434C3331)); } } "; From 070ada1d069653f77ffa3b815377640f9fdbbf56 Mon Sep 17 00:00:00 2001 From: William Chen Date: Sun, 5 Jul 2026 19:30:53 -0400 Subject: [PATCH 16/19] Edit wording --- sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.cs b/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.cs index 8e17740f..d07775e8 100644 --- a/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.cs +++ b/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.cs @@ -6788,7 +6788,7 @@ private void UncheckStmt(string targetTypeName, Stmt stmt) } } - // Need to cast to output type if out of range of corresponding 32-bit integer type + // Cast to output type if out of range of corresponding 32-bit integer type if (IsPrevContextDecl(out _, out _) && !IsStmtAsWritten(stmt, out _, removeParens: true) && ((targetTypeName is "nuint" or "UIntPtr" && stmt.Handle.Evaluate.AsUnsigned > uint.MaxValue) || (targetTypeName is "nint" or "IntPtr" && stmt.Handle.Evaluate.AsLongLong is < int.MinValue or > int.MaxValue))) From ff0d430cd22eeb4c3b1f54722e48b8411acc9fee Mon Sep 17 00:00:00 2001 From: William Chen Date: Sun, 5 Jul 2026 20:04:21 -0400 Subject: [PATCH 17/19] Also check against U/IntPtr when checking against nint/nuint typeNameBacking eventually calls into GetTypeName, which can return either the keyword or the underlying type name depending on the generator configuration, so I believe this change is correct. --- .../PInvokeGenerator.VisitDecl.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.VisitDecl.cs b/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.VisitDecl.cs index 14b9b5e1..77382cdd 100644 --- a/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.VisitDecl.cs +++ b/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.VisitDecl.cs @@ -2587,7 +2587,7 @@ void VisitBitfieldDecl(FieldDecl fieldDecl, BitfieldDesc[] bitfieldDescs, Record case CXType_ULongLong: { - if (typeNameBacking.Equals("nuint", StringComparison.Ordinal)) // TODO: Shouldn't this also check UIntPtr? + if (typeNameBacking is "nuint" or "UIntPtr") { goto case CXType_UInt; } @@ -2620,7 +2620,7 @@ void VisitBitfieldDecl(FieldDecl fieldDecl, BitfieldDesc[] bitfieldDescs, Record { isTypeBackingSigned = true; - if (typeNameBacking.Equals("nint", StringComparison.Ordinal)) // TODO: Shouldn't this also check IntPtr? + if (typeNameBacking is "nint" or "IntPtr") { goto case CXType_Int; } @@ -2686,7 +2686,7 @@ void VisitBitfieldDecl(FieldDecl fieldDecl, BitfieldDesc[] bitfieldDescs, Record case CXType_ULongLong: { - if (typeNameBacking.Equals("nuint", StringComparison.Ordinal)) // TODO: Shouldn't this also check UIntPtr? + if (typeNameBacking is "nuint" or "UIntPtr") { goto case CXType_UInt; } @@ -2719,7 +2719,7 @@ void VisitBitfieldDecl(FieldDecl fieldDecl, BitfieldDesc[] bitfieldDescs, Record { isTypeSigned = true; - if (typeNameBacking.Equals("nint", StringComparison.Ordinal)) // TODO: Shouldn't this also check IntPtr? + if (typeNameBacking is "nint" or "IntPtr") { goto case CXType_Int; } From 6c16210cafff781761e671c76598560b3606693d Mon Sep 17 00:00:00 2001 From: William Chen Date: Mon, 6 Jul 2026 19:20:35 -0400 Subject: [PATCH 18/19] Add CLongDefinesRegressionTestUnix --- .../CTest.cs | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/tests/ClangSharp.PInvokeGenerator.UnitTests/CTest.cs b/tests/ClangSharp.PInvokeGenerator.UnitTests/CTest.cs index bd17103a..6d42855b 100644 --- a/tests/ClangSharp.PInvokeGenerator.UnitTests/CTest.cs +++ b/tests/ClangSharp.PInvokeGenerator.UnitTests/CTest.cs @@ -1068,6 +1068,63 @@ public static partial class Methods return ValidateGeneratedCSharpLatestUnixBindingsAsync(inputContents, expectedOutputContents, commandLineArgs: DefaultCClangCommandLineArgs, language: "c", languageStandard: DefaultCStandard); } + [Test] + [Platform("unix")] + public Task CLongDefinesRegressionTestUnix() + { + // This test is to catch a potential regression when changing how native integers are handled + // Specifically, (18446744073709551615U) became unchecked(18446744073709551615U) + + // Macro values are taken from the Linux headers when using Clang + // Some values are substituted for simplicity + var inputContents = @" +// __stddef_size_t.h +typedef __SIZE_TYPE__ size_t; + +// stdbool.h +#define bool _Bool +#define true 1 +#define false 0 + +// SDL_stdinc.h from SDL3 +bool SDL_size_add_check_overflow(size_t a, size_t b, size_t *ret) +{ + if (b > (18446744073709551615UL) - a) { + return false; + } + *ret = a + b; + return true; +} +"; + + var expectedOutputContents = @"namespace ClangSharp.Test +{ + public static unsafe partial class Methods + { + [return: NativeTypeName(""_Bool"")] + public static bool SDL_size_add_check_overflow([NativeTypeName(""size_t"")] nuint a, [NativeTypeName(""size_t"")] nuint b, [NativeTypeName(""size_t *"")] nuint* ret) + { + if (b > (18446744073709551615U) - a) + { + return (0) != 0; + } + + *ret = a + b; + return (1) != 0; + } + + [NativeTypeName(""#define true 1"")] + public const int @true = 1; + + [NativeTypeName(""#define false 0"")] + public const int @false = 0; + } +} +"; + + return ValidateGeneratedCSharpLatestUnixBindingsAsync(inputContents, expectedOutputContents, commandLineArgs: DefaultCClangCommandLineArgs, language: "c", languageStandard: DefaultCStandard); + } + [Test] [Platform("win")] // This test has platform-specific differences public Task CLongDefinesTestWindows() From 55498f6f585edb93869f3c0954223247c8245203 Mon Sep 17 00:00:00 2001 From: William Chen Date: Mon, 6 Jul 2026 19:42:43 -0400 Subject: [PATCH 19/19] Move nuint/nint range check to be with the rest of the IsUnchecked range checks Didn't realize that IsUnchecked were effectively two different methods with the same name rather than just a simple overload. --- .../PInvokeGenerator.cs | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.cs b/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.cs index d07775e8..f543cecf 100644 --- a/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.cs +++ b/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.cs @@ -5847,14 +5847,6 @@ private bool IsUnchecked(string targetTypeName, Stmt stmt) { var integerLiteral = (IntegerLiteral)stmt; var signedValue = integerLiteral.Value; - - // Casts to native integers may overflow if out of range of corresponding 32-bit integer type - if ((targetTypeName is "nuint" or "UIntPtr" && integerLiteral is { UnsignedValue: > uint.MaxValue }) - || (targetTypeName is "nint" or "IntPtr" && integerLiteral is { Value: < int.MinValue or > int.MaxValue })) - { - return true; - } - return IsUnchecked(targetTypeName, signedValue, integerLiteral.IsNegative, isHex: integerLiteral.ValueString.StartsWith("0x", StringComparison.Ordinal)); } @@ -6163,10 +6155,15 @@ private static bool IsUnchecked(string typeName, long signedValue, bool isNegati case "uint": case "UInt32": + { + return false; + } + case "nuint": case "UIntPtr": { - return false; + var unsignedValue = unchecked((ulong)signedValue); + return unsignedValue is < uint.MinValue or > uint.MaxValue; } case "ulong": @@ -6788,7 +6785,7 @@ private void UncheckStmt(string targetTypeName, Stmt stmt) } } - // Cast to output type if out of range of corresponding 32-bit integer type + // Cast to output type if out of range of the corresponding 32-bit integer type if (IsPrevContextDecl(out _, out _) && !IsStmtAsWritten(stmt, out _, removeParens: true) && ((targetTypeName is "nuint" or "UIntPtr" && stmt.Handle.Evaluate.AsUnsigned > uint.MaxValue) || (targetTypeName is "nint" or "IntPtr" && stmt.Handle.Evaluate.AsLongLong is < int.MinValue or > int.MaxValue)))