[pigeon] allow empty class (#12181)

instead of preventing it from being defined, I just made it work
diff --git a/packages/pigeon/CHANGELOG.md b/packages/pigeon/CHANGELOG.md
index 83d2e72..e70fe00 100644
--- a/packages/pigeon/CHANGELOG.md
+++ b/packages/pigeon/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 27.2.0
+
+* Adds support for empty data classes.
+
 ## 27.1.2
 
 * Reports a clear error when an input file uses an enhanced enum (one with a
diff --git a/packages/pigeon/lib/src/ast.dart b/packages/pigeon/lib/src/ast.dart
index 29e5c34..0ae0078 100644
--- a/packages/pigeon/lib/src/ast.dart
+++ b/packages/pigeon/lib/src/ast.dart
@@ -803,6 +803,8 @@
     return Root(apis: <Api>[], classes: <Class>[], enums: <Enum>[]);
   }
 
+  // TODO(tarrinneal): Ensure classes are sorted in topological dependency order; see
+  // https://github.com/flutter/flutter/issues/128330.
   /// All the classes contained in the AST.
   List<Class> classes;
 
diff --git a/packages/pigeon/lib/src/dart/dart_generator.dart b/packages/pigeon/lib/src/dart/dart_generator.dart
index 3218103..94fe726 100644
--- a/packages/pigeon/lib/src/dart/dart_generator.dart
+++ b/packages/pigeon/lib/src/dart/dart_generator.dart
@@ -222,17 +222,19 @@
 
     indent.write('${sealed}class ${classDefinition.name} $implements');
     indent.addScoped('{', '}', () {
-      if (classDefinition.fields.isEmpty) {
+      if (classDefinition.isSealed) {
         return;
       }
       _writeConstructor(indent, classDefinition);
       indent.newln();
-      for (final NamedType field in getFieldsInSerializationOrder(classDefinition)) {
-        addDocumentationComments(indent, field.documentationComments, docCommentSpec);
+      if (classDefinition.fields.isNotEmpty) {
+        for (final NamedType field in getFieldsInSerializationOrder(classDefinition)) {
+          addDocumentationComments(indent, field.documentationComments, docCommentSpec);
 
-        final String datatype = addGenericTypes(field.type);
-        indent.writeln('$datatype ${field.name};');
-        indent.newln();
+          final String datatype = addGenericTypes(field.type);
+          indent.writeln('$datatype ${field.name};');
+          indent.newln();
+        }
       }
       _writeToList(indent, classDefinition);
       indent.newln();
@@ -264,6 +266,10 @@
 
   void _writeConstructor(Indent indent, Class classDefinition) {
     indent.write(classDefinition.name);
+    if (classDefinition.fields.isEmpty) {
+      indent.addln('();');
+      return;
+    }
     indent.addScoped('({', '});', () {
       for (final NamedType field in getFieldsInSerializationOrder(classDefinition)) {
         final required = !field.type.isNullable && field.defaultValue == null ? 'required ' : '';
diff --git a/packages/pigeon/lib/src/generator_tools.dart b/packages/pigeon/lib/src/generator_tools.dart
index 860898a..1f3f71a 100644
--- a/packages/pigeon/lib/src/generator_tools.dart
+++ b/packages/pigeon/lib/src/generator_tools.dart
@@ -15,7 +15,7 @@
 /// The current version of pigeon.
 ///
 /// This must match the version in pubspec.yaml.
-const String pigeonVersion = '27.1.2';
+const String pigeonVersion = '27.2.0';
 
 /// Default plugin package name.
 const String defaultPluginPackageName = 'dev.flutter.pigeon';
diff --git a/packages/pigeon/lib/src/java/java_generator.dart b/packages/pigeon/lib/src/java/java_generator.dart
index f377695..e0854b4 100644
--- a/packages/pigeon/lib/src/java/java_generator.dart
+++ b/packages/pigeon/lib/src/java/java_generator.dart
@@ -345,11 +345,15 @@
     indent.writeScoped('public boolean equals(Object o) {', '}', () {
       indent.writeln('if (this == o) { return true; }');
       indent.writeln('if (o == null || getClass() != o.getClass()) { return false; }');
-      indent.writeln('${classDefinition.name} that = (${classDefinition.name}) o;');
-      final Iterable<String> checks = classDefinition.fields.map((NamedType field) {
-        return 'pigeonDeepEquals(${field.name}, that.${field.name})';
-      });
-      indent.writeln('return ${checks.join(' && ')};');
+      if (classDefinition.fields.isEmpty) {
+        indent.writeln('return true;');
+      } else {
+        indent.writeln('${classDefinition.name} that = (${classDefinition.name}) o;');
+        final Iterable<String> checks = classDefinition.fields.map((NamedType field) {
+          return 'pigeonDeepEquals(${field.name}, that.${field.name})';
+        });
+        indent.writeln('return ${checks.join(' && ')};');
+      }
     });
     indent.newln();
 
diff --git a/packages/pigeon/lib/src/kotlin/kotlin_generator.dart b/packages/pigeon/lib/src/kotlin/kotlin_generator.dart
index 91b605f..9354fb3 100644
--- a/packages/pigeon/lib/src/kotlin/kotlin_generator.dart
+++ b/packages/pigeon/lib/src/kotlin/kotlin_generator.dart
@@ -348,11 +348,11 @@
         indent.writeln('return true');
       });
 
-      indent.writeln('val other = other as ${classDefinition.name}');
       final Iterable<NamedType> fields = getFieldsInSerializationOrder(classDefinition);
       if (fields.isEmpty) {
         indent.writeln('return true');
       } else {
+        indent.writeln('val other = other as ${classDefinition.name}');
         final String utils = _getUtilsClassName(generatorOptions);
         final String comparisons = fields
             .map((NamedType field) => '$utils.deepEquals(this.${field.name}, other.${field.name})')
@@ -399,24 +399,35 @@
 
   void _writeDataClassSignature(Indent indent, Class classDefinition, {bool private = false}) {
     final privateString = private ? 'private ' : '';
-    final classType = classDefinition.isSealed ? 'sealed' : 'data';
+    final String classType;
+    if (classDefinition.isSealed) {
+      classType = 'sealed ';
+    } else if (classDefinition.fields.isEmpty) {
+      classType = '';
+    } else {
+      classType = 'data ';
+    }
     final inheritance = classDefinition.superClass != null
         ? ' : ${classDefinition.superClassName}()'
         : '';
-    indent.write('$privateString$classType class ${classDefinition.name} ');
+    indent.write('$privateString${classType}class ${classDefinition.name} ');
     if (classDefinition.isSealed) {
       return;
     }
-    indent.addScoped('(', ')$inheritance', () {
-      for (final NamedType element in getFieldsInSerializationOrder(classDefinition)) {
-        _writeClassField(indent, element);
-        if (getFieldsInSerializationOrder(classDefinition).last != element) {
-          indent.addln(',');
-        } else {
-          indent.newln();
+    if (classDefinition.fields.isEmpty) {
+      indent.add(inheritance);
+    } else {
+      indent.addScoped('(', ')$inheritance', () {
+        for (final NamedType element in getFieldsInSerializationOrder(classDefinition)) {
+          _writeClassField(indent, element);
+          if (getFieldsInSerializationOrder(classDefinition).last != element) {
+            indent.addln(',');
+          } else {
+            indent.newln();
+          }
         }
-      }
-    });
+      });
+    }
   }
 
   @override
diff --git a/packages/pigeon/lib/src/objc/objc_generator.dart b/packages/pigeon/lib/src/objc/objc_generator.dart
index 9751b5f..b922a2f 100644
--- a/packages/pigeon/lib/src/objc/objc_generator.dart
+++ b/packages/pigeon/lib/src/objc/objc_generator.dart
@@ -519,7 +519,9 @@
     final String className = _className(generatorOptions.prefix, classDefinition.name);
 
     indent.writeln('@implementation $className');
-    _writeObjcSourceClassInitializer(generatorOptions, root, indent, classDefinition, className);
+    if (classDefinition.fields.isNotEmpty) {
+      _writeObjcSourceClassInitializer(generatorOptions, root, indent, classDefinition, className);
+    }
     writeClassDecode(
       generatorOptions,
       root,
@@ -554,21 +556,21 @@
       indent.writeScoped('if (![object isKindOfClass:[self class]]) {', '}', () {
         indent.writeln('return NO;');
       });
-      indent.writeln('$className *other = ($className *)object;');
-      final Iterable<String> checks = classDefinition.fields.map((NamedType field) {
-        final String name = field.name;
-        if (_usesPrimitive(field.type)) {
-          if (field.type.baseName == 'double') {
-            return '(self.$name == other.$name || (isnan(self.$name) && isnan(other.$name)))';
-          }
-          return 'self.$name == other.$name';
-        } else {
-          return 'FLTPigeonDeepEquals(self.$name, other.$name)';
-        }
-      });
-      if (checks.isEmpty) {
+      if (classDefinition.fields.isEmpty) {
         indent.writeln('return YES;');
       } else {
+        indent.writeln('$className *other = ($className *)object;');
+        final Iterable<String> checks = classDefinition.fields.map((NamedType field) {
+          final String name = field.name;
+          if (_usesPrimitive(field.type)) {
+            if (field.type.baseName == 'double') {
+              return '(self.$name == other.$name || (isnan(self.$name) && isnan(other.$name)))';
+            }
+            return 'self.$name == other.$name';
+          } else {
+            return 'FLTPigeonDeepEquals(self.$name, other.$name)';
+          }
+        });
         indent.writeln('return ${checks.join(' && ')};');
       }
     });
@@ -1900,10 +1902,8 @@
   addDocumentationComments(indent, classDefinition.documentationComments, _docCommentSpec);
 
   indent.writeln('@interface ${_className(prefix, classDefinition.name)} : NSObject');
-  if (getFieldsInSerializationOrder(classDefinition).isNotEmpty) {
-    if (getFieldsInSerializationOrder(
-      classDefinition,
-    ).map((NamedType e) => !e.type.isNullable).any((bool e) => e)) {
+  if (classDefinition.fields.isNotEmpty) {
+    if (classDefinition.fields.any((NamedType e) => !e.type.isNullable)) {
       indent.writeln(
         '$_docCommentPrefix `init` unavailable to enforce nonnull fields, see the `make` class method.',
       );
diff --git a/packages/pigeon/pigeons/core_tests.dart b/packages/pigeon/pigeons/core_tests.dart
index e93cecf..381c3d0 100644
--- a/packages/pigeon/pigeons/core_tests.dart
+++ b/packages/pigeon/pigeons/core_tests.dart
@@ -251,6 +251,9 @@
   Map<int?, Map<Object?, Object?>?>? mapMap;
 }
 
+/// A data class without fields for testing empty classes.
+class AnEmptyClass {}
+
 /// A class for testing nested class handling.
 ///
 /// This is needed to test nested nullable and non-nullable classes,
@@ -265,6 +268,7 @@
     this.classMap,
     this.nullableClassList,
     this.nullableClassMap,
+    this.anEmptyClass,
   );
   AllNullableTypes allNullableTypes;
   AllNullableTypesWithoutRecursion? allNullableTypesWithoutRecursion;
@@ -273,6 +277,7 @@
   List<AllNullableTypesWithoutRecursion?>? nullableClassList;
   Map<int?, AllTypes?> classMap;
   Map<int?, AllNullableTypesWithoutRecursion?>? nullableClassMap;
+  AnEmptyClass? anEmptyClass;
 }
 
 /// The core interface that each host language plugin must implement in
diff --git a/packages/pigeon/pigeons/event_channel_tests.dart b/packages/pigeon/pigeons/event_channel_tests.dart
index ef9e904..9bc9dfd 100644
--- a/packages/pigeon/pigeons/event_channel_tests.dart
+++ b/packages/pigeon/pigeons/event_channel_tests.dart
@@ -129,6 +129,8 @@
   final EventAllNullableTypes value;
 }
 
+class EmptyEvent extends PlatformEvent {}
+
 @EventChannelApi()
 abstract class EventChannelMethods {
   int streamInts();
diff --git a/packages/pigeon/platform_tests/alternate_language_test_plugin/android/src/main/java/com/example/alternate_language_test_plugin/CoreTests.java b/packages/pigeon/platform_tests/alternate_language_test_plugin/android/src/main/java/com/example/alternate_language_test_plugin/CoreTests.java
index da1de3d..868874c 100644
--- a/packages/pigeon/platform_tests/alternate_language_test_plugin/android/src/main/java/com/example/alternate_language_test_plugin/CoreTests.java
+++ b/packages/pigeon/platform_tests/alternate_language_test_plugin/android/src/main/java/com/example/alternate_language_test_plugin/CoreTests.java
@@ -26,6 +26,7 @@
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 
 /** Generated class from Pigeon. */
 @SuppressWarnings({"unused", "unchecked", "CodeBlock2Expr", "RedundantSuppression", "serial"})
@@ -2915,6 +2916,53 @@
   }
 
   /**
+   * A data class without fields for testing empty classes.
+   *
+   * <p>Generated class from Pigeon that represents data sent in messages.
+   */
+  public static final class AnEmptyClass {
+    @Override
+    public boolean equals(Object o) {
+      if (this == o) {
+        return true;
+      }
+      if (o == null || getClass() != o.getClass()) {
+        return false;
+      }
+      return true;
+    }
+
+    @Override
+    public int hashCode() {
+      return Objects.hash(getClass());
+    }
+
+    @Override
+    public String toString() {
+      return "AnEmptyClass{}";
+    }
+
+    public static final class Builder {
+
+      public @NonNull AnEmptyClass build() {
+        AnEmptyClass pigeonReturn = new AnEmptyClass();
+        return pigeonReturn;
+      }
+    }
+
+    @NonNull
+    ArrayList<Object> toList() {
+      ArrayList<Object> toListResult = new ArrayList<>(0);
+      return toListResult;
+    }
+
+    static @NonNull AnEmptyClass fromList(@NonNull ArrayList<Object> pigeonVar_list) {
+      AnEmptyClass pigeonResult = new AnEmptyClass();
+      return pigeonResult;
+    }
+  }
+
+  /**
    * A class for testing nested class handling.
    *
    * <p>This is needed to test nested nullable and non-nullable classes, `AllNullableTypes` is
@@ -3005,6 +3053,16 @@
       this.nullableClassMap = setterArg;
     }
 
+    private @Nullable AnEmptyClass anEmptyClass;
+
+    public @Nullable AnEmptyClass getAnEmptyClass() {
+      return anEmptyClass;
+    }
+
+    public void setAnEmptyClass(@Nullable AnEmptyClass setterArg) {
+      this.anEmptyClass = setterArg;
+    }
+
     /** Constructor is non-public to enforce null safety; use Builder. */
     AllClassesWrapper() {}
 
@@ -3024,7 +3082,8 @@
           && pigeonDeepEquals(classList, that.classList)
           && pigeonDeepEquals(nullableClassList, that.nullableClassList)
           && pigeonDeepEquals(classMap, that.classMap)
-          && pigeonDeepEquals(nullableClassMap, that.nullableClassMap);
+          && pigeonDeepEquals(nullableClassMap, that.nullableClassMap)
+          && pigeonDeepEquals(anEmptyClass, that.anEmptyClass);
     }
 
     @Override
@@ -3038,7 +3097,8 @@
             classList,
             nullableClassList,
             classMap,
-            nullableClassMap
+            nullableClassMap,
+            anEmptyClass
           };
       return pigeonDeepHashCode(fields);
     }
@@ -3066,6 +3126,9 @@
           + ", "
           + "nullableClassMap="
           + nullableClassMap
+          + ", "
+          + "anEmptyClass="
+          + anEmptyClass
           + "}";
     }
 
@@ -3130,6 +3193,14 @@
         return this;
       }
 
+      private @Nullable AnEmptyClass anEmptyClass;
+
+      @CanIgnoreReturnValue
+      public @NonNull Builder setAnEmptyClass(@Nullable AnEmptyClass setterArg) {
+        this.anEmptyClass = setterArg;
+        return this;
+      }
+
       public @NonNull AllClassesWrapper build() {
         AllClassesWrapper pigeonReturn = new AllClassesWrapper();
         pigeonReturn.setAllNullableTypes(allNullableTypes);
@@ -3139,13 +3210,14 @@
         pigeonReturn.setNullableClassList(nullableClassList);
         pigeonReturn.setClassMap(classMap);
         pigeonReturn.setNullableClassMap(nullableClassMap);
+        pigeonReturn.setAnEmptyClass(anEmptyClass);
         return pigeonReturn;
       }
     }
 
     @NonNull
     ArrayList<Object> toList() {
-      ArrayList<Object> toListResult = new ArrayList<>(7);
+      ArrayList<Object> toListResult = new ArrayList<>(8);
       toListResult.add(allNullableTypes);
       toListResult.add(allNullableTypesWithoutRecursion);
       toListResult.add(allTypes);
@@ -3153,6 +3225,7 @@
       toListResult.add(nullableClassList);
       toListResult.add(classMap);
       toListResult.add(nullableClassMap);
+      toListResult.add(anEmptyClass);
       return toListResult;
     }
 
@@ -3174,6 +3247,8 @@
       Object nullableClassMap = pigeonVar_list.get(6);
       pigeonResult.setNullableClassMap(
           (Map<Long, AllNullableTypesWithoutRecursion>) nullableClassMap);
+      Object anEmptyClass = pigeonVar_list.get(7);
+      pigeonResult.setAnEmptyClass((AnEmptyClass) anEmptyClass);
       return pigeonResult;
     }
   }
@@ -3276,8 +3351,10 @@
         case (byte) 134:
           return AllNullableTypesWithoutRecursion.fromList((ArrayList<Object>) readValue(buffer));
         case (byte) 135:
-          return AllClassesWrapper.fromList((ArrayList<Object>) readValue(buffer));
+          return AnEmptyClass.fromList((ArrayList<Object>) readValue(buffer));
         case (byte) 136:
+          return AllClassesWrapper.fromList((ArrayList<Object>) readValue(buffer));
+        case (byte) 137:
           return TestMessage.fromList((ArrayList<Object>) readValue(buffer));
         default:
           return super.readValueOfType(type, buffer);
@@ -3304,11 +3381,14 @@
       } else if (value instanceof AllNullableTypesWithoutRecursion) {
         stream.write(134);
         writeValue(stream, ((AllNullableTypesWithoutRecursion) value).toList());
-      } else if (value instanceof AllClassesWrapper) {
+      } else if (value instanceof AnEmptyClass) {
         stream.write(135);
+        writeValue(stream, ((AnEmptyClass) value).toList());
+      } else if (value instanceof AllClassesWrapper) {
+        stream.write(136);
         writeValue(stream, ((AllClassesWrapper) value).toList());
       } else if (value instanceof TestMessage) {
-        stream.write(136);
+        stream.write(137);
         writeValue(stream, ((TestMessage) value).toList());
       } else {
         super.writeValue(stream, value);
diff --git a/packages/pigeon/platform_tests/alternate_language_test_plugin/darwin/alternate_language_test_plugin/Sources/alternate_language_test_plugin/AlternateLanguageTestPlugin.m b/packages/pigeon/platform_tests/alternate_language_test_plugin/darwin/alternate_language_test_plugin/Sources/alternate_language_test_plugin/AlternateLanguageTestPlugin.m
index 0778fcb..eca8cbe 100644
--- a/packages/pigeon/platform_tests/alternate_language_test_plugin/darwin/alternate_language_test_plugin/Sources/alternate_language_test_plugin/AlternateLanguageTestPlugin.m
+++ b/packages/pigeon/platform_tests/alternate_language_test_plugin/darwin/alternate_language_test_plugin/Sources/alternate_language_test_plugin/AlternateLanguageTestPlugin.m
@@ -254,7 +254,8 @@
                                               classList:@[]
                                       nullableClassList:nil
                                                classMap:@{}
-                                       nullableClassMap:nil];
+                                       nullableClassMap:nil
+                                           anEmptyClass:nil];
 }
 
 - (nullable FLTAllNullableTypes *)
