Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
1b734e7
Add LongDefinesTest with expected set to incorrect, current output
Exanite Jun 28, 2026
7a2fa90
Set expected output
Exanite Jun 28, 2026
e0a33b5
Adjust and rename CLongDefinesTestUnix
Exanite Jun 29, 2026
415ed4e
Define what the probably correct output looks like
Exanite Jun 29, 2026
9fdd364
Adjust tests where needed
Exanite Jun 29, 2026
892a545
Also note down which compiler I took the values from
Exanite Jun 29, 2026
96a8dca
Implement potential partial fix and add todos containing my notes and…
Exanite Jul 2, 2026
95e6f87
Cleanup my comments
Exanite Jul 2, 2026
c466ee1
Update expected output
Exanite Jul 2, 2026
8451745
Get all tests to pass
Exanite Jul 2, 2026
9b20e73
Add IntptrDefineTest
Exanite Jul 5, 2026
782f33c
Add additional test cases for CLongDefinesTestUnix
Exanite Jul 5, 2026
1f4419b
Fix existing UncheckedConversionMacroTest cases
Exanite Jul 5, 2026
80d4e10
Widen IsConstant check and fix incorrect ranges used
Exanite Jul 5, 2026
a0e948d
Avoid duplicate cast when the native code already casts to the output…
Exanite Jul 5, 2026
070ada1
Edit wording
Exanite Jul 5, 2026
ff0d430
Also check against U/IntPtr when checking against nint/nuint
Exanite Jul 6, 2026
6c16210
Add CLongDefinesRegressionTestUnix
Exanite Jul 6, 2026
55498f6
Move nuint/nint range check to be with the rest of the IsUnchecked ra…
Exanite Jul 6, 2026
f0196a4
Merge remote-tracking branch 'dotnet/main' into fix/long-defines
Exanite Jul 12, 2026
b59de76
Reapply IsUnchecked change lost during merge
Exanite Jul 12, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -1454,10 +1454,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":
Expand Down
18 changes: 13 additions & 5 deletions sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.VisitDecl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2587,7 +2587,7 @@ void VisitBitfieldDecl(FieldDecl fieldDecl, BitfieldDesc[] bitfieldDescs, Record

case CXType_ULongLong:
{
if (typeNameBacking.Equals("nuint", StringComparison.Ordinal))
if (typeNameBacking is "nuint" or "UIntPtr")
Comment thread
tannergooding marked this conversation as resolved.
{
goto case CXType_UInt;
}
Expand Down Expand Up @@ -2620,7 +2620,7 @@ void VisitBitfieldDecl(FieldDecl fieldDecl, BitfieldDesc[] bitfieldDescs, Record
{
isTypeBackingSigned = true;

if (typeNameBacking.Equals("nint", StringComparison.Ordinal))
if (typeNameBacking is "nint" or "IntPtr")
{
goto case CXType_Int;
}
Expand Down Expand Up @@ -2686,7 +2686,7 @@ void VisitBitfieldDecl(FieldDecl fieldDecl, BitfieldDesc[] bitfieldDescs, Record

case CXType_ULongLong:
{
if (typeNameBacking.Equals("nuint", StringComparison.Ordinal))
if (typeNameBacking is "nuint" or "UIntPtr")
{
goto case CXType_UInt;
}
Expand Down Expand Up @@ -2719,7 +2719,7 @@ void VisitBitfieldDecl(FieldDecl fieldDecl, BitfieldDesc[] bitfieldDescs, Record
{
isTypeSigned = true;

if (typeNameBacking.Equals("nint", StringComparison.Ordinal))
if (typeNameBacking is "nint" or "IntPtr")
{
goto case CXType_Int;
}
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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;
Expand Down
8 changes: 8 additions & 0 deletions sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3359,6 +3359,14 @@ private void UncheckStmt(string targetTypeName, Stmt stmt)
}
}

// Cast to output type if out of range of the corresponding 32-bit integer type
if (IsPrevContextDecl<VarDecl>(out _, out _) && !IsStmtAsWritten<CastExpr>(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)))
{
needsCast = true;
}

if (needsCast)
{
_outputBuilder.BeginInnerValue();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -267,7 +267,7 @@ namespace ClangSharp.Test
public static partial class Methods
{{
[NativeTypeName(""#define MyMacro1 (long)0x80000000L"")]
public const IntPtr MyMacro1 = unchecked((nint)(0x80000000));
Comment thread
tannergooding marked this conversation as resolved.
public static readonly IntPtr MyMacro1 = unchecked((nint)(0x80000000));

[NativeTypeName(""#define MyMacro2 (int)0x80000000"")]
public const int MyMacro2 = unchecked((int)(0x80000000));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down
209 changes: 209 additions & 0 deletions tests/ClangSharp.PInvokeGenerator.UnitTests/CTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1002,4 +1002,213 @@ readonly get
{ "Flags", "Flags" }
});
}

[Test]
[Platform("unix")] // This test has 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)

// 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
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));

[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)(4294967296));

[NativeTypeName(""#define READONLY_OUT_OF_RANGE_U1 ((unsigned long)4294967296)"")]
public static readonly nuint READONLY_OUT_OF_RANGE_U1 = unchecked((nuint)(4294967296));
}
}
";

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()
{
// 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);
}

[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)(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);
}
}
}