Skip to content

Commit 4d4e062

Browse files
Merge pull request #612 from Exanite/fix/unsigned-int-bitshift
Fix compile error when generating code involving unsigned int bitshifts
2 parents 8dcd1d4 + e6d254c commit 4d4e062

File tree

2 files changed

+225
-1
lines changed

2 files changed

+225
-1
lines changed

sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.VisitStmt.cs

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,60 @@ private void VisitBinaryOperator(BinaryOperator binaryOperator)
4141
outputBuilder.Write(' ');
4242
outputBuilder.Write(binaryOperator.OpcodeStr);
4343
outputBuilder.Write(' ');
44-
Visit(binaryOperator.RHS);
44+
45+
if (binaryOperator.IsShiftOp || binaryOperator.IsShiftAssignOp)
46+
{
47+
// RHS of shift operation in C# must be an int
48+
49+
// Negative shifts are undefined behavior in C/C++, but still needs to have a compilable output
50+
var isNegated = binaryOperator.RHS is UnaryOperator { Opcode: CXUnaryOperator_Minus };
51+
var sign = isNegated ? -1 : 1;
52+
53+
var rhs = isNegated ? ((UnaryOperator)binaryOperator.RHS).SubExpr : binaryOperator.RHS;
54+
switch (rhs)
55+
{
56+
case IntegerLiteral literal:
57+
{
58+
var value = sign * literal.Value;
59+
if (value is > int.MaxValue or < int.MinValue)
60+
{
61+
// Literal is in int32 range
62+
outputBuilder.Write("(int)(");
63+
outputBuilder.Write(sign * literal.Value);
64+
outputBuilder.Write(")");
65+
}
66+
else
67+
{
68+
// Literal is not in int32 range
69+
outputBuilder.Write(sign * literal.Value);
70+
}
71+
72+
break;
73+
}
74+
// Already the correct type or implicitly castable
75+
case { Type.Kind: CXType_Int or CXType_UShort or CXType_Short or CXType_Char_U or CXType_Char_S or CXType_UChar or CXType_SChar }:
76+
case { Type.Kind: CXType_Long } when _config is { GenerateUnixTypes: false }:
77+
case { Type.Kind: CXType_Char16 } when _config is { GenerateDisableRuntimeMarshalling: false }:
78+
case { Type.Kind: CXType_WChar } when _config is { GenerateDisableRuntimeMarshalling: false, GenerateUnixTypes: false }:
79+
{
80+
Visit(binaryOperator.RHS);
81+
break;
82+
}
83+
// Fallback
84+
default:
85+
{
86+
outputBuilder.Write("(int)(");
87+
Visit(binaryOperator.RHS);
88+
outputBuilder.Write(")");
89+
break;
90+
}
91+
}
92+
}
93+
else
94+
{
95+
Visit(binaryOperator.RHS);
96+
}
97+
4598
StopCSharpCode();
4699
}
47100

tests/ClangSharp.PInvokeGenerator.UnitTests/CTest.cs

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,4 +279,175 @@ public static partial class Methods
279279