diff --git a/packages/pigeon/platform_tests/alternate_language_test_plugin/darwin/alternate_language_test_plugin/Sources/alternate_language_test_plugin/CoreTests.gen.m b/packages/pigeon/platform_tests/alternate_language_test_plugin/darwin/alternate_language_test_plugin/Sources/alternate_language_test_plugin/CoreTests.gen.m
index 9692e44..60799e5 100644
--- a/packages/pigeon/platform_tests/alternate_language_test_plugin/darwin/alternate_language_test_plugin/Sources/alternate_language_test_plugin/CoreTests.gen.m
+++ b/packages/pigeon/platform_tests/alternate_language_test_plugin/darwin/alternate_language_test_plugin/Sources/alternate_language_test_plugin/CoreTests.gen.m
@@ -171,6 +171,12 @@
 - (NSArray<id> *)toList;
 @end
 
+@interface FLTAnEmptyClass ()
++ (FLTAnEmptyClass *)fromList:(NSArray<id> *)list;
++ (nullable FLTAnEmptyClass *)nullableFromList:(NSArray<id> *)list;
+- (NSArray<id> *)toList;
+@end
+
 @interface FLTAllClassesWrapper ()
 + (FLTAllClassesWrapper *)fromList:(NSArray<id> *)list;
 + (nullable FLTAllClassesWrapper *)nullableFromList:(NSArray<id> *)list;
@@ -896,6 +902,36 @@
 }
 @end
 
+@implementation FLTAnEmptyClass
++ (FLTAnEmptyClass *)fromList:(NSArray<id> *)list {
+  FLTAnEmptyClass *pigeonResult = [[FLTAnEmptyClass alloc] init];
+  return pigeonResult;
+}
++ (nullable FLTAnEmptyClass *)nullableFromList:(NSArray<id> *)list {
+  return (list) ? [FLTAnEmptyClass fromList:list] : nil;
+}
+- (NSArray<id> *)toList {
+  return @[];
+}
+- (BOOL)isEqual:(id)object {
+  if (self == object) {
+    return YES;
+  }
+  if (![object isKindOfClass:[self class]]) {
+    return NO;
+  }
+  return YES;
+}
+
+- (NSUInteger)hash {
+  NSUInteger result = [self class].hash;
+  return result;
+}
+- (NSString *)description {
+  return [NSString stringWithFormat:@"FLTAnEmptyClass()"];
+}
+@end
+
 @implementation FLTAllClassesWrapper
 + (instancetype)
             makeWithAllNullableTypes:(FLTAllNullableTypes *)allNullableTypes
@@ -908,7 +944,8 @@
                             classMap:(NSDictionary<NSNumber *, FLTAllTypes *> *)classMap
                     nullableClassMap:
                         (nullable NSDictionary<NSNumber *, FLTAllNullableTypesWithoutRecursion *> *)
