diff --git a/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.VisitDecl.cs b/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.VisitDecl.cs index bd29843b..e2b3b16d 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,27 @@ private bool IsConstant(string targetTypeName, Expr initExpr) case CX_StmtClass_IntegerLiteral: { - return true; + 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; } case CX_StmtClass_LambdaExpr: 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(); diff --git a/tests/ClangSharp.PInvokeGenerator.UnitTests/CTest.cs b/tests/ClangSharp.PInvokeGenerator.UnitTests/CTest.cs index 22d264a7..15ddbab1 100644 --- a/tests/ClangSharp.PInvokeGenerator.UnitTests/CTest.cs +++ b/tests/ClangSharp.PInvokeGenerator.UnitTests/CTest.cs @@ -1002,4 +1002,85 @@ readonly get { "Flags", "Flags" } }); } + + [Test] + [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 + // These values are taken from the Linux headers when using Clang + 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) +"; + + // 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 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 = unchecked((nuint)(18446744073709551615U)); + + [NativeTypeName(""#define LONG_MAX __LONG_MAX__"")] + 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)); + } +} +"; + + 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 Windows headers when using MSVC + var inputContents = @" +// 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 2147483647L +#define ULONG_MAX 0xffffffffUL +"; + + var expectedOutputContents = @"namespace ClangSharp.Test +{ + public static partial class Methods + { + [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 = 0xffffffffffffffffUL; + + [NativeTypeName(""#define LONG_MAX 2147483647L"")] + public const int LONG_MAX = 2147483647; + + [NativeTypeName(""#define ULONG_MAX 0xffffffffUL"")] + public const uint ULONG_MAX = 0xffffffffU; + } +} +"; + + return ValidateGeneratedCSharpLatestWindowsBindingsAsync(inputContents, expectedOutputContents, commandLineArgs: DefaultCClangCommandLineArgs, language: "c", languageStandard: DefaultCStandard); + } }