280280
return ValidateGeneratedCSharpLatestWindowsBindingsAsync(inputContents, expectedOutputContents, commandLineArgs: DefaultCClangCommandLineArgs, language: "c", languageStandard: DefaultCStandard);
281281
}
282+
283+
[Test]
284+
public Task UnsignedIntBitshiftTest()
285+
{
286+
var inputContents = @"
287+
const int Signed = 1;
288+
const long SignedLong = 1;
289+
const unsigned int Unsigned = 1;
290+
291+
const int ShiftSigned = 1 << Signed;
292+
const int ShiftSignedLong = 1 << SignedLong;
293+
const int ShiftUnsigned = 1 << Unsigned;
294+
295+
const int Char = 1 << 'a';
296+
297+
const int Byte = 1 << (signed char)1;
298+
const int UByte = 1 << (unsigned char)1;
299+
300+
const int CInt = 1 << 1;
301+
const int CUint = 1 << 1U;
302+
303+
const int Negative = 1 << -1;
304+
305+
const int OutOfRangePos = 1 << 10000000000;
306+
const int OutOfRangeNeg = 1 << -10000000000;
307+
308+
const int IntMax = 1 << 2147483647;
309+
const int IntMin = 1 << -2147483648;
310+
311+
const int LongMax = 1 << 9223372036854775807;
312+
const int LongMin = 1 << -9223372036854775808;
313+
314+
const int ULongMax = 1 << 18446744073709551615;
315+
316+
const int Hexadecimal = 1 << 0x01;
317+
318+
#define Left 1 << 1U
319+
#define Right 1 >> 1U
320+
321+
#define Int 1 << 1
322+
#define Long 1 << 1L
323+
#define LongLong 1 << 1LL
324+
#define ULong 1 << 1UL
325+
#define ULongLong 1 << 1ULL
326+
327+
#define Complex ((((unsigned int)(0)) << 29U) | (((unsigned int)(1)) << 22U) | (((unsigned int)(0)) << 12U) | ((unsigned int)(0)))
328+
";
329+
330+
// Non-ideal cases:
331+
// UByte
332+
// IntMin
333+
// ULongMax
334+
// Hexadecimal
335+
var expectedOutputContents = @"namespace ClangSharp.Test
336+
{
337+
public static partial class Methods
338+
{
339+
[NativeTypeName(""const int"")]
340+
public const int Signed = 1;
341+
342+
[NativeTypeName(""const long"")]
343+
public const int SignedLong = 1;
344+
345+
[NativeTypeName(""const unsigned int"")]
346+
public const uint Unsigned = 1;
347+
348+
[NativeTypeName(""const int"")]
349+
public const int ShiftSigned = 1 << Signed;
350+
351+
[NativeTypeName(""const int"")]
352+
public const int ShiftSignedLong = 1 << SignedLong;
353+
354+
[NativeTypeName(""const int"")]
355+
public const int ShiftUnsigned = 1 << (int)(Unsigned);
356+
357+
[NativeTypeName(""const int"")]
358+
public const int Char = 1 << (sbyte)('a');
359+
360+
[NativeTypeName(""const int"")]
361+
public const int Byte = 1 << (sbyte)(1);
362+
363+
[NativeTypeName(""const int"")]
364+
public const int UByte = unchecked(1 << (byte)(1));
365+
366+
[NativeTypeName(""const int"")]
367+
public const int CInt = 1 << 1;
368+
369+
[NativeTypeName(""const int"")]
370+
public const int CUint = 1 << 1;
371+
372+
[NativeTypeName(""const int"")]
373+
public const int Negative = 1 << -1;
374+
375+
[NativeTypeName(""const int"")]
376+
public const int OutOfRangePos = unchecked(1 << (int)(10000000000));
377+
378+
[NativeTypeName(""const int"")]
379+
public const int OutOfRangeNeg = unchecked(1 << (int)(-10000000000));
380+
381+
[NativeTypeName(""const int"")]
382+
public const int IntMax = 1 << 2147483647;
383+
384+
[NativeTypeName(""const int"")]
385+
public const int IntMin = unchecked(1 << -2147483648);
386+
387+
[NativeTypeName(""const int"")]
388+
public const int LongMax = unchecked(1 << (int)(9223372036854775807));
389+
390+
[NativeTypeName(""const int"")]
391+
public const int LongMin = unchecked(1 << (int)(-9223372036854775808));
392+
393+
[NativeTypeName(""const int"")]
394+
public const int ULongMax = 1 << -1;
395+
396+
[NativeTypeName(""const int"")]
397+
public const int Hexadecimal = 1 << 1;
398+
399+
[NativeTypeName(""#define Left 1 << 1U"")]
400+
public const int Left = 1 << 1;
401+
402+
[NativeTypeName(""#define Right 1 >> 1U"")]
403+
public const int Right = 1 >> 1;
404+
405+
[NativeTypeName(""#define Int 1 << 1"")]
406+
public const int Int = 1 << 1;
407+
408+
[NativeTypeName(""#define Long 1 << 1L"")]
409+
public const int Long = 1 << 1;
410+
411+
[NativeTypeName(""#define LongLong 1 << 1LL"")]
412+
public const int LongLong = 1 << 1;
413+
414+
[NativeTypeName(""#define ULong 1 << 1UL"")]
415+
public const int ULong = 1 << 1;
416+
417+
[NativeTypeName(""#define ULongLong 1 << 1ULL"")]
418+
public const int ULongLong = 1 << 1;
419+
420+
[NativeTypeName(""#define Complex ((((unsigned int)(0)) << 29U) | (((unsigned int)(1)) << 22U) | (((unsigned int)(0)) << 12U) | ((unsigned int)(0)))"")]
421+
public const uint Complex = ((((uint)(0)) << 29) | (((uint)(1)) << 22) | (((uint)(0)) << 12) | ((uint)(0)));
422+
}
282423
}
424+
";
425+
426+
return ValidateGeneratedCSharpLatestWindowsBindingsAsync(inputContents, expectedOutputContents, commandLineArgs: DefaultCClangCommandLineArgs, language: "c", languageStandard: DefaultCStandard);
427+
}
428+
429+
430+
[Test]
431+
public Task UnsignedIntBitshiftTestUnix()
432+
{
433+
var inputContents = @"
434+
const long SignedLong = 1;
435+
const int ShiftSignedLong = 1 << SignedLong;
436+
";
437+
438+
var expectedOutputContents = @"namespace ClangSharp.Test
439+
{
440+
public static partial class Methods
441+
{
442+
[NativeTypeName(""const long"")]
443+
public const nint SignedLong = 1;
444+
445+
[NativeTypeName(""const int"")]
446+
public const int ShiftSignedLong = 1 << (int)(SignedLong);
447+
}
448+
}
449+
";
450+
451+
return ValidateGeneratedCSharpLatestUnixBindingsAsync(inputContents, expectedOutputContents, commandLineArgs: DefaultCClangCommandLineArgs, language: "c", languageStandard: DefaultCStandard);
452+
}
453+
}

0 commit comments

Comments
 (0)