From 93219126652e1f4f392bfce7f14f9291f4a4ce09 Mon Sep 17 00:00:00 2001 From: PRANTA Dutta Date: Wed, 10 Jun 2026 20:34:35 +0600 Subject: [PATCH 1/3] [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 803f62bc5118..83d2e72afd30 100644 --- a/packages/pigeon/CHANGELOG.md +++ b/packages/pigeon/CHANGELOG.md @@ -1,3 +1,9 @@ +## 27.1.2 + +* 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.1.1 * [dart] Adds usage documentation to generated event channel methods, and diff --git a/packages/pigeon/lib/src/generator_tools.dart b/packages/pigeon/lib/src/generator_tools.dart index dbc56d4823b8..860898a5bced 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.1.1'; +const String pigeonVersion = '27.1.2'; /// 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 bf3b9563ca90..fb078cd488f8 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.1.1 # This must match the version in lib/src/generator_tools.dart +version: 27.1.2 # 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 ed7b327be5adbfd27db7c9177a9277911b557305 Mon Sep 17 00:00:00 2001 From: PRANTA Dutta Date: Thu, 11 Jun 2026 00:29:59 +0600 Subject: [PATCH 2/3] [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(); From c231afb97fddc0d06c239434f1a939f6f0b1918a Mon Sep 17 00:00:00 2001 From: Tarrin Neal Date: Wed, 8 Jul 2026 19:11:12 -0700 Subject: [PATCH 3/3] Update pigeon_lib_internal.dart Reduce comment length --- packages/pigeon/lib/src/pigeon_lib_internal.dart | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/packages/pigeon/lib/src/pigeon_lib_internal.dart b/packages/pigeon/lib/src/pigeon_lib_internal.dart index 755060444fce..756451173d34 100644 --- a/packages/pigeon/lib/src/pigeon_lib_internal.dart +++ b/packages/pigeon/lib/src/pigeon_lib_internal.dart @@ -1748,15 +1748,7 @@ 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. + // on their values) aren't supported by Pigeon. final bool isEnhancedEnum = node.body.members.isNotEmpty || node.body.constants.any((dart_ast.EnumConstantDeclaration e) => e.arguments != null) || @@ -1791,8 +1783,7 @@ class RootBuilder extends dart_ast_visitor.RecursiveAstVisitor { ); // 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). + // class-like members outside of a class. if (!isEnhancedEnum) { node.visitChildren(this); }