Skip to content

Commit 18d817f

Browse files
authored
Fix invalid enum base types being generated in C# source (#1899)
* Rename `CheckFlagEnumsPass` to `CheckEnumsPass` * Fix invalid enum base types being generated in C# source * Add enum base type validation tests
1 parent 80bfc78 commit 18d817f

File tree

8 files changed

+113
-62
lines changed

8 files changed

+113
-62
lines changed

src/Generator.Tests/CppSharp.Generator.Tests.csproj

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22
<ItemGroup>
3-
<None Include="..\..\tests\Native\AST.h" LinkBase="tests\Native" />
4-
<None Include="..\..\tests\Native\ASTExtensions.h" LinkBase="tests\Native" />
5-
<None Include="..\..\tests\Native\Passes.h" LinkBase="tests\Native" />
3+
<None Include="..\..\tests\dotnet\Native\AST.h" LinkBase="tests\Native" />
4+
<None Include="..\..\tests\dotnet\Native\ASTExtensions.h" LinkBase="tests\Native" />
5+
<None Include="..\..\tests\dotnet\Native\Passes.h" LinkBase="tests\Native" />
66
</ItemGroup>
77
<ItemGroup>
88
<ProjectReference Include="..\Generator\CppSharp.Generator.csproj" />

src/Generator.Tests/Passes/TestPasses.cs

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -39,19 +39,31 @@ public void TestExtractInterfacePass()
3939
}
4040

4141
[Test]
42-
public void TestCheckFlagEnumsPass()
42+
public void TestCheckEnumsPass()
4343
{
4444
var @enum = AstContext.Enum("FlagEnum");
45+
var enum2 = AstContext.Enum("FlagEnum2");
46+
var boolClassEnum = AstContext.Enum("BoolEnum");
47+
var ucharClassEnum = AstContext.Enum("UCharEnum");
48+
4549
Assert.IsFalse(@enum.IsFlags);
50+
Assert.IsFalse(enum2.IsFlags);
51+
Assert.IsFalse(boolClassEnum.IsFlags);
52+
Assert.IsFalse(ucharClassEnum.IsFlags);
4653

47-
var @enum2 = AstContext.Enum("FlagEnum2");
48-
Assert.IsFalse(@enum2.IsFlags);
54+
Assert.IsTrue(boolClassEnum.BuiltinType.Type == PrimitiveType.Bool);
55+
Assert.IsTrue(ucharClassEnum.BuiltinType.Type == PrimitiveType.UChar);
4956

50-
passBuilder.AddPass(new CheckFlagEnumsPass());
57+
passBuilder.AddPass(new CheckEnumsPass());
5158
passBuilder.RunPasses(pass => pass.VisitASTContext(AstContext));
5259

5360
Assert.IsTrue(@enum.IsFlags);
54-
Assert.IsFalse(@enum2.IsFlags);
61+
Assert.IsFalse(enum2.IsFlags);
62+
Assert.IsFalse(boolClassEnum.IsFlags);
63+
Assert.IsTrue(ucharClassEnum.IsFlags);
64+
65+
Assert.IsTrue(boolClassEnum.BuiltinType.Type != PrimitiveType.Bool, "C# does not support Bool enums");
66+
Assert.IsTrue(ucharClassEnum.BuiltinType.Type == PrimitiveType.UChar);
5567
}
5668

5769
[Test]

src/Generator/Driver.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -244,7 +244,7 @@ public void SetupPasses(ILibrary library)
244244
passes.AddPass(new FastDelegateToDelegatesPass());
245245
passes.AddPass(new FieldToPropertyPass());
246246
passes.AddPass(new CheckIgnoredDeclsPass());
247-
passes.AddPass(new CheckFlagEnumsPass());
247+
passes.AddPass(new CheckEnumsPass());
248248
passes.AddPass(new MakeProtectedNestedTypesPublicPass());
249249

