Skip to content
Merged
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.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
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.1.1';
const String pigeonVersion = '27.1.2';

/// Default plugin package name.
const String defaultPluginPackageName = 'dev.flutter.pigeon';
Expand Down
27 changes: 26 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,26 @@ 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.
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 +1781,12 @@ 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.
if (!isEnhancedEnum) {
node.visitChildren(this);
}
Comment thread
tarrinneal marked this conversation as resolved.
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.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
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
Loading