-                            nullableClassMap {
+                            nullableClassMap
+                        anEmptyClass:(nullable FLTAnEmptyClass *)anEmptyClass {
   FLTAllClassesWrapper *pigeonResult = [[FLTAllClassesWrapper alloc] init];
   pigeonResult.allNullableTypes = allNullableTypes;
   pigeonResult.allNullableTypesWithoutRecursion = allNullableTypesWithoutRecursion;
@@ -917,6 +954,7 @@
   pigeonResult.nullableClassList = nullableClassList;
   pigeonResult.classMap = classMap;
   pigeonResult.nullableClassMap = nullableClassMap;
+  pigeonResult.anEmptyClass = anEmptyClass;
   return pigeonResult;
 }
 + (FLTAllClassesWrapper *)fromList:(NSArray<id> *)list {
@@ -928,6 +966,7 @@
   pigeonResult.nullableClassList = GetNullableObjectAtIndex(list, 4);
   pigeonResult.classMap = GetNullableObjectAtIndex(list, 5);
   pigeonResult.nullableClassMap = GetNullableObjectAtIndex(list, 6);
+  pigeonResult.anEmptyClass = GetNullableObjectAtIndex(list, 7);
   return pigeonResult;
 }
 + (nullable FLTAllClassesWrapper *)nullableFromList:(NSArray<id> *)list {
@@ -942,6 +981,7 @@
     self.nullableClassList ?: [NSNull null],
     self.classMap ?: [NSNull null],
     self.nullableClassMap ?: [NSNull null],
+    self.anEmptyClass ?: [NSNull null],
   ];
 }
 - (BOOL)isEqual:(id)object {
@@ -959,7 +999,8 @@
          FLTPigeonDeepEquals(self.classList, other.classList) &&
          FLTPigeonDeepEquals(self.nullableClassList, other.nullableClassList) &&
          FLTPigeonDeepEquals(self.classMap, other.classMap) &&
-         FLTPigeonDeepEquals(self.nullableClassMap, other.nullableClassMap);
+         FLTPigeonDeepEquals(self.nullableClassMap, other.nullableClassMap) &&
+         FLTPigeonDeepEquals(self.anEmptyClass, other.anEmptyClass);
 }
 
 - (NSUInteger)hash {
@@ -971,16 +1012,18 @@
   result = result * 31 + FLTPigeonDeepHash(self.nullableClassList);
   result = result * 31 + FLTPigeonDeepHash(self.classMap);
   result = result * 31 + FLTPigeonDeepHash(self.nullableClassMap);
+  result = result * 31 + FLTPigeonDeepHash(self.anEmptyClass);
   return result;
 }
 - (NSString *)description {
   return
-      [NSString stringWithFormat:@"FLTAllClassesWrapper(allNullableTypes: %@, "
-                                 @"allNullableTypesWithoutRecursion: %@, allTypes: %@, classList: "
-                                 @"%@, nullableClassList: %@, classMap: %@, nullableClassMap: %@)",
-                                 self.allNullableTypes, self.allNullableTypesWithoutRecursion,
-                                 self.allTypes, self.classList, self.nullableClassList,
-                                 self.classMap, self.nullableClassMap];
+      [NSString stringWithFormat:
+                    @"FLTAllClassesWrapper(allNullableTypes: %@, allNullableTypesWithoutRecursion: "
+                    @"%@, allTypes: %@, classList: %@, nullableClassList: %@, classMap: %@, "
+                    @"nullableClassMap: %@, anEmptyClass: %@)",
+                    self.allNullableTypes, self.allNullableTypesWithoutRecursion, self.allTypes,
+                    self.classList, self.nullableClassList, self.classMap, self.nullableClassMap,
+                    self.anEmptyClass];
 }
 @end
 
@@ -1049,8 +1092,10 @@
     case 134:
       return [FLTAllNullableTypesWithoutRecursion fromList:[self readValue]];
     case 135:
-      return [FLTAllClassesWrapper fromList:[self readValue]];
+      return [FLTAnEmptyClass fromList:[self readValue]];
     case 136:
+      return [FLTAllClassesWrapper fromList:[self readValue]];
+    case 137:
       return [FLTTestMessage fromList:[self readValue]];
     default:
       return [super readValueOfType:type];
@@ -1082,12 +1127,15 @@
   } else if ([value isKindOfClass:[FLTAllNullableTypesWithoutRecursion class]]) {
     [self writeByte:134];
     [self writeValue:[value toList]];
-  } else if ([value isKindOfClass:[FLTAllClassesWrapper class]]) {
+  } else if ([value isKindOfClass:[FLTAnEmptyClass class]]) {
     [self writeByte:135];
     [self writeValue:[value toList]];
-  } else if ([value isKindOfClass:[FLTTestMessage class]]) {
+  } else if ([value isKindOfClass:[FLTAllClassesWrapper class]]) {
     [self writeByte:136];
     [self writeValue:[value toList]];
+  } else if ([value isKindOfClass:[FLTTestMessage class]]) {
+    [self writeByte:137];
+    [self writeValue:[value toList]];
   } else {
     [super writeValue:value];
   }
diff --git a/packages/pigeon/platform_tests/alternate_language_test_plugin/darwin/alternate_language_test_plugin/Sources/alternate_language_test_plugin/include/alternate_language_test_plugin/CoreTests.gen.h b/packages/pigeon/platform_tests/alternate_language_test_plugin/darwin/alternate_language_test_plugin/Sources/alternate_language_test_plugin/include/alternate_language_test_plugin/CoreTests.gen.h
index 03be8eb..6dc4e80 100644
--- a/packages/pigeon/platform_tests/alternate_language_test_plugin/darwin/alternate_language_test_plugin/Sources/alternate_language_test_plugin/include/alternate_language_test_plugin/CoreTests.gen.h
+++ b/packages/pigeon/platform_tests/alternate_language_test_plugin/darwin/alternate_language_test_plugin/Sources/alternate_language_test_plugin/include/alternate_language_test_plugin/CoreTests.gen.h
@@ -42,6 +42,7 @@
 @class FLTAllTypes;
 @class FLTAllNullableTypes;
 @class FLTAllNullableTypesWithoutRecursion;
+@class FLTAnEmptyClass;
 @class FLTAllClassesWrapper;
 @class FLTTestMessage;
 
@@ -244,6 +245,10 @@
 @property(nonatomic, copy, nullable) NSDictionary<NSNumber *, NSDictionary<id, id> *> *mapMap;
 @end
 
+/// A data class without fields for testing empty classes.
+@interface FLTAnEmptyClass : NSObject
+@end
+
 /// A class for testing nested class handling.
 ///
 /// This is needed to test nested nullable and non-nullable classes,
@@ -263,7 +268,8 @@
                             classMap:(NSDictionary<NSNumber *, FLTAllTypes *> *)classMap
                     nullableClassMap:
                         (nullable NSDictionary<NSNumber *, FLTAllNullableTypesWithoutRecursion *> *)
-                            nullableClassMap;
+                            nullableClassMap
+                        anEmptyClass:(nullable FLTAnEmptyClass *)anEmptyClass;
 @property(nonatomic, strong) FLTAllNullableTypes *allNullableTypes;
 @property(nonatomic, strong, nullable)
     FLTAllNullableTypesWithoutRecursion *allNullableTypesWithoutRecursion;
@@ -274,6 +280,7 @@
 @property(nonatomic, copy) NSDictionary<NSNumber *, FLTAllTypes *> *classMap;
 @property(nonatomic, copy, nullable)
     NSDictionary<NSNumber *, FLTAllNullableTypesWithoutRecursion *> *nullableClassMap;
+@property(nonatomic, strong, nullable) FLTAnEmptyClass *anEmptyClass;
 @end
 
 /// A data class containing a List, used in unit tests.
diff --git a/packages/pigeon/platform_tests/shared_test_plugin_code/lib/integration_tests.dart b/packages/pigeon/platform_tests/shared_test_plugin_code/lib/integration_tests.dart
index 6928aae..5c24c97 100644
--- a/packages/pigeon/platform_tests/shared_test_plugin_code/lib/integration_tests.dart
+++ b/packages/pigeon/platform_tests/shared_test_plugin_code/lib/integration_tests.dart
@@ -2768,6 +2768,9 @@
           expect(event.value.aNullableInt, 0);
           expect(count, 6);
           count++;
+        case EmptyEvent():
+          expect(count, 7);
+          count++;
           completer.complete();
       }
     });
diff --git a/packages/pigeon/platform_tests/shared_test_plugin_code/lib/src/generated/core_tests.gen.dart b/packages/pigeon/platform_tests/shared_test_plugin_code/lib/src/generated/core_tests.gen.dart
index c1164b5..7f0a46e 100644
--- a/packages/pigeon/platform_tests/shared_test_plugin_code/lib/src/generated/core_tests.gen.dart
+++ b/packages/pigeon/platform_tests/shared_test_plugin_code/lib/src/generated/core_tests.gen.dart
@@ -800,6 +800,42 @@
   }
 }
 
+/// A data class without fields for testing empty classes.
+class AnEmptyClass {
+  AnEmptyClass();
+
+  List<Object?> _toList() {
+    return <Object?>[];
+  }
+
+  Object encode() {
+    return _toList();
+  }
+
+  static AnEmptyClass decode(Object result) {
+    result as List<Object?>;
+    return AnEmptyClass();
+  }
+
+  @override
+  // ignore: avoid_equals_and_hash_code_on_mutable_classes
+  bool operator ==(Object other) {
+    if (other is! AnEmptyClass || other.runtimeType != runtimeType) {
+      return false;
+    }
+    return true;
+  }
+
+  @override
+  // ignore: avoid_equals_and_hash_code_on_mutable_classes
+  int get hashCode => _deepHash(<Object?>[runtimeType, ..._toList()]);
+
+  @override
+  String toString() {
+    return 'AnEmptyClass()';
+  }
+}
+
 /// A class for testing nested class handling.
 ///
 /// This is needed to test nested nullable and non-nullable classes,
@@ -814,6 +850,7 @@
     this.nullableClassList,
     required this.classMap,
     this.nullableClassMap,
+    this.anEmptyClass,
   });
 
   AllNullableTypes allNullableTypes;
@@ -830,6 +867,8 @@
 
   Map<int?, AllNullableTypesWithoutRecursion?>? nullableClassMap;
 
+  AnEmptyClass? anEmptyClass;
+
   List<Object?> _toList() {
     return <Object?>[
       allNullableTypes,
@@ -839,6 +878,7 @@
       nullableClassList,
       classMap,
       nullableClassMap,
+      anEmptyClass,
     ];
   }
 
@@ -857,6 +897,7 @@
       classMap: (result[5]! as Map<Object?, Object?>).cast<int?, AllTypes?>(),
       nullableClassMap: (result[6] as Map<Object?, Object?>?)
           ?.cast<int?, AllNullableTypesWithoutRecursion?>(),
+      anEmptyClass: result[7] as AnEmptyClass?,
     );
   }
 
@@ -875,7 +916,8 @@
         _deepEquals(classList, other.classList) &&
         _deepEquals(nullableClassList, other.nullableClassList) &&
         _deepEquals(classMap, other.classMap) &&
-        _deepEquals(nullableClassMap, other.nullableClassMap);
+        _deepEquals(nullableClassMap, other.nullableClassMap) &&
+        _deepEquals(anEmptyClass, other.anEmptyClass);
   }
 
   @override