250250
if (Options.IsCSharpGenerator)
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
using CppSharp.AST;
2+
using CppSharp.Extensions;
3+
4+
namespace CppSharp.Passes
5+
{
6+
/// <summary>
7+
/// Validates enumerations and checks if any should be treated as a collection
8+
/// of flags (and annotate them with the .NET [Flags] when generated).
9+
/// </summary>
10+
public class CheckEnumsPass : TranslationUnitPass
11+
{
12+
public CheckEnumsPass()
13+
=> VisitOptions.ResetFlags(VisitFlags.NamespaceEnums);
14+
15+
private static bool IsFlagEnum(Enumeration @enum)
16+
{
17+
// If the enumeration only has power of two values, assume it's
18+
// a flags enum.
19+
20+
var isFlags = true;
21+
var hasBigRange = false;
22+
23+
foreach (var item in @enum.Items)
24+
{
25+
var value = item.Value;
26+
27+
if (value >= 4)
28+
hasBigRange = true;
29+
30+
if (value <= 1 || value.IsPowerOfTwo())
31+
continue;
32+
33+
isFlags = false;
34+
}
35+
36+
// Only apply this heuristic if there are enough values to have a
37+
// reasonable chance that it really is a bitfield.
38+
39+
return isFlags && hasBigRange;
40+
}
41+
42+
private bool IsValidEnumBaseType(Enumeration @enum)
43+
{
44+
if (Options.IsCSharpGenerator)
45+
return @enum.BuiltinType.Type.IsIntegerType();
46+
47+
return @enum.BuiltinType.Type.IsIntegerType() || @enum.BuiltinType.Type == PrimitiveType.Bool;
48+
}
49+
50+
public override bool VisitEnumDecl(Enumeration @enum)
51+
{
52+
if (!base.VisitEnumDecl(@enum))
53+
return false;
54+
55+
if (!IsValidEnumBaseType(@enum))
56+
{
57+
if (@enum.BuiltinType.Type == PrimitiveType.Bool)
58+
{
59+
@enum.BuiltinType = new BuiltinType(PrimitiveType.UChar);
60+
}
61+
else
62+
{
63+
Diagnostics.Warning(
64+
"The enum `{0}` has a base type of `{1}`, which is currently not supported. The base type will be ignored.",
65+
@enum, @enum.BuiltinType);
66+
@enum.BuiltinType = new BuiltinType(PrimitiveType.Int);
67+
}
68+
}
69+
70+
if (IsFlagEnum(@enum))
71+
@enum.Modifiers |= Enumeration.EnumModifiers.Flags;
72+
73+
return true;
74+
}
75+
}
76+
}

src/Generator/Passes/CheckFlagEnumsPass.cs

Lines changed: 0 additions & 52 deletions
This file was deleted.

tests/dotnet/Common/Common.Tests.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,7 @@ public void TestPrimitiveOutRefParameters()
172172
}
173173
}
174174

175+
[Test]
175176
public void TestPrimitiveInOutParameters()
176177
{
177178
using (var hello = new Hello())

tests/dotnet/Native/ASTExtensions.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#include "AST.h"
22
#include <vector>
33

4-
// Tests class templates accross translation units
4+
// Tests class templates across translation units
55
// Explicit instantiation
66
template class TestTemplateClass<Math::Complex>;
77
// Implicit instantiations

tests/dotnet/Native/Passes.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,20 @@ enum FlagEnum2
1414
D1 = 1 << 4,
1515
};
1616

17+
enum class BoolEnum : bool
18+
{
19+
True = true,
20+
False = false,
21+
};
22+
23+
enum class UCharEnum : unsigned char
24+
{
25+
A = 1 << 0,
26+
B = 1 << 1,
27+
C = 1 << 2,
28+
D = 1 << 3,
29+
};
30+
1731
class Foo
1832
{
1933
void toIgnore() { }

0 commit comments

Comments
 (0)