Browse Source

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
pull/1909/head
Jelle 5 months ago committed by GitHub
parent
commit
18d817ffaf
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 6
      src/Generator.Tests/CppSharp.Generator.Tests.csproj
  2. 22
      src/Generator.Tests/Passes/TestPasses.cs
  3. 2
      src/Generator/Driver.cs
  4. 76
      src/Generator/Passes/CheckEnumsPass.cs
  5. 52
      src/Generator/Passes/CheckFlagEnumsPass.cs
  6. 1
      tests/dotnet/Common/Common.Tests.cs
  7. 2
      tests/dotnet/Native/ASTExtensions.h
  8. 14
      tests/dotnet/Native/Passes.h

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

@ -1,8 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<ItemGroup> <ItemGroup>
<None Include="..\..\tests\Native\AST.h" LinkBase="tests\Native" /> <None Include="..\..\tests\dotnet\Native\AST.h" LinkBase="tests\Native" />
<None Include="..\..\tests\Native\ASTExtensions.h" LinkBase="tests\Native" /> <None Include="..\..\tests\dotnet\Native\ASTExtensions.h" LinkBase="tests\Native" />
<None Include="..\..\tests\Native\Passes.h" LinkBase="tests\Native" /> <None Include="..\..\tests\dotnet\Native\Passes.h" LinkBase="tests\Native" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Generator\CppSharp.Generator.csproj" /> <ProjectReference Include="..\Generator\CppSharp.Generator.csproj" />

22
src/Generator.Tests/Passes/TestPasses.cs

@ -39,19 +39,31 @@ namespace CppSharp.Generator.Tests.Passes
} }
[Test] [Test]
public void TestCheckFlagEnumsPass() public void TestCheckEnumsPass()
{ {
var @enum = AstContext.Enum("FlagEnum"); var @enum = AstContext.Enum("FlagEnum");
var enum2 = AstContext.Enum("FlagEnum2");
var boolClassEnum = AstContext.Enum("BoolEnum");
var ucharClassEnum = AstContext.Enum("UCharEnum");
Assert.IsFalse(@enum.IsFlags); Assert.IsFalse(@enum.IsFlags);
Assert.IsFalse(enum2.IsFlags);
Assert.IsFalse(boolClassEnum.IsFlags);
Assert.IsFalse(ucharClassEnum.IsFlags);
var @enum2 = AstContext.Enum("FlagEnum2"); Assert.IsTrue(boolClassEnum.BuiltinType.Type == PrimitiveType.Bool);
Assert.IsFalse(@enum2.IsFlags); Assert.IsTrue(ucharClassEnum.BuiltinType.Type == PrimitiveType.UChar);
passBuilder.AddPass(new CheckFlagEnumsPass()); passBuilder.AddPass(new CheckEnumsPass());
passBuilder.RunPasses(pass => pass.VisitASTContext(AstContext)); passBuilder.RunPasses(pass => pass.VisitASTContext(AstContext));
Assert.IsTrue(@enum.IsFlags); Assert.IsTrue(@enum.IsFlags);
Assert.IsFalse(@enum2.IsFlags); Assert.IsFalse(enum2.IsFlags);
Assert.IsFalse(boolClassEnum.IsFlags);
Assert.IsTrue(ucharClassEnum.IsFlags);
Assert.IsTrue(boolClassEnum.BuiltinType.Type != PrimitiveType.Bool, "C# does not support Bool enums");
Assert.IsTrue(ucharClassEnum.BuiltinType.Type == PrimitiveType.UChar);
} }
[Test] [Test]

2
src/Generator/Driver.cs

@ -244,7 +244,7 @@ namespace CppSharp
passes.AddPass(new FastDelegateToDelegatesPass()); passes.AddPass(new FastDelegateToDelegatesPass());
passes.AddPass(new FieldToPropertyPass()); passes.AddPass(new FieldToPropertyPass());
passes.AddPass(new CheckIgnoredDeclsPass()); passes.AddPass(new CheckIgnoredDeclsPass());
passes.AddPass(new CheckFlagEnumsPass()); passes.AddPass(new CheckEnumsPass());
passes.AddPass(new MakeProtectedNestedTypesPublicPass()); passes.AddPass(new MakeProtectedNestedTypesPublicPass());
if (Options.IsCSharpGenerator) if (Options.IsCSharpGenerator)

76
src/Generator/Passes/CheckEnumsPass.cs

