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..755060444fce 100644 --- a/packages/pigeon/lib/src/pigeon_lib_internal.dart +++ b/packages/pigeon/lib/src/pigeon_lib_internal.dart @@ -1747,6 +1747,34 @@ 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 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, type parameters, ' + 'mixins, interfaces, or arguments on its values.', + lineNumber: calculateLineNumber(source, node.offset), + ), + ); + } _enums.add( Enum( name: node.namePart.typeName.lexeme, @@ -1761,7 +1789,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..0273624ee61b 100644 --- a/packages/pigeon/test/pigeon_lib_test.dart +++ b/packages/pigeon/test/pigeon_lib_test.dart @@ -276,6 +276,125 @@ 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('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(); +} +'''; + 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 {