@@ -884,7 +926,7 @@
 
   @override
   String toString() {
-    return 'AllClassesWrapper(allNullableTypes: $allNullableTypes, allNullableTypesWithoutRecursion: $allNullableTypesWithoutRecursion, allTypes: $allTypes, classList: $classList, nullableClassList: $nullableClassList, classMap: $classMap, nullableClassMap: $nullableClassMap)';
+    return 'AllClassesWrapper(allNullableTypes: $allNullableTypes, allNullableTypesWithoutRecursion: $allNullableTypesWithoutRecursion, allTypes: $allTypes, classList: $classList, nullableClassList: $nullableClassList, classMap: $classMap, nullableClassMap: $nullableClassMap, anEmptyClass: $anEmptyClass)';
   }
 }
 
@@ -954,12 +996,15 @@
     } else if (value is AllNullableTypesWithoutRecursion) {
       buffer.putUint8(134);
       writeValue(buffer, value.encode());
-    } else if (value is AllClassesWrapper) {
+    } else if (value is AnEmptyClass) {
       buffer.putUint8(135);
       writeValue(buffer, value.encode());
-    } else if (value is TestMessage) {
+    } else if (value is AllClassesWrapper) {
       buffer.putUint8(136);
       writeValue(buffer, value.encode());
+    } else if (value is TestMessage) {
+      buffer.putUint8(137);
+      writeValue(buffer, value.encode());
     } else {
       super.writeValue(buffer, value);
     }
@@ -983,8 +1028,10 @@
       case 134:
         return AllNullableTypesWithoutRecursion.decode(readValue(buffer)!);
       case 135:
-        return AllClassesWrapper.decode(readValue(buffer)!);
+        return AnEmptyClass.decode(readValue(buffer)!);
       case 136:
+        return AllClassesWrapper.decode(readValue(buffer)!);
+      case 137:
         return TestMessage.decode(readValue(buffer)!);
       default:
         return super.readValueOfType(type, buffer);
diff --git a/packages/pigeon/platform_tests/shared_test_plugin_code/lib/src/generated/event_channel_tests.gen.dart b/packages/pigeon/platform_tests/shared_test_plugin_code/lib/src/generated/event_channel_tests.gen.dart
index 9602837..8a3a4a0 100644
--- a/packages/pigeon/platform_tests/shared_test_plugin_code/lib/src/generated/event_channel_tests.gen.dart
+++ b/packages/pigeon/platform_tests/shared_test_plugin_code/lib/src/generated/event_channel_tests.gen.dart
@@ -588,6 +588,41 @@
   }
 }
 
+class EmptyEvent extends PlatformEvent {
+  EmptyEvent();
+
+  List<Object?> _toList() {
+    return <Object?>[];
+  }
+
+  Object encode() {
+    return _toList();
+  }
+
+  static EmptyEvent decode(Object result) {
+    result as List<Object?>;
+    return EmptyEvent();
+  }
+
+  @override
+  // ignore: avoid_equals_and_hash_code_on_mutable_classes
+  bool operator ==(Object other) {
+    if (other is! EmptyEvent || other.runtimeType != runtimeType) {
+      return false;
+    }
+    return true;
+  }
+
+  @override
+  // ignore: avoid_equals_and_hash_code_on_mutable_classes
+  int get hashCode => _deepHash(<Object?>[runtimeType, ..._toList()]);
+
+  @override
+  String toString() {
+    return 'EmptyEvent()';
+  }
+}
+
 class _PigeonCodec extends StandardMessageCodec {
   const _PigeonCodec();
   @override
@@ -625,6 +660,9 @@
     } else if (value is ClassEvent) {
       buffer.putUint8(138);
       writeValue(buffer, value.encode());
+    } else if (value is EmptyEvent) {
+      buffer.putUint8(139);
+      writeValue(buffer, value.encode());
     } else {
       super.writeValue(buffer, value);
     }
@@ -655,6 +693,8 @@
         return EnumEvent.decode(readValue(buffer)!);
       case 138:
         return ClassEvent.decode(readValue(buffer)!);
+      case 139:
+        return EmptyEvent.decode(readValue(buffer)!);
       default:
         return super.readValueOfType(type, buffer);
     }
diff --git a/packages/pigeon/platform_tests/shared_test_plugin_code/test/data_class_methods_test.dart b/packages/pigeon/platform_tests/shared_test_plugin_code/test/data_class_methods_test.dart
index 33407ef..1327fbc 100644
--- a/packages/pigeon/platform_tests/shared_test_plugin_code/test/data_class_methods_test.dart
+++ b/packages/pigeon/platform_tests/shared_test_plugin_code/test/data_class_methods_test.dart
@@ -199,5 +199,13 @@
         'AllTypes(aBool: true, anInt: 42, anInt64: 3000000000, aDouble: 3.14159, aByteArray: [1, 2, 3], a4ByteArray: [4, 5, 6], a8ByteArray: [7, 8, 9], aFloatArray: [2.71828, 3.14159], anEnum: AnEnum.fortyTwo, anotherEnum: AnotherEnum.justInCase, aString: Hello host!, anObject: 1, list: [Thing 1, 2, true, 3.14, null], stringList: [Thing 1, 2, true, 3.14], intList: [1, 2, 3, 4], doubleList: [1.0, 2.99999, 3.0, 3.14], boolList: [true, false, true, false], enumList: [AnEnum.one, AnEnum.two, AnEnum.three, AnEnum.fortyTwo, AnEnum.fourHundredTwentyTwo], objectList: [Thing 1, 2, true, 3.14], listList: [[Thing 1, 2, true, 3.14], [Thing 1, 2, true, 3.14], [1, 2, 3, 4], [1.0, 2.99999, 3.0, 3.14], [true, false, true, false], [AnEnum.one, AnEnum.two, AnEnum.three, AnEnum.fortyTwo, AnEnum.fourHundredTwentyTwo]], mapList: [{a: 1, b: 2.0, c: three, d: false}, {a: 1, b: 2.0, c: three, d: false}, {0.0: 0.0, 1.1: 2.0, 3.0: 0.3, -0.4: -0.2}, {0: 0, 1: 1, 2: 3, 4: -1}, {0: true, 1: false, 2: true}, {AnEnum.one: AnEnum.one, AnEnum.two: AnEnum.two, AnEnum.three: AnEnum.three, AnEnum.fortyTwo: AnEnum.fortyTwo}], map: {a: 1, b: 2.0, c: three, d: false}, stringMap: {a: 1, b: 2.0, c: three, d: false}, intMap: {0: 0, 1: 1, 2: 3, 4: -1}, enumMap: {AnEnum.one: AnEnum.one, AnEnum.two: AnEnum.two, AnEnum.three: AnEnum.three, AnEnum.fortyTwo: AnEnum.fortyTwo}, objectMap: {a: 1, b: 2.0, c: three, d: false}, listMap: {0: [Thing 1, 2, true, 3.14], 1: [Thing 1, 2, true, 3.14], 2: [1.0, 2.99999, 3.0, 3.14], 4: [1, 2, 3, 4], 5: [true, false, true, false], 6: [AnEnum.one, AnEnum.two, AnEnum.three, AnEnum.fortyTwo, AnEnum.fourHundredTwentyTwo]}, mapMap: {0: {a: 1, b: 2.0, c: three, d: false}, 1: {a: 1, b: 2.0, c: three, d: false}, 2: {0.0: 0.0, 1.1: 2.0, 3.0: 0.3, -0.4: -0.2}, 4: {0: 0, 1: 1, 2: 3, 4: -1}, 5: {0: true, 1: false, 2: true}, 6: {AnEnum.one: AnEnum.one, AnEnum.two: AnEnum.two, AnEnum.three: AnEnum.three, AnEnum.fortyTwo: AnEnum.fortyTwo}})',
       );
     });
+
+    test('AnEmptyClass basic methods', () {
+      final a = AnEmptyClass();
+      final b = AnEmptyClass();
+      expect(a, b);
+      expect(a.hashCode, b.hashCode);
+      expect(a.toString(), 'AnEmptyClass()');
+    });
   });
 }
