[pigeon] Produce a helpful error for bad method return type (#11204)

When we encounter an unexpected (or missing) method return type, we now
produce a meaningful error instead of a null-pointer exception.

Fixes https://github.com/flutter/flutter/issues/92639
This commit is contained in:
Sam Rawlins
2026-03-11 08:21:41 -07:00
committed by GitHub
parent 1ea3725c43
commit ecace66e92
5 changed files with 89 additions and 30 deletions

View File

@@ -1,3 +1,8 @@
## 26.2.3
* Produces a helpful error message when a method return type is missing or an
unsupported type, such as a function type or record type.
## 26.2.2
* [dart] Ignores all lint rules in generated code.

View File

@@ -15,7 +15,7 @@ import 'generator.dart';
/// The current version of pigeon.
///
/// This must match the version in pubspec.yaml.
const String pigeonVersion = '26.2.2';
const String pigeonVersion = '26.2.3';
/// Default plugin package name.
const String defaultPluginPackageName = 'dev.flutter.pigeon';

View File

@@ -1913,36 +1913,58 @@ class RootBuilder extends dart_ast_visitor.RecursiveAstVisitor<Object?> {
if (_currentApi != null) {
// Methods without named return types aren't supported.
final dart_ast.TypeAnnotation returnType = node.returnType!;
returnType as dart_ast.NamedType;
_currentApi!.methods.add(
Method(
name: node.name.lexeme,
returnType: TypeDeclaration(
baseName: _getNamedTypeQualifiedName(returnType),
typeArguments: _typeAnnotationsToTypeArguments(
returnType.typeArguments,
final dart_ast.TypeAnnotation? returnType = node.returnType;
if (returnType is! dart_ast.NamedType) {
// In order to support implicit types (either `dynamic` or inherited
// return types), type aliases, function types, or record types, we'd
// need to use the analyzer's element model, via `getParsedUnit` instead
// of `getParsedUnit` in `Pigeon.parseFile`, and then access the
// resolved return type, via
// `node.declaredFragment!.element.returnType`.
String erroneousDeclaration = node.name.lexeme;
final dart_ast.AstNode? enclosingDeclaration = node.parent;
if (enclosingDeclaration is dart_ast.ClassDeclaration) {
erroneousDeclaration =
'${enclosingDeclaration.name}.$erroneousDeclaration';
}
_errors.add(
Error(
message:
'Expected a named type for the return type of '
'("$erroneousDeclaration").',
lineNumber: calculateLineNumber(source, node.offset),
),
);
} else {
_currentApi!.methods.add(
Method(
name: node.name.lexeme,
returnType: TypeDeclaration(
baseName: _getNamedTypeQualifiedName(returnType),
typeArguments: _typeAnnotationsToTypeArguments(
returnType.typeArguments,
),
isNullable: returnType.question != null,
),
parameters: arguments,
isStatic: isStatic,
location: switch (_currentApi!) {
AstHostApi() => ApiLocation.host,
AstProxyApi() => ApiLocation.host,
AstFlutterApi() => ApiLocation.flutter,
AstEventChannelApi() => ApiLocation.host,
},
isAsynchronous: isAsynchronous,
objcSelector: objcSelector,
swiftFunction: swiftFunction,
offset: node.offset,
taskQueueType: taskQueueType,
documentationComments: _documentationCommentsParser(
node.documentationComment?.tokens,
),
isNullable: returnType.question != null,
),
parameters: arguments,
isStatic: isStatic,
location: switch (_currentApi!) {
AstHostApi() => ApiLocation.host,
AstProxyApi() => ApiLocation.host,
AstFlutterApi() => ApiLocation.flutter,
AstEventChannelApi() => ApiLocation.host,
},
isAsynchronous: isAsynchronous,
objcSelector: objcSelector,
swiftFunction: swiftFunction,
offset: node.offset,
taskQueueType: taskQueueType,
documentationComments: _documentationCommentsParser(
node.documentationComment?.tokens,
),
),
);
);
}
} else if (_currentClass != null) {
_errors.add(
Error(

View File

@@ -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: 26.2.2 # This must match the version in lib/src/generator_tools.dart
version: 26.2.3 # This must match the version in lib/src/generator_tools.dart
environment:
sdk: ^3.9.0

View File

@@ -1265,6 +1265,38 @@ abstract class Api {
expect(results.errors[0].message, contains('Unknown type: Foo'));
});
test('function return type is missing', () {
const code = '''
@HostApi()
abstract class Api {
storeAll(List<int?> foos);
}
''';
final ParseResults results = parseSource(code);
expect(results.errors, hasLength(1));
expect(results.errors[0].lineNumber, 3);
expect(
results.errors[0].message,
contains('Expected a named type for the return type of ("Api.storeAll")'),
);
});
test('function return type is record type', () {
const code = '''
@HostApi()
abstract class Api {
(int, int) storeAll(List<int?> foos);
}
''';
final ParseResults results = parseSource(code);
expect(results.errors, hasLength(1));
expect(results.errors[0].lineNumber, 3);
expect(
results.errors[0].message,
contains('Expected a named type for the return type of ("Api.storeAll")'),
);
});
test('Object type argument', () {
const code = '''
@HostApi()