@ -0,0 +1,76 @@
using CppSharp.AST;
using CppSharp.Extensions;
namespace CppSharp.Passes
{
/// <summary>
/// Validates enumerations and checks if any should be treated as a collection
/// of flags (and annotate them with the .NET [Flags] when generated).
/// </summary>
public class CheckEnumsPass : TranslationUnitPass
{
public CheckEnumsPass()
=> VisitOptions.ResetFlags(VisitFlags.NamespaceEnums);
private static bool IsFlagEnum(Enumeration @enum)
{
// If the enumeration only has power of two values, assume it's
// a flags enum.
var isFlags = true;
var hasBigRange = false;
foreach (var item in @enum.Items)
{
var value = item.Value;
if (value >= 4)
hasBigRange = true;
if (value <= 1 || value.IsPowerOfTwo())
continue;
isFlags = false;
}
// Only apply this heuristic if there are enough values to have a
// reasonable chance that it really is a bitfield.
return isFlags && hasBigRange;
}
private bool IsValidEnumBaseType(Enumeration @enum)
{
if (Options.IsCSharpGenerator)
return @enum.BuiltinType.Type.IsIntegerType();
return @enum.BuiltinType.Type.IsIntegerType() || @enum.BuiltinType.Type == PrimitiveType.Bool;
}
public override bool VisitEnumDecl(Enumeration @enum)
{
if (!base.VisitEnumDecl(@enum))
return false;
if (!IsValidEnumBaseType(@enum))
{
if (@enum.BuiltinType.Type == PrimitiveType.Bool)
{
@enum.BuiltinType = new BuiltinType(PrimitiveType.UChar);
}
else
{
Diagnostics.Warning(
"The enum `{0}` has a base type of `{1}`, which is currently not supported. The base type will be ignored.",
@enum, @enum.BuiltinType);
@enum.BuiltinType = new BuiltinType(PrimitiveType.Int);
}
}
if (IsFlagEnum(@enum))
@enum.Modifiers |= Enumeration.EnumModifiers.Flags;
return true;
}
}
}

52
src/Generator/Passes/CheckFlagEnumsPass.cs

@ -1,52 +0,0 @@
using CppSharp.AST;
namespace CppSharp.Passes
{
/// <summary>
/// Checks for enumerations that should be treated as a collection
/// of flags (and annotated with the .NET [Flags] when generated).
/// </summary>
public class CheckFlagEnumsPass : TranslationUnitPass
{
public CheckFlagEnumsPass()
=> VisitOptions.ResetFlags(VisitFlags.NamespaceEnums);
private static bool IsFlagEnum(Enumeration @enum)
{
// If the enumeration only has power of two values, assume it's
// a flags enum.
var isFlags = true;
var hasBigRange = false;
foreach (var item in @enum.Items)
{
var value = item.Value;
if (value >= 4)
hasBigRange = true;
if (value <= 1 || value.IsPowerOfTwo())
continue;
isFlags = false;
}
// Only apply this heuristic if there are enough values to have a
// reasonable chance that it really is a bitfield.
return isFlags && hasBigRange;
}
public override bool VisitEnumDecl(Enumeration @enum)
{
if (IsFlagEnum(@enum))
{
@enum.Modifiers |= Enumeration.EnumModifiers.Flags;
return true;
}
return base.VisitEnumDecl(@enum);
}
}
}

1
tests/dotnet/Common/Common.Tests.cs

@ -172,6 +172,7 @@ public class CommonTests
} }
} }
[Test]
public void TestPrimitiveInOutParameters() public void TestPrimitiveInOutParameters()
{ {
using (var hello = new Hello()) using (var hello = new Hello())

2
tests/dotnet/Native/ASTExtensions.h

@ -1,7 +1,7 @@
#include "AST.h" #include "AST.h"
#include <vector> #include <vector>
// Tests class templates accross translation units // Tests class templates across translation units
// Explicit instantiation // Explicit instantiation
template class TestTemplateClass<Math::Complex>; template class TestTemplateClass<Math::Complex>;
// Implicit instantiations // Implicit instantiations

14
tests/dotnet/Native/Passes.h

@ -14,6 +14,20 @@ enum FlagEnum2
D1 = 1 << 4, D1 = 1 << 4,
}; };
enum class BoolEnum : bool
{
True = true,
False = false,
};
enum class UCharEnum : unsigned char
{
A = 1 << 0,
B = 1 << 1,
C = 1 << 2,
D = 1 << 3,
};
class Foo class Foo
{ {
void toIgnore() { } void toIgnore() { }

Loading…
Cancel
Save