diff --git a/packages/pigeon/platform_tests/test_plugin/android/src/main/kotlin/com/example/test_plugin/CoreTests.gen.kt b/packages/pigeon/platform_tests/test_plugin/android/src/main/kotlin/com/example/test_plugin/CoreTests.gen.kt
index a22c40a..25ff604 100644
--- a/packages/pigeon/platform_tests/test_plugin/android/src/main/kotlin/com/example/test_plugin/CoreTests.gen.kt
+++ b/packages/pigeon/platform_tests/test_plugin/android/src/main/kotlin/com/example/test_plugin/CoreTests.gen.kt
@@ -895,6 +895,42 @@
 }
 
 /**
+ * A data class without fields for testing empty classes.
+ *
+ * Generated class from Pigeon that represents data sent in messages.
+ */
+class AnEmptyClass {
+  companion object {
+    fun fromList(pigeonVar_list: List<Any?>): AnEmptyClass {
+      return AnEmptyClass()
+    }
+  }
+
+  fun toList(): List<Any?> {
+    return listOf()
+  }
+
+  override fun equals(other: Any?): Boolean {
+    if (other == null || other.javaClass != javaClass) {
+      return false
+    }
+    if (this === other) {
+      return true
+    }
+    return true
+  }
+
+  override fun hashCode(): Int {
+    var result = javaClass.hashCode()
+    return result
+  }
+
+  override fun toString(): String {
+    return "AnEmptyClass()"
+  }
+}
+
+/**
  * A class for testing nested class handling.
  *
  * This is needed to test nested nullable and non-nullable classes, `AllNullableTypes` is
@@ -910,7 +946,8 @@
     val classList: List<AllTypes?>,
     val nullableClassList: List<AllNullableTypesWithoutRecursion?>? = null,
     val classMap: Map<Long?, AllTypes?>,
-    val nullableClassMap: Map<Long?, AllNullableTypesWithoutRecursion?>? = null
+    val nullableClassMap: Map<Long?, AllNullableTypesWithoutRecursion?>? = null,
+    val anEmptyClass: AnEmptyClass? = null
 ) {
   companion object {
     fun fromList(pigeonVar_list: List<Any?>): AllClassesWrapper {
@@ -921,6 +958,7 @@
       val nullableClassList = pigeonVar_list[4] as List<AllNullableTypesWithoutRecursion?>?
       val classMap = pigeonVar_list[5] as Map<Long?, AllTypes?>
       val nullableClassMap = pigeonVar_list[6] as Map<Long?, AllNullableTypesWithoutRecursion?>?
+      val anEmptyClass = pigeonVar_list[7] as AnEmptyClass?
       return AllClassesWrapper(
           allNullableTypes,
           allNullableTypesWithoutRecursion,
@@ -928,7 +966,8 @@
           classList,
           nullableClassList,
           classMap,
-          nullableClassMap)
+          nullableClassMap,
+          anEmptyClass)
     }
   }
 
@@ -941,6 +980,7 @@
         nullableClassList,
         classMap,
         nullableClassMap,
+        anEmptyClass,
     )
   }
 
@@ -959,7 +999,8 @@
         CoreTestsPigeonUtils.deepEquals(this.classList, other.classList) &&
         CoreTestsPigeonUtils.deepEquals(this.nullableClassList, other.nullableClassList) &&
         CoreTestsPigeonUtils.deepEquals(this.classMap, other.classMap) &&
-        CoreTestsPigeonUtils.deepEquals(this.nullableClassMap, other.nullableClassMap)
+        CoreTestsPigeonUtils.deepEquals(this.nullableClassMap, other.nullableClassMap) &&
+        CoreTestsPigeonUtils.deepEquals(this.anEmptyClass, other.anEmptyClass)
   }
 
   override fun hashCode(): Int {
@@ -971,11 +1012,12 @@
     result = 31 * result + CoreTestsPigeonUtils.deepHash(this.nullableClassList)
     result = 31 * result + CoreTestsPigeonUtils.deepHash(this.classMap)
     result = 31 * result + CoreTestsPigeonUtils.deepHash(this.nullableClassMap)
+    result = 31 * result + CoreTestsPigeonUtils.deepHash(this.anEmptyClass)
     return result
   }
 
   override fun toString(): String {
-    return "AllClassesWrapper(allNullableTypes=$allNullableTypes, allNullableTypesWithoutRecursion=$allNullableTypesWithoutRecursion, allTypes=$allTypes, classList=$classList, nullableClassList=$nullableClassList, classMap=$classMap, nullableClassMap=$nullableClassMap)"
+    return "AllClassesWrapper(allNullableTypes=$allNullableTypes, allNullableTypesWithoutRecursion=$allNullableTypesWithoutRecursion, allTypes=$allTypes, classList=$classList, nullableClassList=$nullableClassList, classMap=$classMap, nullableClassMap=$nullableClassMap, anEmptyClass=$anEmptyClass)"
   }
 }
 
@@ -1044,9 +1086,12 @@
         }
       }
       135.toByte() -> {
-        return (readValue(buffer) as? List<Any?>)?.let { AllClassesWrapper.fromList(it) }
+        return (readValue(buffer) as? List<Any?>)?.let { AnEmptyClass.fromList(it) }
       }
       136.toByte() -> {
+        return (readValue(buffer) as? List<Any?>)?.let { AllClassesWrapper.fromList(it) }
+      }
+      137.toByte() -> {
         return (readValue(buffer) as? List<Any?>)?.let { TestMessage.fromList(it) }
       }
       else -> super.readValueOfType(type, buffer)
@@ -1079,14 +1124,18 @@
         stream.write(134)
         writeValue(stream, value.toList())
       }
-      is AllClassesWrapper -> {
+      is AnEmptyClass -> {
         stream.write(135)
         writeValue(stream, value.toList())
       }
-      is TestMessage -> {
+      is AllClassesWrapper -> {
         stream.write(136)
         writeValue(stream, value.toList())
       }
+      is TestMessage -> {
+        stream.write(137)
+        writeValue(stream, value.toList())
+      }
       else -> super.writeValue(stream, value)
     }
   }
diff --git a/packages/pigeon/platform_tests/test_plugin/android/src/main/kotlin/com/example/test_plugin/EventChannelTests.gen.kt b/packages/pigeon/platform_tests/test_plugin/android/src/main/kotlin/com/example/test_plugin/EventChannelTests.gen.kt
index a9f3d65..cd76dc0 100644
--- a/packages/pigeon/platform_tests/test_plugin/android/src/main/kotlin/com/example/test_plugin/EventChannelTests.gen.kt
+++ b/packages/pigeon/platform_tests/test_plugin/android/src/main/kotlin/com/example/test_plugin/EventChannelTests.gen.kt
@@ -692,6 +692,38 @@
   }
 }
 
+/** Generated class from Pigeon that represents data sent in messages. */
+class EmptyEvent : PlatformEvent() {
+  companion object {
+    fun fromList(pigeonVar_list: List<Any?>): EmptyEvent {
+      return EmptyEvent()
+    }
+  }
+
+  fun toList(): List<Any?> {
+    return listOf()
+  }
+
+  override fun equals(other: Any?): Boolean {
+    if (other == null || other.javaClass != javaClass) {
+      return false
+    }
+    if (this === other) {
+      return true
+    }
+    return true
+  }
+
+  override fun hashCode(): Int {
+    var result = javaClass.hashCode()
+    return result
+  }
+
+  override fun toString(): String {
+    return "EmptyEvent()"
+  }
+}
+
 private open class EventChannelTestsPigeonCodec : StandardMessageCodec() {
   override fun readValueOfType(type: Byte, buffer: ByteBuffer): Any? {
     return when (type) {
@@ -725,6 +757,9 @@
       138.toByte() -> {
         return (readValue(buffer) as? List<Any?>)?.let { ClassEvent.fromList(it) }
       }
+      139.toByte() -> {
+        return (readValue(buffer) as? List<Any?>)?.let { EmptyEvent.fromList(it) }
+      }
       else -> super.readValueOfType(type, buffer)
     }
   }
@@ -771,6 +806,10 @@
         stream.write(138)
         writeValue(stream, value.toList())
       }
+      is EmptyEvent -> {
+        stream.write(139)
+        writeValue(stream, value.toList())
+      }
       else -> super.writeValue(stream, value)
     }
   }
diff --git a/packages/pigeon/platform_tests/test_plugin/android/src/main/kotlin/com/example/test_plugin/TestPlugin.kt b/packages/pigeon/platform_tests/test_plugin/android/src/main/kotlin/com/example/test_plugin/TestPlugin.kt
index dd42cb4..8373ef7 100644
--- a/packages/pigeon/platform_tests/test_plugin/android/src/main/kotlin/com/example/test_plugin/TestPlugin.kt
+++ b/packages/pigeon/platform_tests/test_plugin/android/src/main/kotlin/com/example/test_plugin/TestPlugin.kt
@@ -948,7 +948,8 @@
           DoubleEvent(3.14),
           ObjectsEvent(true),
           EnumEvent(EventEnum.FORTY_TWO),
-          ClassEvent(EventAllNullableTypes(aNullableInt = 0)))
+          ClassEvent(EventAllNullableTypes(aNullableInt = 0)),
+          EmptyEvent())
 
   override fun onListen(p0: Any?, sink: PigeonEventSink<PlatformEvent>) {
     var count: Int = 0
diff --git a/packages/pigeon/platform_tests/test_plugin/darwin/test_plugin/Sources/test_plugin/CoreTests.gen.swift b/packages/pigeon/platform_tests/test_plugin/darwin/test_plugin/Sources/test_plugin/CoreTests.gen.swift
index 3269407..1afcdbd 100644
--- a/packages/pigeon/platform_tests/test_plugin/darwin/test_plugin/Sources/test_plugin/CoreTests.gen.swift
+++ b/packages/pigeon/platform_tests/test_plugin/darwin/test_plugin/Sources/test_plugin/CoreTests.gen.swift
@@ -926,6 +926,35 @@
   }
 }
 
+/// A data class without fields for testing empty classes.
+///
+/// Generated class from Pigeon that represents data sent in messages.
+struct AnEmptyClass: Hashable, CustomStringConvertible {
+
+  // swift-format-ignore: AlwaysUseLowerCamelCase
+  static func fromList(_ pigeonVar_list: [Any?]) -> AnEmptyClass? {
+
+    return AnEmptyClass()
+  }
+  func toList() -> [Any?] {
+    return []
+  }
+  static func == (lhs: AnEmptyClass, rhs: AnEmptyClass) -> Bool {
+    if Swift.type(of: lhs) != Swift.type(of: rhs) {
+      return false
+    }
+    return true
+  }
+
+  func hash(into hasher: inout Hasher) {
+    hasher.combine("AnEmptyClass")
+  }
+
+  public var description: String {
+    return "AnEmptyClass()"
+  }
+}
+
 /// A class for testing nested class handling.
 ///
 /// This is needed to test nested nullable and non-nullable classes,
@@ -941,6 +970,7 @@
   var nullableClassList: [AllNullableTypesWithoutRecursion?]? = nil
   var classMap: [Int64?: AllTypes?]
   var nullableClassMap: [Int64?: AllNullableTypesWithoutRecursion?]? = nil
+  var anEmptyClass: AnEmptyClass? = nil
 
   // swift-format-ignore: AlwaysUseLowerCamelCase
   static func fromList(_ pigeonVar_list: [Any?]) -> AllClassesWrapper? {
@@ -953,6 +983,7 @@
     let classMap = pigeonVar_list[5] as! [Int64?: AllTypes?]
     let nullableClassMap: [Int64?: AllNullableTypesWithoutRecursion?]? = nilOrValue(
       pigeonVar_list[6])
+    let anEmptyClass: AnEmptyClass? = nilOrValue(pigeonVar_list[7])
 
     return AllClassesWrapper(
       allNullableTypes: allNullableTypes,
@@ -961,7 +992,8 @@
       classList: classList,
       nullableClassList: nullableClassList,
       classMap: classMap,
-      nullableClassMap: nullableClassMap
+      nullableClassMap: nullableClassMap,
+      anEmptyClass: anEmptyClass
     )
   }
   func toList() -> [Any?] {
@@ -973,6 +1005,7 @@
       nullableClassList,
       classMap,
       nullableClassMap,
+      anEmptyClass,
     ]
   }
   static func == (lhs: AllClassesWrapper, rhs: AllClassesWrapper) -> Bool {
@@ -987,6 +1020,7 @@
       && CoreTestsPigeonInternal.deepEquals(lhs.nullableClassList, rhs.nullableClassList)
       && CoreTestsPigeonInternal.deepEquals(lhs.classMap, rhs.classMap)
       && CoreTestsPigeonInternal.deepEquals(lhs.nullableClassMap, rhs.nullableClassMap)
+      && CoreTestsPigeonInternal.deepEquals(lhs.anEmptyClass, rhs.anEmptyClass)
   }
 
   func hash(into hasher: inout Hasher) {
@@ -998,11 +1032,12 @@
     CoreTestsPigeonInternal.deepHash(value: nullableClassList, hasher: &hasher)
     CoreTestsPigeonInternal.deepHash(value: classMap, hasher: &hasher)
     CoreTestsPigeonInternal.deepHash(value: nullableClassMap, hasher: &hasher)
+    CoreTestsPigeonInternal.deepHash(value: anEmptyClass, hasher: &hasher)
   }
 
   public var description: String {
     return
-      "AllClassesWrapper(allNullableTypes: \(String(describing: allNullableTypes)), allNullableTypesWithoutRecursion: \(String(describing: allNullableTypesWithoutRecursion)), allTypes: \(String(describing: allTypes)), classList: \(String(describing: classList)), nullableClassList: \(String(describing: nullableClassList)), classMap: \(String(describing: classMap)), nullableClassMap: \(String(describing: nullableClassMap)))"
+      "AllClassesWrapper(allNullableTypes: \(String(describing: allNullableTypes)), allNullableTypesWithoutRecursion: \(String(describing: allNullableTypesWithoutRecursion)), allTypes: \(String(describing: allTypes)), classList: \(String(describing: classList)), nullableClassList: \(String(describing: nullableClassList)), classMap: \(String(describing: classMap)), nullableClassMap: \(String(describing: nullableClassMap)), anEmptyClass: \(String(describing: anEmptyClass)))"
   }
 }
 
@@ -1066,8 +1101,10 @@
     case 134:
       return AllNullableTypesWithoutRecursion.fromList(self.readValue() as! [Any?])
     case 135:
-      return AllClassesWrapper.fromList(self.readValue() as! [Any?])
+      return AnEmptyClass.fromList(self.readValue() as! [Any?])
     case 136:
+      return AllClassesWrapper.fromList(self.readValue() as! [Any?])
+    case 137:
       return TestMessage.fromList(self.readValue() as! [Any?])
     default:
       return super.readValue(ofType: type)
