diff --git a/Coder.Test/Languages/CGeneratedSourceCompilesTests.cs b/Coder.Test/Languages/CGeneratedSourceCompilesTests.cs
index 10fb2cc..7bf8267 100644
--- a/Coder.Test/Languages/CGeneratedSourceCompilesTests.cs
+++ b/Coder.Test/Languages/CGeneratedSourceCompilesTests.cs
@@ -203,6 +203,8 @@ private static SourceFile Exemplar()
file.Members.Add(CompiledExemplar.Shape());
file.Members.Add(CompiledExemplar.OriginAlias());
file.Members.Add(CompiledExemplar.OriginTable());
+ file.Members.Add(new EnumDeclaration("Empty"));
+ file.Members.Add(new EnumDeclaration("EmptyFlags") { UnderlyingType = "long" });
file.Members.Add(new CompileTimeAssertion
{
Condition = "sizeof(Point) == 2 * sizeof(int)",
diff --git a/Coder.Test/Languages/CGeneratorTests.cs b/Coder.Test/Languages/CGeneratorTests.cs
index fe6957d..c2bc2f9 100644
--- a/Coder.Test/Languages/CGeneratorTests.cs
+++ b/Coder.Test/Languages/CGeneratorTests.cs
@@ -404,6 +404,21 @@ public void Enum_UnderlyingType_IsANote()
Assert.IsFalse(code.Contains("enum Colour : int", StringComparison.Ordinal));
}
+ ///
+ /// Tests that an enumeration with no members is a typedef of the type that would hold one, since
+ /// ISO C has no empty enumerator list.
+ ///
+ [TestMethod]
+ public void Enum_WithNoMembers_IsATypedefOfItsType()
+ {
+ string bare = Generator.Generate(new EnumDeclaration("Empty"));
+ string typed = Generator.Generate(new EnumDeclaration("Flags") { UnderlyingType = "long" });
+
+ StringAssert.EndsWith(bare, $"typedef int Empty;{NewLine}", StringComparison.Ordinal);
+ StringAssert.EndsWith(typed, $"typedef long long Flags;{NewLine}", StringComparison.Ordinal);
+ Assert.IsFalse(bare.Contains("typedef enum", StringComparison.Ordinal));
+ }
+
///
/// Tests that a constant is static const. A file-scope const in C has external
/// linkage, so a header declaring one and included twice is the same object defined twice.
diff --git a/Coder.Test/Languages/RustGeneratedSourceCompilesTests.cs b/Coder.Test/Languages/RustGeneratedSourceCompilesTests.cs
index ddc0fef..0a2ffbf 100644
--- a/Coder.Test/Languages/RustGeneratedSourceCompilesTests.cs
+++ b/Coder.Test/Languages/RustGeneratedSourceCompilesTests.cs
@@ -298,6 +298,7 @@ private static SourceFile Exemplar()
geometry.Members.Add(CompiledExemplar.Measure());
geometry.Members.Add(Boxed());
geometry.Members.Add(Clamp());
+ geometry.Members.Add(new EnumDeclaration("Empty") { UnderlyingType = "int" });
geometry.Members.Add(new CompileTimeAssertion
{
Condition = "std::mem::size_of::() == 4",
diff --git a/Coder.Test/Languages/RustGeneratorTests.cs b/Coder.Test/Languages/RustGeneratorTests.cs
index c7d2dc6..167f316 100644
--- a/Coder.Test/Languages/RustGeneratorTests.cs
+++ b/Coder.Test/Languages/RustGeneratorTests.cs
@@ -562,6 +562,19 @@ public void Enum_IsScopedAndCanFixItsRepresentation()
code);
}
+ ///
+ /// Tests that an enumeration with no variants leaves off #[repr], which rustc rejects on
+ /// one (E0084).
+ ///
+ [TestMethod]
+ public void Enum_WithNoVariants_HasNoRepr()
+ {
+ string code = Generator.Generate(new EnumDeclaration("Empty") { UnderlyingType = "int" });
+
+ Assert.IsFalse(code.Contains("#[repr", StringComparison.Ordinal));
+ StringAssert.StartsWith(code, "pub enum Empty {", StringComparison.Ordinal);
+ }
+
///
/// Tests that a constant is a const and an ordinary field a static, and that an
/// array one is a borrowed slice — the bound-free spelling Rust does have, which is what a table
diff --git a/Coder/Languages/CGenerator.cs b/Coder/Languages/CGenerator.cs
index a0f6dcc..f09c105 100644
--- a/Coder/Languages/CGenerator.cs
+++ b/Coder/Languages/CGenerator.cs
@@ -878,6 +878,10 @@ protected override void WriteListValue(AstNode value, CodeBlocker code)
/// that needs the guarantee can assert it with a , which is a
/// thing the AST can already say.
///
+ ///
+ /// An enumeration with no members is a typedef of its underlying type, or of int, since
+ /// ISO C has no empty enumerator list and typedef enum Name { } Name; does not compile.
+ ///
///
protected override void GenerateEnumDeclaration(EnumDeclaration enumDecl, CodeBlocker code)
{
@@ -886,6 +890,14 @@ protected override void GenerateEnumDeclaration(EnumDeclaration enumDecl, CodeBl
string name = enumDecl.Name ?? "UnnamedEnum";
+ if (enumDecl.Members.Count == 0)
+ {
+ WriteInexpressible(code, $"enum {name} has no members: C has no empty enum, so it is the type that would hold one");
+ WritePreamble(enumDecl, code, PreambleSite.Enum);
+ code.WriteLine($"typedef {(enumDecl.UnderlyingType is TypeReference type ? MapToCType(type) : "int")} {name};");
+ return;
+ }
+
// Above the documentation rather than below it, so the comment block in front of the
// declaration stays one block rather than the note splitting it in two.
if (enumDecl.UnderlyingType is TypeReference underlying)
diff --git a/Coder/Languages/RustGenerator.cs b/Coder/Languages/RustGenerator.cs
index b1fb54d..5229f71 100644
--- a/Coder/Languages/RustGenerator.cs
+++ b/Coder/Languages/RustGenerator.cs
@@ -1335,7 +1335,8 @@ private static string SpellStorageType(TypeReference type)
/// An enumeration's variants are named through it — Colour::Red — so nothing has to be
/// prefixed to keep two enumerations with a None each apart, which is the thing C cannot
/// do. A fixed underlying type is #[repr], which is the guarantee rather than a comment
- /// about one.
+ /// about one. An enumeration with no variants has no representation to fix, and rustc rejects
+ /// #[repr] on one (E0084), so it is left off.
///
protected override void GenerateEnumDeclaration(EnumDeclaration enumDecl, CodeBlocker code)
{
@@ -1344,7 +1345,7 @@ protected override void GenerateEnumDeclaration(EnumDeclaration enumDecl, CodeBl
WritePreamble(enumDecl, code, PreambleSite.Enum);
- if (enumDecl.UnderlyingType is TypeReference underlying)
+ if (enumDecl.UnderlyingType is TypeReference underlying && enumDecl.Members.Count > 0)
{
code.WriteLine($"#[repr({SpellType(underlying)})]");
}