From 339c7970479ad52e2e8460bdc4b339731b668a9e Mon Sep 17 00:00:00 2001 From: PRANTA Dutta Date: Wed, 10 Jun 2026 20:34:35 +0600 Subject: [PATCH 1/2] [pigeon] Report a clear error for enhanced enums MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pigeon's input parser silently ignored the extra syntax in enhanced enums (constructors, fields, methods, or arguments on values), reading only the value names. This produced confusing generated output — the enum's fields were effectively coalesced into the next class — with no indication that the input was unsupported. Detect enhanced enums in visitEnumDeclaration and report a clear, source-located error instead. The enum is still registered by its value names so that classes referencing it don't produce additional cascading 'Unknown type' errors, and the visitor no longer walks the enum's body (whose class-like members it doesn't expect; a getter there previously crashed the parser on a null parameter list). Fixes https://github.com/flutter/flutter/issues/160827 --- packages/pigeon/CHANGELOG.md | 6 ++ packages/pigeon/lib/src/generator_tools.dart | 2 +- .../pigeon/lib/src/pigeon_lib_internal.dart | 33 ++++++- packages/pigeon/pubspec.yaml | 2 +- packages/pigeon/test/pigeon_lib_test.dart | 93 +++++++++++++++++++ 5 files changed, 133 insertions(+), 3 deletions(-) diff --git a/packages/pigeon/CHANGELOG.md b/packages/pigeon/CHANGELOG.md index dab9b7ac831a..e344c8d5f934 100644 --- a/packages/pigeon/CHANGELOG.md +++ b/packages/pigeon/CHANGELOG.md @@ -1,3 +1,9 @@ +## 27.0.1 + +* Reports a clear error when an input file uses an enhanced enum (one with a + constructor, fields, methods, or arguments on its values), instead of + silently generating incorrect output. + ## 27.0.0 * **Breaking Change** Overrides `toString` (or equivalent) methods on generated data classes diff --git a/packages/pigeon/lib/src/generator_tools.dart b/packages/pigeon/lib/src/generator_tools.dart index 8ab2bbb4031b..a69394a566b4 100644 --- a/packages/pigeon/lib/src/generator_tools.dart +++ b/packages/pigeon/lib/src/generator_tools.dart @@ -15,7 +15,7 @@ import 'generator.dart'; /// The current version of pigeon. /// /// This must match the version in pubspec.yaml. -const String pigeonVersion = '27.0.0'; +const String pigeonVersion = '27.0.1'; /// Default plugin package name. const String defaultPluginPackageName = 'dev.flutter.pigeon'; diff --git a/packages/pigeon/lib/src/pigeon_lib_internal.dart b/packages/pigeon/lib/src/pigeon_lib_internal.dart index b9607e12f528..cd746af37abe 100644 --- a/packages/pigeon/lib/src/pigeon_lib_internal.dart +++ b/packages/pigeon/lib/src/pigeon_lib_internal.dart @@ -1747,6 +1747,31 @@ class RootBuilder extends dart_ast_visitor.RecursiveAstVisitor { @override Object? visitEnumDeclaration(dart_ast.EnumDeclaration node) { + // Enhanced enums (those with a constructor, fields, methods, or arguments + // on their values) aren't supported by Pigeon. Detect them and emit a + // clear error instead of silently dropping the extra members, which + // previously produced confusing output (the enum's fields were coalesced + // into the following class). + // See https://github.com/flutter/flutter/issues/160827. + // + // The enum is still registered below (by its value names) so that classes + // referencing it don't produce additional, confusing "Unknown type" + // errors; recording the error here already prevents code generation. + final bool hasEnumMembers = node.body.members.isNotEmpty; + final bool hasConstantArguments = node.body.constants.any( + (dart_ast.EnumConstantDeclaration e) => e.arguments != null, + ); + final bool isEnhancedEnum = hasEnumMembers || hasConstantArguments; + if (isEnhancedEnum) { + _errors.add( + Error( + message: + 'Pigeon doesn\'t support enhanced enums ("${node.namePart.typeName.lexeme}"). ' + 'Use a plain enum without a constructor, fields, methods, or arguments on its values.', + lineNumber: calculateLineNumber(source, node.offset), + ), + ); + } _enums.add( Enum( name: node.namePart.typeName.lexeme, @@ -1761,7 +1786,13 @@ class RootBuilder extends dart_ast_visitor.RecursiveAstVisitor { documentationComments: _documentationCommentsParser(node.documentationComment?.tokens), ), ); - node.visitChildren(this); + // Don't visit the children of an enhanced enum: the declaration is + // already reported as unsupported, and the visitor doesn't expect + // class-like members outside of a class (for example, a getter would + // fail visitMethodDeclaration's parameter access). + if (!isEnhancedEnum) { + node.visitChildren(this); + } return null; } diff --git a/packages/pigeon/pubspec.yaml b/packages/pigeon/pubspec.yaml index 0472d4c5fe8d..324a3365a7e6 100644 --- a/packages/pigeon/pubspec.yaml +++ b/packages/pigeon/pubspec.yaml @@ -2,7 +2,7 @@ name: pigeon 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.0.0 # This must match the version in lib/src/generator_tools.dart +version: 27.0.1 # This must match the version in lib/src/generator_tools.dart environment: sdk: ^3.10.0 diff --git a/packages/pigeon/test/pigeon_lib_test.dart b/packages/pigeon/test/pigeon_lib_test.dart index 7c319672e81d..a376a694d68e 100644 --- a/packages/pigeon/test/pigeon_lib_test.dart +++ b/packages/pigeon/test/pigeon_lib_test.dart @@ -276,6 +276,99 @@ abstract class Api { expect(results.root.classes[0].fields[0].name, equals('enum1')); }); + test('enhanced enum with a constructor and fields is an error', () { + const code = ''' +enum Enum1 { + one(1), + two(2); + + const Enum1(this.value); + final int value; +} + +class ClassWithEnum { + Enum1? enum1; +} + +@HostApi +abstract class Api { + ClassWithEnum foo(); +} +'''; + final ParseResults results = parseSource(code); + expect(results.errors, hasLength(1)); + expect(results.errors[0].message, contains('enhanced enums')); + expect(results.errors[0].message, contains('Enum1')); + }); + + test('plain enum with a trailing semicolon is not an error', () { + const code = ''' +enum Enum1 { + one, + two; +} + +class ClassWithEnum { + Enum1? enum1; +} + +@HostApi +abstract class Api { + ClassWithEnum foo(); +} +'''; + final ParseResults results = parseSource(code); + expect(results.errors, isEmpty); + }); + + test('enhanced enum with a getter is an error and does not crash', () { + const code = ''' +enum Enum1 { + one(1), + two(2); + + const Enum1(this.value); + final int value; + int get doubled => value * 2; +} + +class ClassWithEnum { + Enum1? enum1; +} + +@HostApi +abstract class Api { + ClassWithEnum foo(); +} +'''; + final ParseResults results = parseSource(code); + expect(results.errors, hasLength(1)); + expect(results.errors[0].message, contains('enhanced enums')); + expect(results.errors[0].message, contains('Enum1')); + }); + + test('enum with arguments on its values is an error', () { + const code = ''' +enum Enum1 { + one(1), + two(2); +} + +class ClassWithEnum { + Enum1? enum1; +} + +@HostApi +abstract class Api { + ClassWithEnum foo(); +} +'''; + final ParseResults results = parseSource(code); + expect(results.errors, hasLength(1)); + expect(results.errors[0].message, contains('enhanced enums')); + expect(results.errors[0].message, contains('Enum1')); + }); + test('two methods', () { const code = ''' class Input1 { From c4afe8d7168e3956f73aff83207bd7ddf46b71d5 Mon Sep 17 00:00:00 2001 From: PRANTA Dutta Date: Thu, 11 Jun 2026 00:29:59 +0600 Subject: [PATCH 2/2] [pigeon] Also detect type parameters, primary constructors, and with/implements clauses as enhanced enums These forms were previously still silently reduced to their value names. The error message now lists the full set of unsupported syntax. --- .../pigeon/lib/src/pigeon_lib_internal.dart | 15 ++++++----- packages/pigeon/test/pigeon_lib_test.dart | 26 +++++++++++++++++++ 2 files changed, 35 insertions(+), 6 deletions(-) diff --git a/packages/pigeon/lib/src/pigeon_lib_internal.dart b/packages/pigeon/lib/src/pigeon_lib_internal.dart index cd746af37abe..755060444fce 100644 --- a/packages/pigeon/lib/src/pigeon_lib_internal.dart +++ b/packages/pigeon/lib/src/pigeon_lib_internal.dart @@ -1757,17 +1757,20 @@ class RootBuilder extends dart_ast_visitor.RecursiveAstVisitor { // The enum is still registered below (by its value names) so that classes // referencing it don't produce additional, confusing "Unknown type" // errors; recording the error here already prevents code generation. - final bool hasEnumMembers = node.body.members.isNotEmpty; - final bool hasConstantArguments = node.body.constants.any( - (dart_ast.EnumConstantDeclaration e) => e.arguments != null, - ); - final bool isEnhancedEnum = hasEnumMembers || hasConstantArguments; + final bool isEnhancedEnum = + node.body.members.isNotEmpty || + node.body.constants.any((dart_ast.EnumConstantDeclaration e) => e.arguments != null) || + node.namePart.typeParameters != null || + node.namePart is dart_ast.PrimaryConstructorDeclaration || + node.withClause != null || + node.implementsClause != null; if (isEnhancedEnum) { _errors.add( Error( message: 'Pigeon doesn\'t support enhanced enums ("${node.namePart.typeName.lexeme}"). ' - 'Use a plain enum without a constructor, fields, methods, or arguments on its values.', + 'Use a plain enum without a constructor, fields, methods, type parameters, ' + 'mixins, interfaces, or arguments on its values.', lineNumber: calculateLineNumber(source, node.offset), ), ); diff --git a/packages/pigeon/test/pigeon_lib_test.dart b/packages/pigeon/test/pigeon_lib_test.dart index a376a694d68e..0273624ee61b 100644 --- a/packages/pigeon/test/pigeon_lib_test.dart +++ b/packages/pigeon/test/pigeon_lib_test.dart @@ -290,6 +290,32 @@ class ClassWithEnum { Enum1? enum1; } +@HostApi +abstract class Api { + ClassWithEnum foo(); +} +'''; + final ParseResults results = parseSource(code); + expect(results.errors, hasLength(1)); + expect(results.errors[0].message, contains('enhanced enums')); + expect(results.errors[0].message, contains('Enum1')); + }); + + test('enum with a mixin or interface is an error', () { + const code = ''' +mixin Mixin1 {} + +abstract class Interface1 {} + +enum Enum1 with Mixin1 implements Interface1 { + one, + two, +} + +class ClassWithEnum { + Enum1? enum1; +} + @HostApi abstract class Api { ClassWithEnum foo();