@@ -1095,12 +1132,15 @@
     } else if let value = value as? AllNullableTypesWithoutRecursion {
       super.writeByte(134)
       super.writeValue(value.toList())
-    } else if let value = value as? AllClassesWrapper {
+    } else if let value = value as? AnEmptyClass {
       super.writeByte(135)
       super.writeValue(value.toList())
-    } else if let value = value as? TestMessage {
+    } else if let value = value as? AllClassesWrapper {
       super.writeByte(136)
       super.writeValue(value.toList())
+    } else if let value = value as? TestMessage {
+      super.writeByte(137)
+      super.writeValue(value.toList())
     } else {
       super.writeValue(value)
     }
diff --git a/packages/pigeon/platform_tests/test_plugin/darwin/test_plugin/Sources/test_plugin/EventChannelTests.gen.swift b/packages/pigeon/platform_tests/test_plugin/darwin/test_plugin/Sources/test_plugin/EventChannelTests.gen.swift
index f659243..377ef25 100644
--- a/packages/pigeon/platform_tests/test_plugin/darwin/test_plugin/Sources/test_plugin/EventChannelTests.gen.swift
+++ b/packages/pigeon/platform_tests/test_plugin/darwin/test_plugin/Sources/test_plugin/EventChannelTests.gen.swift
@@ -706,6 +706,33 @@
   }
 }
 
+/// Generated class from Pigeon that represents data sent in messages.
+struct EmptyEvent: PlatformEvent {
+
+  // swift-format-ignore: AlwaysUseLowerCamelCase
+  static func fromList(_ pigeonVar_list: [Any?]) -> EmptyEvent? {
+
+    return EmptyEvent()
+  }
+  func toList() -> [Any?] {
+    return []
+  }
+  static func == (lhs: EmptyEvent, rhs: EmptyEvent) -> Bool {
+    if Swift.type(of: lhs) != Swift.type(of: rhs) {
+      return false
+    }
+    return true
+  }
+
+  func hash(into hasher: inout Hasher) {
+    hasher.combine("EmptyEvent")
+  }
+
+  public var description: String {
+    return "EmptyEvent()"
+  }
+}
+
 private class EventChannelTestsPigeonCodecReader: FlutterStandardReader {
   override func readValue(ofType type: UInt8) -> Any? {
     switch type {
@@ -737,6 +764,8 @@
       return EnumEvent.fromList(self.readValue() as! [Any?])
     case 138:
       return ClassEvent.fromList(self.readValue() as! [Any?])
+    case 139:
+      return EmptyEvent.fromList(self.readValue() as! [Any?])
     default:
       return super.readValue(ofType: type)
     }
@@ -775,6 +804,9 @@
     } else if let value = value as? ClassEvent {
       super.writeByte(138)
       super.writeValue(value.toList())
+    } else if let value = value as? EmptyEvent {
+      super.writeByte(139)
+      super.writeValue(value.toList())
     } else {
       super.writeValue(value)
     }
diff --git a/packages/pigeon/platform_tests/test_plugin/darwin/test_plugin/Sources/test_plugin/TestPlugin.swift b/packages/pigeon/platform_tests/test_plugin/darwin/test_plugin/Sources/test_plugin/TestPlugin.swift
index 95d6302..c74c5c2 100644
--- a/packages/pigeon/platform_tests/test_plugin/darwin/test_plugin/Sources/test_plugin/TestPlugin.swift
+++ b/packages/pigeon/platform_tests/test_plugin/darwin/test_plugin/Sources/test_plugin/TestPlugin.swift
@@ -1313,6 +1313,7 @@
       ObjectsEvent(value: true),
       EnumEvent(value: EventEnum.fortyTwo),
       ClassEvent(value: EventAllNullableTypes(aNullableInt: 0)),
+      EmptyEvent(),
     ]
 
   override func onListen(withArguments arguments: Any?, sink: PigeonEventSink<PlatformEvent>) {
diff --git a/packages/pigeon/platform_tests/test_plugin/linux/pigeon/core_tests.gen.cc b/packages/pigeon/platform_tests/test_plugin/linux/pigeon/core_tests.gen.cc
index d099ee1..fbe4c2f 100644
--- a/packages/pigeon/platform_tests/test_plugin/linux/pigeon/core_tests.gen.cc
+++ b/packages/pigeon/platform_tests/test_plugin/linux/pigeon/core_tests.gen.cc
@@ -3922,6 +3922,70 @@
   return g_string_free(str, FALSE);
 }
 
+struct _CoreTestsPigeonTestAnEmptyClass {
+  GObject parent_instance;
+};
+
+G_DEFINE_TYPE(CoreTestsPigeonTestAnEmptyClass,
+              core_tests_pigeon_test_an_empty_class, G_TYPE_OBJECT)
+
+static void core_tests_pigeon_test_an_empty_class_dispose(GObject* object) {
+  G_OBJECT_CLASS(core_tests_pigeon_test_an_empty_class_parent_class)
+      ->dispose(object);
+}
+
+static void core_tests_pigeon_test_an_empty_class_init(
+    CoreTestsPigeonTestAnEmptyClass* self) {}
+
+static void core_tests_pigeon_test_an_empty_class_class_init(
+    CoreTestsPigeonTestAnEmptyClassClass* klass) {
+  G_OBJECT_CLASS(klass)->dispose =
+      core_tests_pigeon_test_an_empty_class_dispose;
+}
+
+CoreTestsPigeonTestAnEmptyClass* core_tests_pigeon_test_an_empty_class_new() {
+  CoreTestsPigeonTestAnEmptyClass* self = CORE_TESTS_PIGEON_TEST_AN_EMPTY_CLASS(
+      g_object_new(core_tests_pigeon_test_an_empty_class_get_type(), nullptr));
+  return self;
+}
+
+static FlValue* core_tests_pigeon_test_an_empty_class_to_list(
+    CoreTestsPigeonTestAnEmptyClass* self) {
+  FlValue* values = fl_value_new_list();
+  return values;
+}
+
+static CoreTestsPigeonTestAnEmptyClass*
+core_tests_pigeon_test_an_empty_class_new_from_list(FlValue* values) {
+  return core_tests_pigeon_test_an_empty_class_new();
+}
+
+gboolean core_tests_pigeon_test_an_empty_class_equals(
+    CoreTestsPigeonTestAnEmptyClass* a, CoreTestsPigeonTestAnEmptyClass* b) {
+  if (a == b) {
+    return TRUE;
+  }
+  if (a == nullptr || b == nullptr) {
+    return FALSE;
+  }
+  return TRUE;
+}
+
+guint core_tests_pigeon_test_an_empty_class_hash(
+    CoreTestsPigeonTestAnEmptyClass* self) {
+  g_return_val_if_fail(CORE_TESTS_PIGEON_TEST_IS_AN_EMPTY_CLASS(self), 0);
+  guint result = 0;
+  return result;
+}
+
+gchar* core_tests_pigeon_test_an_empty_class_to_string(
+    CoreTestsPigeonTestAnEmptyClass* self) {
+  g_return_val_if_fail(CORE_TESTS_PIGEON_TEST_IS_AN_EMPTY_CLASS(self), NULL);
+  GString* str = g_string_new("AnEmptyClass(");
+  g_string_append(str, ")");
+  return g_string_free(str, FALSE);
+}
+
 struct _CoreTestsPigeonTestAllClassesWrapper {
   GObject parent_instance;
 
@@ -3933,6 +3997,7 @@
   FlValue* nullable_class_list;
   FlValue* class_map;
   FlValue* nullable_class_map;
+  CoreTestsPigeonTestAnEmptyClass* an_empty_class;
 };
 
 G_DEFINE_TYPE(CoreTestsPigeonTestAllClassesWrapper,
@@ -3949,6 +4014,7 @@
   g_clear_pointer(&self->nullable_class_list, fl_value_unref);
   g_clear_pointer(&self->class_map, fl_value_unref);
   g_clear_pointer(&self->nullable_class_map, fl_value_unref);
+  g_clear_object(&self->an_empty_class);
   G_OBJECT_CLASS(core_tests_pigeon_test_all_classes_wrapper_parent_class)
       ->dispose(object);
 }
@@ -3969,7 +4035,8 @@
         all_nullable_types_without_recursion,
     CoreTestsPigeonTestAllTypes* all_types, FlValue* class_list,
     FlValue* nullable_class_list, FlValue* class_map,
-    FlValue* nullable_class_map) {
+    FlValue* nullable_class_map,
+    CoreTestsPigeonTestAnEmptyClass* an_empty_class) {
   CoreTestsPigeonTestAllClassesWrapper* self =
       CORE_TESTS_PIGEON_TEST_ALL_CLASSES_WRAPPER(g_object_new(
           core_tests_pigeon_test_all_classes_wrapper_get_type(), nullptr));
@@ -3999,6 +4066,12 @@
   } else {
     self->nullable_class_map = nullptr;
   }
+  if (an_empty_class != nullptr) {
+    self->an_empty_class =
+        CORE_TESTS_PIGEON_TEST_AN_EMPTY_CLASS(g_object_ref(an_empty_class));
+  } else {
+    self->an_empty_class = nullptr;
+  }
   return self;
 }
 
@@ -4054,6 +4127,14 @@
   return self->nullable_class_map;
 }
 
+CoreTestsPigeonTestAnEmptyClass*
+core_tests_pigeon_test_all_classes_wrapper_get_an_empty_class(
+    CoreTestsPigeonTestAllClassesWrapper* self) {
+  g_return_val_if_fail(CORE_TESTS_PIGEON_TEST_IS_ALL_CLASSES_WRAPPER(self),
+                       nullptr);
+  return self->an_empty_class;
+}
+
 static FlValue* core_tests_pigeon_test_all_classes_wrapper_to_list(
     CoreTestsPigeonTestAllClassesWrapper* self) {
   FlValue* values = fl_value_new_list();
@@ -4082,6 +4163,12 @@
   fl_value_append_take(values, self->nullable_class_map != nullptr
                                    ? fl_value_ref(self->nullable_class_map)
                                    : fl_value_new_null());
+  fl_value_append_take(values,
+                       self->an_empty_class != nullptr
+                           ? fl_value_new_custom_object(
+                                 core_tests_pigeon_test_an_empty_class_type_id,
+                                 G_OBJECT(self->an_empty_class))
+                           : fl_value_new_null());
   return values;
 }
 
@@ -4119,9 +4206,16 @@
   if (fl_value_get_type(value6) != FL_VALUE_TYPE_NULL) {
     nullable_class_map = value6;
   }
+  FlValue* value7 = fl_value_get_list_value(values, 7);
+  CoreTestsPigeonTestAnEmptyClass* an_empty_class = nullptr;
+  if (fl_value_get_type(value7) != FL_VALUE_TYPE_NULL) {
+    an_empty_class = CORE_TESTS_PIGEON_TEST_AN_EMPTY_CLASS(
+        fl_value_get_custom_value_object(value7));
+  }
   return core_tests_pigeon_test_all_classes_wrapper_new(
       all_nullable_types, all_nullable_types_without_recursion, all_types,
-      class_list, nullable_class_list, class_map, nullable_class_map);
+      class_list, nullable_class_list, class_map, nullable_class_map,
+      an_empty_class);
 }
 
 gboolean core_tests_pigeon_test_all_classes_wrapper_equals(
@@ -4157,6 +4251,10 @@
   if (!flpigeon_deep_equals(a->nullable_class_map, b->nullable_class_map)) {
     return FALSE;
   }
+  if (!core_tests_pigeon_test_an_empty_class_equals(a->an_empty_class,
+                                                    b->an_empty_class)) {
+    return FALSE;
+  }
   return TRUE;
 }
 
