Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions packages/pigeon/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
2 changes: 1 addition & 1 deletion packages/pigeon/lib/src/generator_tools.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
36 changes: 35 additions & 1 deletion packages/pigeon/lib/src/pigeon_lib_internal.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1747,6 +1747,34 @@ class RootBuilder extends dart_ast_visitor.RecursiveAstVisitor<Object?> {

@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,
Expand All @@ -1761,7 +1789,13 @@ class RootBuilder extends dart_ast_visitor.RecursiveAstVisitor<Object?> {
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;
}

Expand Down
2 changes: 1 addition & 1 deletion packages/pigeon/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
119 changes: 119 additions & 0 deletions packages/pigeon/test/pigeon_lib_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down