@@ -4174,6 +4272,8 @@
   result = result * 31 + flpigeon_deep_hash(self->nullable_class_list);
   result = result * 31 + flpigeon_deep_hash(self->class_map);
   result = result * 31 + flpigeon_deep_hash(self->nullable_class_map);
+  result = result * 31 +
+           core_tests_pigeon_test_an_empty_class_hash(self->an_empty_class);
   return result;
 }
 
@@ -4242,6 +4342,15 @@
   } else {
     g_string_append(str, "null");
   }
+  g_string_append(str, ", an_empty_class: ");
+  if (self->an_empty_class != nullptr) {
+    gchar* field_str =
+        core_tests_pigeon_test_an_empty_class_to_string(self->an_empty_class);
+    g_string_append(str, field_str);
+    g_free(field_str);
+  } else {
+    g_string_append(str, "null");
+  }
   g_string_append(str, ")");
   return g_string_free(str, FALSE);
 }
@@ -4361,8 +4470,9 @@
 const int core_tests_pigeon_test_all_nullable_types_type_id = 133;
 const int core_tests_pigeon_test_all_nullable_types_without_recursion_type_id =
     134;
-const int core_tests_pigeon_test_all_classes_wrapper_type_id = 135;
-const int core_tests_pigeon_test_test_message_type_id = 136;
+const int core_tests_pigeon_test_an_empty_class_type_id = 135;
+const int core_tests_pigeon_test_all_classes_wrapper_type_id = 136;
+const int core_tests_pigeon_test_test_message_type_id = 137;
 
 static gboolean
 core_tests_pigeon_test_message_codec_write_core_tests_pigeon_test_an_enum(
@@ -4429,6 +4539,17 @@
 }
 
 static gboolean
+core_tests_pigeon_test_message_codec_write_core_tests_pigeon_test_an_empty_class(
+    FlStandardMessageCodec* codec, GByteArray* buffer,
+    CoreTestsPigeonTestAnEmptyClass* value, GError** error) {
+  uint8_t type = core_tests_pigeon_test_an_empty_class_type_id;
+  g_byte_array_append(buffer, &type, sizeof(uint8_t));
+  g_autoptr(FlValue) values =
+      core_tests_pigeon_test_an_empty_class_to_list(value);
+  return fl_standard_message_codec_write_value(codec, buffer, values, error);
+}
+
+static gboolean
 core_tests_pigeon_test_message_codec_write_core_tests_pigeon_test_all_classes_wrapper(
     FlStandardMessageCodec* codec, GByteArray* buffer,
     CoreTestsPigeonTestAllClassesWrapper* value, GError** error) {
@@ -4491,6 +4612,12 @@
             CORE_TESTS_PIGEON_TEST_ALL_NULLABLE_TYPES_WITHOUT_RECURSION(
                 fl_value_get_custom_value_object(value)),
             error);
+      case core_tests_pigeon_test_an_empty_class_type_id:
+        return core_tests_pigeon_test_message_codec_write_core_tests_pigeon_test_an_empty_class(
+            codec, buffer,
+            CORE_TESTS_PIGEON_TEST_AN_EMPTY_CLASS(
+                fl_value_get_custom_value_object(value)),
+            error);
       case core_tests_pigeon_test_all_classes_wrapper_type_id:
         return core_tests_pigeon_test_message_codec_write_core_tests_pigeon_test_all_classes_wrapper(
             codec, buffer,
@@ -4622,6 +4749,28 @@
 }
 
 static FlValue*
+core_tests_pigeon_test_message_codec_read_core_tests_pigeon_test_an_empty_class(
+    FlStandardMessageCodec* codec, GBytes* buffer, size_t* offset,
+    GError** error) {
+  g_autoptr(FlValue) values =
+      fl_standard_message_codec_read_value(codec, buffer, offset, error);
+  if (values == nullptr) {
+    return nullptr;
+  }
+
+  g_autoptr(CoreTestsPigeonTestAnEmptyClass) value =
+      core_tests_pigeon_test_an_empty_class_new_from_list(values);
+  if (value == nullptr) {
+    g_set_error(error, FL_MESSAGE_CODEC_ERROR, FL_MESSAGE_CODEC_ERROR_FAILED,
+                "Invalid data received for MessageData");
+    return nullptr;
+  }
+
+  return fl_value_new_custom_object(
+      core_tests_pigeon_test_an_empty_class_type_id, G_OBJECT(value));
+}
+
+static FlValue*
 core_tests_pigeon_test_message_codec_read_core_tests_pigeon_test_all_classes_wrapper(
     FlStandardMessageCodec* codec, GBytes* buffer, size_t* offset,
     GError** error) {
@@ -4687,6 +4836,9 @@
     case core_tests_pigeon_test_all_nullable_types_without_recursion_type_id:
       return core_tests_pigeon_test_message_codec_read_core_tests_pigeon_test_all_nullable_types_without_recursion(
           codec, buffer, offset, error);
+    case core_tests_pigeon_test_an_empty_class_type_id:
+      return core_tests_pigeon_test_message_codec_read_core_tests_pigeon_test_an_empty_class(
+          codec, buffer, offset, error);
     case core_tests_pigeon_test_all_classes_wrapper_type_id:
       return core_tests_pigeon_test_message_codec_read_core_tests_pigeon_test_all_classes_wrapper(
           codec, buffer, offset, error);
diff --git a/packages/pigeon/platform_tests/test_plugin/linux/pigeon/core_tests.gen.h b/packages/pigeon/platform_tests/test_plugin/linux/pigeon/core_tests.gen.h
index e5d97e2..d3de994 100644
--- a/packages/pigeon/platform_tests/test_plugin/linux/pigeon/core_tests.gen.h
+++ b/packages/pigeon/platform_tests/test_plugin/linux/pigeon/core_tests.gen.h
@@ -1422,6 +1422,59 @@
     CoreTestsPigeonTestAllNullableTypesWithoutRecursion* object);
 
 /**
+ * CoreTestsPigeonTestAnEmptyClass:
+ *
+ * A data class without fields for testing empty classes.
+ */
+
+G_DECLARE_FINAL_TYPE(CoreTestsPigeonTestAnEmptyClass,
+                     core_tests_pigeon_test_an_empty_class,
+                     CORE_TESTS_PIGEON_TEST, AN_EMPTY_CLASS, GObject)
+
+/**
+ * core_tests_pigeon_test_an_empty_class_new:
+ *
+ * Creates a new #AnEmptyClass object.
+ *
+ * Returns: a new #CoreTestsPigeonTestAnEmptyClass
+ */
+CoreTestsPigeonTestAnEmptyClass* core_tests_pigeon_test_an_empty_class_new();
+
+/**
+ * core_tests_pigeon_test_an_empty_class_equals:
+ * @a: a #CoreTestsPigeonTestAnEmptyClass.
+ * @b: another #CoreTestsPigeonTestAnEmptyClass.
+ *
+ * Checks if two #CoreTestsPigeonTestAnEmptyClass objects are equal.
+ *
+ * Returns: TRUE if @a and @b are equal.
+ */
+gboolean core_tests_pigeon_test_an_empty_class_equals(
+    CoreTestsPigeonTestAnEmptyClass* a, CoreTestsPigeonTestAnEmptyClass* b);
+
+/**
+ * core_tests_pigeon_test_an_empty_class_hash:
+ * @object: a #CoreTestsPigeonTestAnEmptyClass.
+ *
+ * Calculates a hash code for a #CoreTestsPigeonTestAnEmptyClass object.
+ *
+ * Returns: the hash code.
+ */
+guint core_tests_pigeon_test_an_empty_class_hash(
+    CoreTestsPigeonTestAnEmptyClass* object);
+
+/**
+ * core_tests_pigeon_test_an_empty_class_to_string:
+ * @object: a #CoreTestsPigeonTestAnEmptyClass.
+ *
+ * Returns a string representation of a #CoreTestsPigeonTestAnEmptyClass object.
+ *
+ * Returns: (transfer full): a new string, free with g_free().
+ */
+gchar* core_tests_pigeon_test_an_empty_class_to_string(
+    CoreTestsPigeonTestAnEmptyClass* object);
+
+/**
  * CoreTestsPigeonTestAllClassesWrapper:
  *
  * A class for testing nested class handling.
@@ -1444,6 +1497,7 @@
  * nullable_class_list: field in this object.
  * class_map: field in this object.
  * nullable_class_map: field in this object.
+ * an_empty_class: field in this object.
  *
  * Creates a new #AllClassesWrapper object.
  *
@@ -1456,7 +1510,8 @@
         all_nullable_types_without_recursion,
     CoreTestsPigeonTestAllTypes* all_types, FlValue* class_list,
     FlValue* nullable_class_list, FlValue* class_map,
-    FlValue* nullable_class_map);
+    FlValue* nullable_class_map,
+    CoreTestsPigeonTestAnEmptyClass* an_empty_class);
 
 /**
  * core_tests_pigeon_test_all_classes_wrapper_get_all_nullable_types
@@ -1539,6 +1594,18 @@
     CoreTestsPigeonTestAllClassesWrapper* object);
 
 /**
+ * core_tests_pigeon_test_all_classes_wrapper_get_an_empty_class
+ * @object: a #CoreTestsPigeonTestAllClassesWrapper.
+ *
+ * Gets the value of the anEmptyClass field of @object.
+ *
+ * Returns: the field value.
+ */
+CoreTestsPigeonTestAnEmptyClass*
+core_tests_pigeon_test_all_classes_wrapper_get_an_empty_class(
+    CoreTestsPigeonTestAllClassesWrapper* object);
+
+/**
  * core_tests_pigeon_test_all_classes_wrapper_equals:
  * @a: a #CoreTestsPigeonTestAllClassesWrapper.
  * @b: another #CoreTestsPigeonTestAllClassesWrapper.
@@ -1659,6 +1726,7 @@
 extern const int core_tests_pigeon_test_all_nullable_types_type_id;
 extern const int
     core_tests_pigeon_test_all_nullable_types_without_recursion_type_id;
+extern const int core_tests_pigeon_test_an_empty_class_type_id;
 extern const int core_tests_pigeon_test_all_classes_wrapper_type_id;
 extern const int core_tests_pigeon_test_test_message_type_id;
 
diff --git a/packages/pigeon/platform_tests/test_plugin/linux/test_plugin.cc b/packages/pigeon/platform_tests/test_plugin/linux/test_plugin.cc
index 1c4af93..9fd88b5 100644
--- a/packages/pigeon/platform_tests/test_plugin/linux/test_plugin.cc
+++ b/packages/pigeon/platform_tests/test_plugin/linux/test_plugin.cc
@@ -333,7 +333,8 @@
   FlValue* classMap = fl_value_new_map();
   g_autoptr(CoreTestsPigeonTestAllClassesWrapper) wrapper =
       core_tests_pigeon_test_all_classes_wrapper_new(
-          types, nullptr, nullptr, classList, nullptr, classMap, nullptr);
+          types, nullptr, nullptr, classList, nullptr, classMap, nullptr,
+          nullptr);
   return core_tests_pigeon_test_host_integration_core_api_create_nested_nullable_string_response_new(
       wrapper);
 }
diff --git a/packages/pigeon/platform_tests/test_plugin/windows/pigeon/core_tests.gen.cpp b/packages/pigeon/platform_tests/test_plugin/windows/pigeon/core_tests.gen.cpp
index d78f843..0fe69ec 100644
--- a/packages/pigeon/platform_tests/test_plugin/windows/pigeon/core_tests.gen.cpp
+++ b/packages/pigeon/platform_tests/test_plugin/windows/pigeon/core_tests.gen.cpp
@@ -2873,6 +2873,40 @@
   return v.Hash();
 }
 
+// AnEmptyClass
+
+AnEmptyClass::AnEmptyClass() {}
+
+EncodableList AnEmptyClass::ToEncodableList() const {
+  EncodableList list;
+  list.reserve(0);
+  return list;
+}
+
+AnEmptyClass AnEmptyClass::FromEncodableList(const EncodableList& list) {
+  AnEmptyClass decoded;
+  return decoded;
+}
+
+bool AnEmptyClass::operator==(const AnEmptyClass& other) const { return true; }
+
+bool AnEmptyClass::operator!=(const AnEmptyClass& other) const {
+  return !(*this == other);
+}
+
+size_t AnEmptyClass::Hash() const {
+  size_t result = 1;
+  return result;
+}
+
+std::ostream& operator<<(std::ostream& os, const AnEmptyClass& obj) {
+  os << "AnEmptyClass(";
+  os << ")";
+  return os;
+}
+
+size_t PigeonInternalDeepHash(const AnEmptyClass& v) { return v.Hash(); }
+
 // AllClassesWrapper
 
 AllClassesWrapper::AllClassesWrapper(const AllNullableTypes& all_nullable_types,
@@ -2890,7 +2924,8 @@
                                      const EncodableList& class_list,
                                      const EncodableList* nullable_class_list,
                                      const EncodableMap& class_map,
-                                     const EncodableMap* nullable_class_map)
+                                     const EncodableMap* nullable_class_map,
+                                     const AnEmptyClass* an_empty_class)
     : all_nullable_types_(
           std::make_unique<AllNullableTypes>(all_nullable_types)),
       all_nullable_types_without_recursion_(
@@ -2906,7 +2941,10 @@
       class_map_(class_map),
       nullable_class_map_(nullable_class_map
                               ? std::optional<EncodableMap>(*nullable_class_map)
-                              : std::nullopt) {}
+                              : std::nullopt),
+      an_empty_class_(an_empty_class
+                          ? std::make_unique<AnEmptyClass>(*an_empty_class)
+                          : nullptr) {}
 
 AllClassesWrapper::AllClassesWrapper(const AllClassesWrapper& other)
     : all_nullable_types_(
@@ -2928,7 +2966,10 @@
       nullable_class_map_(
           other.nullable_class_map_
               ? std::optional<EncodableMap>(*other.nullable_class_map_)
-              : std::nullopt) {}
+              : std::nullopt),
+      an_empty_class_(other.an_empty_class_ ? std::make_unique<AnEmptyClass>(
+                                                  *other.an_empty_class_)
+                                            : nullptr) {}
 
 AllClassesWrapper& AllClassesWrapper::operator=(
     const AllClassesWrapper& other) {
@@ -2945,6 +2986,9 @@
   nullable_class_list_ = other.nullable_class_list_;
   class_map_ = other.class_map_;
   nullable_class_map_ = other.nullable_class_map_;
+  an_empty_class_ = other.an_empty_class_
+                        ? std::make_unique<AnEmptyClass>(*other.an_empty_class_)
+                        : nullptr;
   return *this;
 }
 
@@ -3029,9 +3073,22 @@
   nullable_class_map_ = value_arg;
 }
 
+const AnEmptyClass* AllClassesWrapper::an_empty_class() const {
+  return an_empty_class_.get();
+}
+
+void AllClassesWrapper::set_an_empty_class(const AnEmptyClass* value_arg) {
+  an_empty_class_ =
+      value_arg ? std::make_unique<AnEmptyClass>(*value_arg) : nullptr;
+}
+
+void AllClassesWrapper::set_an_empty_class(const AnEmptyClass& value_arg) {
+  an_empty_class_ = std::make_unique<AnEmptyClass>(value_arg);
+}
+
 EncodableList AllClassesWrapper::ToEncodableList() const {
   EncodableList list;
-  list.reserve(7);
+  list.reserve(8);
   list.push_back(CustomEncodableValue(*all_nullable_types_));
   list.push_back(
       all_nullable_types_without_recursion_
@@ -3045,6 +3102,8 @@
   list.push_back(EncodableValue(class_map_));
   list.push_back(nullable_class_map_ ? EncodableValue(*nullable_class_map_)
                                      : EncodableValue());
+  list.push_back(an_empty_class_ ? CustomEncodableValue(*an_empty_class_)
+                                 : EncodableValue());
   return list;
 }
 
@@ -3076,6 +3135,11 @@
     decoded.set_nullable_class_map(
         std::get<EncodableMap>(encodable_nullable_class_map));
   }
+  auto& encodable_an_empty_class = list[7];
+  if (!encodable_an_empty_class.IsNull()) {
+    decoded.set_an_empty_class(std::any_cast<const AnEmptyClass&>(
+        std::get<CustomEncodableValue>(encodable_an_empty_class)));
+  }
   return decoded;
 }
 
@@ -3091,7 +3155,8 @@
                                   other.nullable_class_list_) &&
          PigeonInternalDeepEquals(class_map_, other.class_map_) &&
          PigeonInternalDeepEquals(nullable_class_map_,
-                                  other.nullable_class_map_);
+                                  other.nullable_class_map_) &&
+         PigeonInternalDeepEquals(an_empty_class_, other.an_empty_class_);
 }
 
 bool AllClassesWrapper::operator!=(const AllClassesWrapper& other) const {
@@ -3108,6 +3173,7 @@
   result = result * 31 + PigeonInternalDeepHash(nullable_class_list_);
   result = result * 31 + PigeonInternalDeepHash(class_map_);
   result = result * 31 + PigeonInternalDeepHash(nullable_class_map_);
+  result = result * 31 + PigeonInternalDeepHash(an_empty_class_);
   return result;
 }
 
@@ -3143,6 +3209,12 @@
   } else {
     os << "null";
   }
+  os << ", an_empty_class: ";
+  if (obj.an_empty_class_) {
+    os << *obj.an_empty_class_;
+  } else {
+    os << "null";
+  }
   os << ")";
   return os;
 }
@@ -3254,10 +3326,14 @@
               std::get<EncodableList>(ReadValue(stream))));
     }
     case 135: {
-      return CustomEncodableValue(AllClassesWrapper::FromEncodableList(
+      return CustomEncodableValue(AnEmptyClass::FromEncodableList(
           std::get<EncodableList>(ReadValue(stream))));
     }
     case 136: {
+      return CustomEncodableValue(AllClassesWrapper::FromEncodableList(
+          std::get<EncodableList>(ReadValue(stream))));
+    }
+    case 137: {
       return CustomEncodableValue(TestMessage::FromEncodableList(
           std::get<EncodableList>(ReadValue(stream))));
     }
@@ -3315,15 +3391,23 @@
                  stream);
       return;
     }
-    if (custom_value->type() == typeid(AllClassesWrapper)) {
+    if (custom_value->type() == typeid(AnEmptyClass)) {
       stream->WriteByte(135);
+      WriteValue(
+          EncodableValue(
+              std::any_cast<AnEmptyClass>(*custom_value).ToEncodableList()),
+          stream);
+      return;
+    }
+    if (custom_value->type() == typeid(AllClassesWrapper)) {
+      stream->WriteByte(136);
       WriteValue(EncodableValue(std::any_cast<AllClassesWrapper>(*custom_value)
                                     .ToEncodableList()),
                  stream);
       return;
     }
     if (custom_value->type() == typeid(TestMessage)) {
-      stream->WriteByte(136);
+      stream->WriteByte(137);
       WriteValue(
           EncodableValue(
               std::any_cast<TestMessage>(*custom_value).ToEncodableList()),
diff --git a/packages/pigeon/platform_tests/test_plugin/windows/pigeon/core_tests.gen.h b/packages/pigeon/platform_tests/test_plugin/windows/pigeon/core_tests.gen.h
index 6778e19..521cddd 100644
--- a/packages/pigeon/platform_tests/test_plugin/windows/pigeon/core_tests.gen.h
+++ b/packages/pigeon/platform_tests/test_plugin/windows/pigeon/core_tests.gen.h
@@ -697,6 +697,35 @@
   std::optional<::flutter::EncodableMap> map_map_;
 };
 
+// A data class without fields for testing empty classes.
+//
+// Generated class from Pigeon that represents data sent in messages.
+class AnEmptyClass {
+ public:
+  // Constructs an object setting all fields.
+  AnEmptyClass();
+
+  bool operator==(const AnEmptyClass& other) const;
+  bool operator!=(const AnEmptyClass& other) const;
+  /// Returns a hash code value for the object. This method is supported for the
+  /// benefit of hash tables.
+  size_t Hash() const;
+  /// Stream output operator for formatted string representation.
+  friend std::ostream& operator<<(std::ostream& os, const AnEmptyClass& obj);
+
+ private:
+  static AnEmptyClass FromEncodableList(const ::flutter::EncodableList& list);
+  ::flutter::EncodableList ToEncodableList() const;
+  friend class AllClassesWrapper;
+  friend class HostIntegrationCoreApi;
+  friend class FlutterIntegrationCoreApi;
+  friend class HostTrivialApi;
+  friend class HostSmallApi;
+  friend class FlutterSmallApi;
+  friend class PigeonInternalCodecSerializer;
+  friend class CoreTestsTest;
+};
+
 // A class for testing nested class handling.
 //
 // This is needed to test nested nullable and non-nullable classes,
@@ -719,7 +748,8 @@
       const AllTypes* all_types, const ::flutter::EncodableList& class_list,
       const ::flutter::EncodableList* nullable_class_list,
       const ::flutter::EncodableMap& class_map,
-      const ::flutter::EncodableMap* nullable_class_map);
+      const ::flutter::EncodableMap* nullable_class_map,
+      const AnEmptyClass* an_empty_class);
 
   ~AllClassesWrapper() = default;
   AllClassesWrapper(const AllClassesWrapper& other);
@@ -754,6 +784,10 @@
   void set_nullable_class_map(const ::flutter::EncodableMap* value_arg);
   void set_nullable_class_map(const ::flutter::EncodableMap& value_arg);
 
+  const AnEmptyClass* an_empty_class() const;
+  void set_an_empty_class(const AnEmptyClass* value_arg);
+  void set_an_empty_class(const AnEmptyClass& value_arg);
+
   bool operator==(const AllClassesWrapper& other) const;
   bool operator!=(const AllClassesWrapper& other) const;
   /// Returns a hash code value for the object. This method is supported for the
@@ -782,6 +816,7 @@
   std::optional<::flutter::EncodableList> nullable_class_list_;
   ::flutter::EncodableMap class_map_;
   std::optional<::flutter::EncodableMap> nullable_class_map_;
+  std::unique_ptr<AnEmptyClass> an_empty_class_;
 };
 
 // A data class containing a List, used in unit tests.
diff --git a/packages/pigeon/pubspec.yaml b/packages/pigeon/pubspec.yaml
index fb078cd..8103bc0 100644
--- a/packages/pigeon/pubspec.yaml
+++ b/packages/pigeon/pubspec.yaml
@@ -2,7 +2,7 @@
 description: Code generator tool to make communication between Flutter and the host platform type-safe and easier.
 repository: https://github.com/flutter/packages/tree/main/packages/pigeon
 issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+pigeon%22
-version: 27.1.2 # This must match the version in lib/src/generator_tools.dart
+version: 27.2.0 # This must match the version in lib/src/generator_tools.dart
 
 environment:
   sdk: ^3.10.0