[pigeon] generics and parsing multiple parameters (#428)

* [pigeon] implemented generics
* [pigeon] implemented multiple arity parsing
* [pigeon] implemented generics support in primitives
* [pigeon] made generated dart code keep the parameter name
* [pigeon] refactored the AST to be more clear
diff --git a/packages/pigeon/CHANGELOG.md b/packages/pigeon/CHANGELOG.md
index 3caaeba..8da8cd4 100644
--- a/packages/pigeon/CHANGELOG.md
+++ b/packages/pigeon/CHANGELOG.md
@@ -7,6 +7,8 @@
 * Started allowing primitive data types as arguments and return types.
 * Added one_language flag for allowing Pigeon to only generate code for one platform.
 * Fixed NPE in java generated code for nested types.
+* Started supporting generics' type arguments for fields in classes.
+* Generics (class fields and primitives)
 
 ## 0.3.0
 
diff --git a/packages/pigeon/lib/ast.dart b/packages/pigeon/lib/ast.dart
index bfc438f..d70f9e0 100644
--- a/packages/pigeon/lib/ast.dart
+++ b/packages/pigeon/lib/ast.dart
@@ -20,10 +20,8 @@
   Method({
     required this.name,
     required this.returnType,
-    required this.argType,
-    this.isArgNullable = false,
+    required this.arguments,
     this.isAsynchronous = false,
-    this.isReturnNullable = false,
     this.offset,
   });
 
@@ -31,16 +29,10 @@
   String name;
 
   /// The data-type of the return value.
-  String returnType;
+  TypeDeclaration returnType;
 
-  /// True if the method can return a null value.
-  bool isReturnNullable;
-
-  /// The data-type of the argument.
-  String argType;
-
-  /// True if the argument has a null tag `?`.
-  bool isArgNullable;
+  /// The arguments passed into the [Method].
+  List<NamedType> arguments;
 
   /// Whether the receiver of this method is expected to return synchronously or not.
   bool isAsynchronous;
@@ -50,7 +42,7 @@
 
   @override
   String toString() {
-    return '(Api name:$name returnType:$returnType argType:$argType isAsynchronous:$isAsynchronous)';
+    return '(Method name:$name returnType:$returnType arguments:$arguments isAsynchronous:$isAsynchronous)';
   }
 }
 
@@ -82,39 +74,51 @@
   }
 }
 
-/// Represents a field on a [Class].
-class Field extends Node {
-  /// Parametric constructor for [Field].
-  Field({
-    required this.name,
-    required this.dataType,
+/// A specific instance of a type.
+class TypeDeclaration {
+  /// Constructor for [TypeDeclaration].
+  TypeDeclaration({
+    required this.baseName,
     required this.isNullable,
     this.typeArguments,
-    this.offset,
   });
 
-  /// The name of the field.
-  String name;
+  /// The base name of the [TypeDeclaration] (ex 'Foo' to 'Foo<Bar>?').
+  final String baseName;
 
-  /// The data-type of the field (ex 'String' or 'int').
-  String dataType;
+  /// The type arguments to the entity (ex 'Bar' to 'Foo<Bar>?').
+  final List<TypeDeclaration>? typeArguments;
 
-  /// The offset in the source file where the field appears.
-  int? offset;
-
-  /// True if the datatype is nullable (ex `int?`).
-  bool isNullable;
-
-  /// Type parameters used for generics.
-  List<Field>? typeArguments;
+  /// True if the type is nullable.
+  final bool isNullable;
 
   @override
   String toString() {
-    return '(Field name:$name dataType:$dataType)';
+    return '(TypeDeclaration baseName:$baseName isNullable:$isNullable typeArguments:$typeArguments)';
   }
 }
 
-/// Represents a class with [Field]s.
+/// Represents a named entity that has a type.
+class NamedType extends Node {
+  /// Parametric constructor for [NamedType].
+  NamedType({required this.name, required this.type, this.offset});
+
+  /// The name of the entity.
+  String name;
+
+  /// The type.
+  TypeDeclaration type;
+
+  /// The offset in the source file where the [NamedType] appears.
+  int? offset;
+
+  @override
+  String toString() {
+    return '(NamedType name:$name type:$type)';
+  }
+}
+
+/// Represents a class with fields.
 class Class extends Node {
   /// Parametric constructor for [Class].
   Class({
@@ -126,7 +130,7 @@
   String name;
 
   /// All the fields contained in the class.
-  List<Field> fields;
+  List<NamedType> fields;
 
   @override
   String toString() {
diff --git a/packages/pigeon/lib/dart_generator.dart b/packages/pigeon/lib/dart_generator.dart
index c231135..5c51d51 100644
--- a/packages/pigeon/lib/dart_generator.dart
+++ b/packages/pigeon/lib/dart_generator.dart
@@ -92,6 +92,24 @@
   });
 }
 
+/// Creates a Dart type where all type arguments are [Objects].
+String _makeGenericTypeArguments(TypeDeclaration type, String nullTag) {
+  return type.typeArguments != null
+      ? '${type.baseName}<${type.typeArguments!.map<String>((TypeDeclaration e) => 'Object$nullTag').join(', ')}>'
+      : _addGenericTypes(type, nullTag);
+}
+
+/// Creates a `.cast<>` call for an type. Returns an empty string if the
+/// type has no type arguments.
+String _makeGenericCastCall(TypeDeclaration type, String nullTag) {
+  return type.typeArguments != null
+      ? '.cast<${_flattenTypeArguments(type.typeArguments!, nullTag)}>()'
+      : '';
+}
+
+String _getArgumentName(NamedType field) =>
+    field.name.isEmpty ? 'arg' : field.name;
+
 void _writeHostApi(DartOptions opt, Indent indent, Api api) {
   assert(api.location == ApiLocation.host);
   final String codecName = _getCodecName(api);
@@ -121,12 +139,13 @@
       }
       String argSignature = '';
       String sendArgument = 'null';
-      if (func.argType != 'void') {
-        argSignature = '${func.argType} arg';
-        sendArgument = 'arg';
+      if (func.arguments.isNotEmpty) {
+        sendArgument = _getArgumentName(func.arguments[0]);
+        argSignature =
+            '${_addGenericTypes(func.arguments[0].type, nullTag)} $sendArgument';
       }
       indent.write(
-        'Future<${func.returnType}> ${func.name}($argSignature) async ',
+        'Future<${_addGenericTypes(func.returnType, nullTag)}> ${func.name}($argSignature) async ',
       );
       indent.scoped('{', '}', () {
         final String channelName = makeChannelName(api, func);
@@ -137,9 +156,12 @@
             '\'$channelName\', codec, binaryMessenger: _binaryMessenger);',
           );
         });
-        final String returnStatement = func.returnType == 'void'
+        final String returnType =
+            _makeGenericTypeArguments(func.returnType, nullTag);
+        final String castCall = _makeGenericCastCall(func.returnType, nullTag);
+        final String returnStatement = func.returnType.baseName == 'void'
             ? '// noop'
-            : 'return (replyMap[\'${Keys.result}\'] as ${func.returnType}$nullTag)$unwrapOperator;';
+            : 'return (replyMap[\'${Keys.result}\'] as $returnType$nullTag)$unwrapOperator$castCall;';
         indent.format('''
 final Map<Object$nullTag, Object$nullTag>$nullTag replyMap =\n\t\tawait channel.send($sendArgument) as Map<Object$nullTag, Object$nullTag>$nullTag;
 if (replyMap == null) {
@@ -181,10 +203,12 @@
     indent.addln('');
     for (final Method func in api.methods) {
       final bool isAsync = func.isAsynchronous;
-      final String returnType =
-          isAsync ? 'Future<${func.returnType}>' : func.returnType;
-      final String argSignature =
-          func.argType == 'void' ? '' : '${func.argType} arg';
+      final String returnType = isAsync
+          ? 'Future<${_addGenericTypes(func.returnType, nullTag)}>'
+          : _addGenericTypes(func.returnType, nullTag);
+      final String argSignature = func.arguments.isEmpty
+          ? ''
+          : '${_addGenericTypes(func.arguments[0].type, nullTag)} ${_getArgumentName(func.arguments[0])}';
       indent.writeln('$returnType ${func.name}($argSignature);');
     }
     indent.write('static void setup(${api.name}$nullTag api) ');
@@ -215,19 +239,21 @@
               'channel.$messageHandlerSetter((Object$nullTag message) async ',
             );
             indent.scoped('{', '});', () {
-              final String argType = func.argType;
-              final String returnType = func.returnType;
+              final String returnType =
+                  _addGenericTypes(func.returnType, nullTag);
               final bool isAsync = func.isAsynchronous;
               final String emptyReturnStatement = isMockHandler
                   ? 'return <Object$nullTag, Object$nullTag>{};'
-                  : func.returnType == 'void'
+                  : func.returnType.baseName == 'void'
                       ? 'return;'
                       : 'return null;';
               String call;
-              if (argType == 'void') {
+              if (func.arguments.isEmpty) {
                 indent.writeln('// ignore message');
                 call = 'api.${func.name}()';
               } else {
+                final String argType =
+                    _addGenericTypes(func.arguments[0].type, nullTag);
                 indent.writeln(
                   'assert(message != null, \'Argument for $channelName was null. Expected $argType.\');',
                 );
@@ -263,17 +289,38 @@
   });
 }
 
-String _addGenericTypes(String dataType, String nullTag) {
-  switch (dataType) {
+/// Converts a [List] of [TypeDeclaration]s to a comma separated [String] to be
+/// used in Dart code.
+String _flattenTypeArguments(List<TypeDeclaration> args, String nullTag) {
+  return args
+      .map<String>((TypeDeclaration arg) => arg.typeArguments == null
+          ? '${arg.baseName}$nullTag'
+          : '${arg.baseName}<${_flattenTypeArguments(arg.typeArguments!, nullTag)}>$nullTag')
+      .join(', ');
+}
+
+/// Creates the type declaration for use in Dart code from a [NamedType] making sure
+/// that type arguments are used for primitive generic types.
+String _addGenericTypes(TypeDeclaration type, String nullTag) {
+  final List<TypeDeclaration>? typeArguments = type.typeArguments;
+  switch (type.baseName) {
     case 'List':
-      return 'List<Object$nullTag>$nullTag';
+      return (typeArguments == null)
+          ? 'List<Object$nullTag>'
+          : 'List<${_flattenTypeArguments(typeArguments, nullTag)}>';
     case 'Map':
-      return 'Map<Object$nullTag, Object$nullTag>$nullTag';
+      return (typeArguments == null)
+          ? 'Map<Object$nullTag, Object$nullTag>'
+          : 'Map<${_flattenTypeArguments(typeArguments, nullTag)}>';
     default:
-      return '$dataType$nullTag';
+      return type.baseName;
   }
 }
 
+String _addGenericTypesNullable(NamedType field, String nullTag) {
+  return '${_addGenericTypes(field.type, nullTag)}$nullTag';
+}
+
 /// Generates Dart source code for the given AST represented by [root],
 /// outputting the code to [sink].
 void generateDart(DartOptions opt, Root root, StringSink sink) {
@@ -314,8 +361,8 @@
     indent.writeln('');
     indent.write('class ${klass.name} ');
     indent.scoped('{', '}', () {
-      for (final Field field in klass.fields) {
-        final String datatype = _addGenericTypes(field.dataType, nullTag);
+      for (final NamedType field in klass.fields) {
+        final String datatype = _addGenericTypesNullable(field, nullTag);
         indent.writeln('$datatype ${field.name};');
       }
       if (klass.fields.isNotEmpty) {
@@ -326,13 +373,13 @@
         indent.writeln(
           'final Map<Object$nullTag, Object$nullTag> pigeonMap = <Object$nullTag, Object$nullTag>{};',
         );
-        for (final Field field in klass.fields) {
+        for (final NamedType field in klass.fields) {
           indent.write('pigeonMap[\'${field.name}\'] = ');
-          if (customClassNames.contains(field.dataType)) {
+          if (customClassNames.contains(field.type.baseName)) {
             indent.addln(
               '${field.name} == null ? null : ${field.name}$unwrapOperator.encode();',
             );
-          } else if (customEnumNames.contains(field.dataType)) {
+          } else if (customEnumNames.contains(field.type.baseName)) {
             indent.addln(
               '${field.name} == null ? null : ${field.name}$unwrapOperator.index;',
             );
@@ -353,21 +400,28 @@
         indent.writeln('return ${klass.name}()');
         indent.nest(1, () {
           for (int index = 0; index < klass.fields.length; index += 1) {
-            final Field field = klass.fields[index];
+            final NamedType field = klass.fields[index];
             indent.write('..${field.name} = ');
-            if (customClassNames.contains(field.dataType)) {
+            if (customClassNames.contains(field.type.baseName)) {
               indent.format('''
 pigeonMap['${field.name}'] != null
-\t\t? ${field.dataType}.decode(pigeonMap['${field.name}']$unwrapOperator)
+\t\t? ${field.type.baseName}.decode(pigeonMap['${field.name}']$unwrapOperator)
 \t\t: null''', leadingSpace: false, trailingNewline: false);
-            } else if (customEnumNames.contains(field.dataType)) {
+            } else if (customEnumNames.contains(field.type.baseName)) {
               indent.format('''
 pigeonMap['${field.name}'] != null
-\t\t? ${field.dataType}.values[pigeonMap['${field.name}']$unwrapOperator as int]
+\t\t? ${field.type.baseName}.values[pigeonMap['${field.name}']$unwrapOperator as int]
 \t\t: null''', leadingSpace: false, trailingNewline: false);
+            } else if (field.type.typeArguments != null) {
+              final String genericType =
+                  _makeGenericTypeArguments(field.type, nullTag);
+              final String castCall = _makeGenericCastCall(field.type, nullTag);
+              indent.add(
+                '(pigeonMap[\'${field.name}\'] as $genericType$nullTag)$nullTag$castCall',
+              );
             } else {
               indent.add(
-                'pigeonMap[\'${field.name}\'] as ${_addGenericTypes(field.dataType, nullTag)}',
+                'pigeonMap[\'${field.name}\'] as ${_addGenericTypesNullable(field, nullTag)}',
               );
             }
             indent.addln(index == klass.fields.length - 1 ? ';' : '');
diff --git a/packages/pigeon/lib/generator_tools.dart b/packages/pigeon/lib/generator_tools.dart
index 560bcd1..d5bc4d1 100644
--- a/packages/pigeon/lib/generator_tools.dart
+++ b/packages/pigeon/lib/generator_tools.dart
@@ -154,28 +154,28 @@
   final bool isBuiltin;
 }
 
-/// Calculates the [HostDatatype] for the provided [Field].  It will check the
+/// Calculates the [HostDatatype] for the provided [NamedType].  It will check the
 /// field against the `classes` to check if it is a builtin type.
 /// `builtinResolver` will return the host datatype for the Dart datatype for
 /// builtin types.  `customResolver` can modify the datatype of custom types.
-HostDatatype getHostDatatype(Field field, List<Class> classes, List<Enum> enums,
-    String? Function(String) builtinResolver,
+HostDatatype getHostDatatype(NamedType field, List<Class> classes,
+    List<Enum> enums, String? Function(NamedType) builtinResolver,
     {String Function(String)? customResolver}) {
-  final String? datatype = builtinResolver(field.dataType);
+  final String? datatype = builtinResolver(field);
   if (datatype == null) {
-    if (classes.map((Class x) => x.name).contains(field.dataType)) {
+    if (classes.map((Class x) => x.name).contains(field.type.baseName)) {
       final String customName = customResolver != null
-          ? customResolver(field.dataType)
-          : field.dataType;
+          ? customResolver(field.type.baseName)
+          : field.type.baseName;
       return HostDatatype(datatype: customName, isBuiltin: false);
-    } else if (enums.map((Enum x) => x.name).contains(field.dataType)) {
+    } else if (enums.map((Enum x) => x.name).contains(field.type.baseName)) {
       final String customName = customResolver != null
-          ? customResolver(field.dataType)
-          : field.dataType;
+          ? customResolver(field.type.baseName)
+          : field.type.baseName;
       return HostDatatype(datatype: customName, isBuiltin: false);
     } else {
       throw Exception(
-          'unrecognized datatype for field:"${field.name}" of type:"${field.dataType}"');
+          'unrecognized datatype for field:"${field.name}" of type:"${field.type.baseName}"');
     }
   } else {
     return HostDatatype(datatype: datatype, isBuiltin: true);
@@ -285,8 +285,10 @@
 Iterable<EnumeratedClass> getCodecClasses(Api api) sync* {
   final Set<String> names = <String>{};
   for (final Method method in api.methods) {
-    names.add(method.returnType);
-    names.add(method.argType);
+    names.add(method.returnType.baseName);
+    if (method.arguments.isNotEmpty) {
+      names.add(method.arguments[0].type.baseName);
+    }
   }
   final List<String> sortedNames = names
       .where((String element) =>
diff --git a/packages/pigeon/lib/java_generator.dart b/packages/pigeon/lib/java_generator.dart
index f0fc6bd..7f2cd6d 100644
--- a/packages/pigeon/lib/java_generator.dart
+++ b/packages/pigeon/lib/java_generator.dart
@@ -5,19 +5,6 @@
 import 'ast.dart';
 import 'generator_tools.dart';
 
-const Map<String, String> _javaTypeForDartTypeMap = <String, String>{
-  'bool': 'Boolean',
-  'int': 'Long',
-  'String': 'String',
-  'double': 'Double',
-  'Uint8List': 'byte[]',
-  'Int32List': 'int[]',
-  'Int64List': 'long[]',
-  'Float64List': 'double[]',
-  'List': 'List<Object>',
-  'Map': 'Map<Object, Object>',
-};
-
 /// Options that control how Java code will be generated.
 class JavaOptions {
   /// Creates a [JavaOptions] object
@@ -113,22 +100,6 @@
   });
 }
 
-/// This performs Dart to Java type conversions.  If performs a passthrough of
-/// the input if it can't be converted.
-// TODO(gaaclarke): Remove this method and unify it with `_javaTypeForDartType`.
-String _javaTypeForDartTypePassthrough(String type) {
-  const Map<String, String> map = <String, String>{
-    'int': 'Integer',
-    'bool': 'Boolean',
-    'double': 'Double',
-    'Int32List': 'int[]',
-    'Uint8List': 'byte[]',
-    'Int64List': 'long[]',
-    'Float64List': 'double[]',
-  };
-  return map[type] ?? type;
-}
-
 void _writeHostApi(Indent indent, Api api) {
   assert(api.location == ApiLocation.host);
 
@@ -137,17 +108,18 @@
   indent.write('public interface ${api.name} ');
   indent.scoped('{', '}', () {
     for (final Method method in api.methods) {
-      final String argType = _javaTypeForDartTypePassthrough(method.argType);
       final String returnType = method.isAsynchronous
           ? 'void'
-          : _javaTypeForDartTypePassthrough(method.returnType);
+          : _javaTypeForDartType(method.returnType);
       final List<String> argSignature = <String>[];
-      if (method.argType != 'void') {
+      if (method.arguments.isNotEmpty) {
+        final String argType = _javaTypeForDartType(method.arguments[0].type);
         argSignature.add('$argType arg');
       }
       if (method.isAsynchronous) {
-        final String returnType =
-            method.returnType == 'void' ? 'Void' : method.returnType;
+        final String returnType = method.returnType.baseName == 'void'
+            ? 'Void'
+            : _javaTypeForDartType(method.returnType);
         argSignature.add('Result<$returnType> result');
       }
       indent.writeln('$returnType ${method.name}(${argSignature.join(', ')});');
@@ -180,15 +152,14 @@
           indent.scoped('{', '} else {', () {
             indent.write('channel.setMessageHandler((message, reply) -> ');
             indent.scoped('{', '});', () {
-              final String argType =
-                  _javaTypeForDartTypePassthrough(method.argType);
-              final String returnType =
-                  _javaTypeForDartTypePassthrough(method.returnType);
+              final String returnType = _javaTypeForDartType(method.returnType);
               indent.writeln('Map<String, Object> wrapped = new HashMap<>();');
               indent.write('try ');
               indent.scoped('{', '}', () {
                 final List<String> methodArgument = <String>[];
-                if (argType != 'void') {
+                if (method.arguments.isNotEmpty) {
+                  final String argType =
+                      _javaTypeForDartType(method.arguments[0].type);
                   indent.writeln('@SuppressWarnings("ConstantConditions")');
                   indent.writeln('$argType input = ($argType)message;');
                   indent.write('if (input == null) ');
@@ -200,7 +171,7 @@
                 }
                 if (method.isAsynchronous) {
                   final String resultValue =
-                      method.returnType == 'void' ? 'null' : 'result';
+                      method.returnType.baseName == 'void' ? 'null' : 'result';
                   methodArgument.add(
                     'result -> { '
                     'wrapped.put("${Keys.result}", $resultValue); '
@@ -212,7 +183,7 @@
                     'api.${method.name}(${methodArgument.join(', ')})';
                 if (method.isAsynchronous) {
                   indent.writeln('$call;');
-                } else if (method.returnType == 'void') {
+                } else if (method.returnType.baseName == 'void') {
                   indent.writeln('$call;');
                   indent.writeln('wrapped.put("${Keys.result}", null);');
                 } else {
@@ -265,15 +236,15 @@
 ''');
     for (final Method func in api.methods) {
       final String channelName = makeChannelName(api, func);
-      final String returnType = func.returnType == 'void'
+      final String returnType = func.returnType.baseName == 'void'
           ? 'Void'
-          : _javaTypeForDartTypePassthrough(func.returnType);
-      final String argType = _javaTypeForDartTypePassthrough(func.argType);
+          : _javaTypeForDartType(func.returnType);
       String sendArgument;
-      if (func.argType == 'void') {
+      if (func.arguments.isEmpty) {
         indent.write('public void ${func.name}(Reply<$returnType> callback) ');
         sendArgument = 'null';
       } else {
+        final String argType = _javaTypeForDartType(func.arguments[0].type);
         indent.write(
             'public void ${func.name}($argType argInput, Reply<$returnType> callback) ');
         sendArgument = 'argInput';
@@ -288,7 +259,7 @@
         indent.dec();
         indent.write('channel.send($sendArgument, channelReply -> ');
         indent.scoped('{', '});', () {
-          if (func.returnType == 'void') {
+          if (func.returnType.baseName == 'void') {
             indent.writeln('callback.reply(null);');
           } else {
             indent.writeln('@SuppressWarnings("ConstantConditions")');
@@ -301,30 +272,61 @@
   });
 }
 
-String _makeGetter(Field field) {
+String _makeGetter(NamedType field) {
   final String uppercased =
       field.name.substring(0, 1).toUpperCase() + field.name.substring(1);
   return 'get$uppercased';
 }
 
-String _makeSetter(Field field) {
+String _makeSetter(NamedType field) {
   final String uppercased =
       field.name.substring(0, 1).toUpperCase() + field.name.substring(1);
   return 'set$uppercased';
 }
 
-String? _javaTypeForDartType(String datatype) {
-  return _javaTypeForDartTypeMap[datatype];
+/// Converts a [List] of [TypeDeclaration]s to a comma separated [String] to be
+/// used in Java code.
+String _flattenTypeArguments(List<TypeDeclaration> args) {
+  return args.map<String>(_javaTypeForDartType).join(', ');
+}
+
+String? _javaTypeForBuiltinDartType(TypeDeclaration type) {
+  const Map<String, String> javaTypeForDartTypeMap = <String, String>{
+    'bool': 'Boolean',
+    'int': 'Long',
+    'String': 'String',
+    'double': 'Double',
+    'Uint8List': 'byte[]',
+    'Int32List': 'int[]',
+    'Int64List': 'long[]',
+    'Float64List': 'double[]',
+    'Map': 'Map<Object, Object>',
+  };
+  if (javaTypeForDartTypeMap.containsKey(type.baseName)) {
+    return javaTypeForDartTypeMap[type.baseName];
+  } else if (type.baseName == 'List') {
+    if (type.typeArguments == null) {
+      return 'List<Object>';
+    } else {
+      return 'List<${_flattenTypeArguments(type.typeArguments!)}>';
+    }
+  } else {
+    return null;
+  }
+}
+
+String _javaTypeForDartType(TypeDeclaration type) {
+  return _javaTypeForBuiltinDartType(type) ?? type.baseName;
 }
 
 String _castObject(
-    Field field, List<Class> classes, List<Enum> enums, String varName) {
-  final HostDatatype hostDatatype =
-      getHostDatatype(field, classes, enums, _javaTypeForDartType);
-  if (field.dataType == 'int') {
+    NamedType field, List<Class> classes, List<Enum> enums, String varName) {
+  final HostDatatype hostDatatype = getHostDatatype(field, classes, enums,
+      (NamedType x) => _javaTypeForBuiltinDartType(x.type));
+  if (field.type.baseName == 'int') {
     return '($varName == null) ? null : (($varName instanceof Integer) ? (Integer)$varName : (${hostDatatype.datatype})$varName)';
   } else if (!hostDatatype.isBuiltin &&
-      classes.map((Class x) => x.name).contains(field.dataType)) {
+      classes.map((Class x) => x.name).contains(field.type.baseName)) {
     return '${hostDatatype.datatype}.fromMap((Map)$varName)';
   } else {
     return '(${hostDatatype.datatype})$varName';
@@ -394,9 +396,9 @@
           '/** Generated class from Pigeon that represents data sent in messages. */');
       indent.write('public static class ${klass.name} ');
       indent.scoped('{', '}', () {
-        for (final Field field in klass.fields) {
-          final HostDatatype hostDatatype = getHostDatatype(
-              field, root.classes, root.enums, _javaTypeForDartType);
+        for (final NamedType field in klass.fields) {
+          final HostDatatype hostDatatype = getHostDatatype(field, root.classes,
+              root.enums, (NamedType x) => _javaTypeForBuiltinDartType(x.type));
           indent.writeln('private ${hostDatatype.datatype} ${field.name};');
           indent.writeln(
               'public ${hostDatatype.datatype} ${_makeGetter(field)}() { return ${field.name}; }');
@@ -407,16 +409,19 @@
         indent.write('Map<String, Object> toMap() ');
         indent.scoped('{', '}', () {
           indent.writeln('Map<String, Object> toMapResult = new HashMap<>();');
-          for (final Field field in klass.fields) {
+          for (final NamedType field in klass.fields) {
             final HostDatatype hostDatatype = getHostDatatype(
-                field, root.classes, root.enums, _javaTypeForDartType);
+                field,
+                root.classes,
+                root.enums,
+                (NamedType x) => _javaTypeForBuiltinDartType(x.type));
             String toWriteValue = '';
             if (!hostDatatype.isBuiltin &&
-                rootClassNameSet.contains(field.dataType)) {
+                rootClassNameSet.contains(field.type.baseName)) {
               final String fieldName = field.name;
               toWriteValue = '($fieldName == null) ? null : $fieldName.toMap()';
             } else if (!hostDatatype.isBuiltin &&
-                rootEnumNameSet.contains(field.dataType)) {
+                rootEnumNameSet.contains(field.type.baseName)) {
               toWriteValue = '${field.name}.index';
             } else {
               toWriteValue = field.name;
@@ -428,11 +433,11 @@
         indent.write('static ${klass.name} fromMap(Map<String, Object> map) ');
         indent.scoped('{', '}', () {
           indent.writeln('${klass.name} fromMapResult = new ${klass.name}();');
-          for (final Field field in klass.fields) {
+          for (final NamedType field in klass.fields) {
             indent.writeln('Object ${field.name} = map.get("${field.name}");');
-            if (rootEnumNameSet.contains(field.dataType)) {
+            if (rootEnumNameSet.contains(field.type.baseName)) {
               indent.writeln(
-                  'fromMapResult.${field.name} = ${field.dataType}.values()[(int)${field.name}];');
+                  'fromMapResult.${field.name} = ${field.type.baseName}.values()[(int)${field.name}];');
             } else {
               indent.writeln(
                   'fromMapResult.${field.name} = ${_castObject(field, root.classes, root.enums, field.name)};');
diff --git a/packages/pigeon/lib/objc_generator.dart b/packages/pigeon/lib/objc_generator.dart
index 77612a4..3641a22 100644
--- a/packages/pigeon/lib/objc_generator.dart
+++ b/packages/pigeon/lib/objc_generator.dart
@@ -79,37 +79,48 @@
   'Map': 'NSDictionary',
 };
 
-const Map<String, String> _propertyTypeForDartTypeMap = <String, String>{
-  'String': 'copy',
-  'bool': 'strong',
-  'int': 'strong',
-  'double': 'strong',
-  'Uint8List': 'strong',
-  'Int32List': 'strong',
-  'Int64List': 'strong',
-  'Float64List': 'strong',
-  'List': 'strong',
-  'Map': 'strong',
-};
+String _flattenTypeArguments(String? classPrefix, List<TypeDeclaration> args) {
+  final String result = args
+      .map<String>(
+          (TypeDeclaration e) => '${_objcTypeForDartType(classPrefix, e)} *')
+      .join(', ');
+  return result;
+}
 
-String? _objcTypePtrForPrimitiveDartType(String type) {
-  return _objcTypeForDartTypeMap.containsKey(type)
-      ? '${_objcTypeForDartTypeMap[type]} *'
+String? _objcTypePtrForPrimitiveDartType(String? classPrefix, NamedType field) {
+  return _objcTypeForDartTypeMap.containsKey(field.type.baseName)
+      ? '${_objcTypeForDartType(classPrefix, field.type)} *'
       : null;
 }
 
 /// Returns the objc type for a dart [type], prepending the [classPrefix] for
 /// generated classes.  For example:
 /// _objcTypeForDartType(null, 'int') => 'NSNumber'.
-String _objcTypeForDartType(String? classPrefix, String type) {
-  final String? builtinObjcType = _objcTypeForDartTypeMap[type];
-  return builtinObjcType ?? _className(classPrefix, type);
+String _objcTypeForDartType(String? classPrefix, TypeDeclaration field) {
+  return _objcTypeForDartTypeMap.containsKey(field.baseName)
+      ? field.typeArguments == null
+          ? _objcTypeForDartTypeMap[field.baseName]!
+          : '${_objcTypeForDartTypeMap[field.baseName]}<${_flattenTypeArguments(classPrefix, field.typeArguments!)}>'
+      : _className(classPrefix, field.baseName);
 }
 
-String _propertyTypeForDartType(String type) {
-  final String? result = _propertyTypeForDartTypeMap[type];
+String _propertyTypeForDartType(NamedType field) {
+  const Map<String, String> propertyTypeForDartTypeMap = <String, String>{
+    'String': 'copy',
+    'bool': 'strong',
+    'int': 'strong',
+    'double': 'strong',
+    'Uint8List': 'strong',
+    'Int32List': 'strong',
+    'Int64List': 'strong',
+    'Float64List': 'strong',
+    'List': 'strong',
+    'Map': 'strong',
+  };
+
+  final String? result = propertyTypeForDartTypeMap[field.type.baseName];
   if (result == null) {
-    return 'assign';
+    return 'strong';
   } else {
     return result;
   }
@@ -120,19 +131,17 @@
   final List<String> enumNames = enums.map((Enum x) => x.name).toList();
   for (final Class klass in classes) {
     indent.writeln('@interface ${_className(prefix, klass.name)} : NSObject');
-    for (final Field field in klass.fields) {
-      final HostDatatype hostDatatype = getHostDatatype(
-          field, classes, enums, _objcTypePtrForPrimitiveDartType,
-          customResolver: enumNames.contains(field.dataType)
+    for (final NamedType field in klass.fields) {
+      final HostDatatype hostDatatype = getHostDatatype(field, classes, enums,
+          (NamedType x) => _objcTypePtrForPrimitiveDartType(prefix, x),
+          customResolver: enumNames.contains(field.type.baseName)
               ? (String x) => _className(prefix, x)
               : (String x) => '${_className(prefix, x)} *');
       late final String propertyType;
-      if (hostDatatype.isBuiltin) {
-        propertyType = _propertyTypeForDartType(field.dataType);
-      } else if (enumNames.contains(field.dataType)) {
+      if (enumNames.contains(field.type.baseName)) {
         propertyType = 'assign';
       } else {
-        propertyType = 'strong';
+        propertyType = _propertyTypeForDartType(field);
       }
       final String nullability =
           hostDatatype.datatype.contains('*') ? ', nullable' : '';
@@ -230,36 +239,37 @@
     final String returnTypeName =
         _objcTypeForDartType(options.prefix, func.returnType);
     if (func.isAsynchronous) {
-      if (func.returnType == 'void') {
-        if (func.argType == 'void') {
+      if (func.returnType.baseName == 'void') {
+        if (func.arguments.isEmpty) {
           indent.writeln(
               '-(void)${func.name}:(void(^)(FlutterError *_Nullable))completion;');
         } else {
           final String argType =
-              _objcTypeForDartType(options.prefix, func.argType);
+              _objcTypeForDartType(options.prefix, func.arguments[0].type);
           indent.writeln(
               '-(void)${func.name}:(nullable $argType *)input completion:(void(^)(FlutterError *_Nullable))completion;');
         }
       } else {
-        if (func.argType == 'void') {
+        if (func.arguments.isEmpty) {
           indent.writeln(
               '-(void)${func.name}:(void(^)($returnTypeName *_Nullable, FlutterError *_Nullable))completion;');
         } else {
           final String argType =
-              _objcTypeForDartType(options.prefix, func.argType);
+              _objcTypeForDartType(options.prefix, func.arguments[0].type);
           indent.writeln(
               '-(void)${func.name}:(nullable $argType *)input completion:(void(^)($returnTypeName *_Nullable, FlutterError *_Nullable))completion;');
         }
       }
     } else {
-      final String returnType =
-          func.returnType == 'void' ? 'void' : 'nullable $returnTypeName *';
-      if (func.argType == 'void') {
+      final String returnType = func.returnType.baseName == 'void'
+          ? 'void'
+          : 'nullable $returnTypeName *';
+      if (func.arguments.isEmpty) {
         indent.writeln(
             '-($returnType)${func.name}:(FlutterError *_Nullable *_Nonnull)error;');
       } else {
         final String argType =
-            _objcTypeForDartType(options.prefix, func.argType);
+            _objcTypeForDartType(options.prefix, func.arguments[0].type);
         indent.writeln(
             '-($returnType)${func.name}:($argType*)input error:(FlutterError *_Nullable *_Nonnull)error;');
       }
@@ -280,11 +290,13 @@
   for (final Method func in api.methods) {
     final String returnType =
         _objcTypeForDartType(options.prefix, func.returnType);
-    final String callbackType = _callbackForType(func.returnType, returnType);
-    if (func.argType == 'void') {
+    final String callbackType =
+        _callbackForType(func.returnType.baseName, returnType);
+    if (func.arguments.isEmpty) {
       indent.writeln('- (void)${func.name}:($callbackType)completion;');
     } else {
-      final String argType = _objcTypeForDartType(options.prefix, func.argType);
+      final String argType =
+          _objcTypeForDartType(options.prefix, func.arguments[0].type);
       indent.writeln(
           '- (void)${func.name}:($argType*)input completion:($callbackType)completion;');
     }
@@ -351,9 +363,9 @@
 }
 
 String _dictGetter(
-    List<String> classNames, String dict, Field field, String? prefix) {
-  if (classNames.contains(field.dataType)) {
-    String className = field.dataType;
+    List<String> classNames, String dict, NamedType field, String? prefix) {
+  if (classNames.contains(field.type.baseName)) {
+    String className = field.type.baseName;
     if (prefix != null) {
       className = '$prefix$className';
     }
@@ -364,10 +376,10 @@
 }
 
 String _dictValue(
-    List<String> classNames, List<String> enumNames, Field field) {
-  if (classNames.contains(field.dataType)) {
+    List<String> classNames, List<String> enumNames, NamedType field) {
+  if (classNames.contains(field.type.baseName)) {
     return '(self.${field.name} ? [self.${field.name} toMap] : [NSNull null])';
-  } else if (enumNames.contains(field.dataType)) {
+  } else if (enumNames.contains(field.type.baseName)) {
     return '@(self.${field.name})';
   } else {
     return '(self.${field.name} ? self.${field.name} : [NSNull null])';
@@ -403,18 +415,18 @@
             final String returnType =
                 _objcTypeForDartType(options.prefix, func.returnType);
             String syncCall;
-            if (func.argType == 'void') {
+            if (func.arguments.isEmpty) {
               syncCall = '[api ${func.name}:&error]';
             } else {
               final String argType =
-                  _objcTypeForDartType(options.prefix, func.argType);
+                  _objcTypeForDartType(options.prefix, func.arguments[0].type);
               indent.writeln('$argType *input = message;');
               syncCall = '[api ${func.name}:input error:&error]';
             }
             if (func.isAsynchronous) {
-              if (func.returnType == 'void') {
+              if (func.returnType.baseName == 'void') {
                 const String callback = 'callback(wrapResult(nil, error));';
-                if (func.argType == 'void') {
+                if (func.arguments.isEmpty) {
                   indent.writeScoped(
                       '[api ${func.name}:^(FlutterError *_Nullable error) {',
                       '}];', () {
@@ -429,7 +441,7 @@
                 }
               } else {
                 const String callback = 'callback(wrapResult(output, error));';
-                if (func.argType == 'void') {
+                if (func.arguments.isEmpty) {
                   indent.writeScoped(
                       '[api ${func.name}:^($returnType *_Nullable output, FlutterError *_Nullable error) {',
                       '}];', () {
@@ -445,7 +457,7 @@
               }
             } else {
               indent.writeln('FlutterError *error;');
-              if (func.returnType == 'void') {
+              if (func.returnType.baseName == 'void') {
                 indent.writeln('$syncCall;');
                 indent.writeln('callback(wrapResult(nil, error));');
               } else {
@@ -487,14 +499,16 @@
   for (final Method func in api.methods) {
     final String returnType =
         _objcTypeForDartType(options.prefix, func.returnType);
-    final String callbackType = _callbackForType(func.returnType, returnType);
+    final String callbackType =
+        _callbackForType(func.returnType.baseName, returnType);
 
     String sendArgument;
-    if (func.argType == 'void') {
+    if (func.arguments.isEmpty) {
       indent.write('- (void)${func.name}:($callbackType)completion ');
       sendArgument = 'nil';
     } else {
-      final String argType = _objcTypeForDartType(options.prefix, func.argType);
+      final String argType =
+          _objcTypeForDartType(options.prefix, func.arguments[0].type);
       indent.write(
           '- (void)${func.name}:($argType*)input completion:($callbackType)completion ');
       sendArgument = 'input';
@@ -512,7 +526,7 @@
       indent.dec();
       indent.write('[channel sendMessage:$sendArgument reply:^(id reply) ');
       indent.scoped('{', '}];', () {
-        if (func.returnType == 'void') {
+        if (func.returnType.baseName == 'void') {
           indent.writeln('completion(nil);');
         } else {
           indent.writeln('$returnType * output = reply;');
@@ -580,8 +594,8 @@
     indent.scoped('{', '}', () {
       const String resultName = 'result';
       indent.writeln('$className* $resultName = [[$className alloc] init];');
-      for (final Field field in klass.fields) {
-        if (enumNames.contains(field.dataType)) {
+      for (final NamedType field in klass.fields) {
+        if (enumNames.contains(field.type.baseName)) {
           indent.writeln(
               '$resultName.${field.name} = [${_dictGetter(classNames, 'dict', field, options.prefix)} integerValue];');
         } else {
@@ -599,7 +613,7 @@
     indent.write('-(NSDictionary*)toMap ');
     indent.scoped('{', '}', () {
       indent.write('return [NSDictionary dictionaryWithObjectsAndKeys:');
-      for (final Field field in klass.fields) {
+      for (final NamedType field in klass.fields) {
         indent.add(
             _dictValue(classNames, enumNames, field) + ', @"${field.name}", ');
       }
diff --git a/packages/pigeon/lib/pigeon_lib.dart b/packages/pigeon/lib/pigeon_lib.dart
index a685a3b..9026226 100644
--- a/packages/pigeon/lib/pigeon_lib.dart
+++ b/packages/pigeon/lib/pigeon_lib.dart
@@ -13,7 +13,8 @@
 import 'package:analyzer/dart/analysis/results.dart' show ParsedUnitResult;
 import 'package:analyzer/dart/analysis/session.dart' show AnalysisSession;
 import 'package:analyzer/dart/ast/ast.dart' as dart_ast;
-import 'package:analyzer/dart/ast/ast.dart' show CompilationUnit;
+import 'package:analyzer/dart/ast/syntactic_entity.dart'
+    as dart_ast_syntactic_entity;
 import 'package:analyzer/dart/ast/visitor.dart' as dart_ast_visitor;
 import 'package:analyzer/error/error.dart' show AnalysisError;
 import 'package:args/args.dart';
@@ -382,19 +383,24 @@
       root.classes.map((Class x) => x.name).toList();
   final Iterable<String> customEnums = root.enums.map((Enum x) => x.name);
   for (final Class klass in root.classes) {
-    for (final Field field in klass.fields) {
-      if (field.typeArguments != null) {
+    for (final NamedType field in klass.fields) {
+      if (field.type.typeArguments != null) {
+        for (final TypeDeclaration typeArgument in field.type.typeArguments!) {
+          if (!typeArgument.isNullable) {
+            result.add(Error(
+              message:
+                  'Generic type arguments must be nullable in field "${field.name}" in class "${klass.name}".',
+              lineNumber: _calculateLineNumberNullable(source, field.offset),
+            ));
+          }
+        }
+      }
+      if (!(validTypes.contains(field.type.baseName) ||
+          customClasses.contains(field.type.baseName) ||
+          customEnums.contains(field.type.baseName))) {
         result.add(Error(
           message:
-              'Unsupported datatype:"${field.dataType}" in class "${klass.name}". Generic fields aren\'t yet supported (https://github.com/flutter/flutter/issues/63468).',
-          lineNumber: _calculateLineNumberNullable(source, field.offset),
-        ));
-      } else if (!(validTypes.contains(field.dataType) ||
-          customClasses.contains(field.dataType) ||
-          customEnums.contains(field.dataType))) {
-        result.add(Error(
-          message:
-              'Unsupported datatype:"${field.dataType}" in class "${klass.name}".',
+              'Unsupported datatype:"${field.type.baseName}" in class "${klass.name}".',
           lineNumber: _calculateLineNumberNullable(source, field.offset),
         ));
       }
@@ -402,31 +408,38 @@
   }
   for (final Api api in root.apis) {
     for (final Method method in api.methods) {
-      if (method.isReturnNullable) {
+      if (method.returnType.isNullable) {
         result.add(Error(
           message:
-              'Nullable return types types aren\'t supported for Pigeon methods: "${method.argType}" in API: "${api.name}" method: "${method.name}"',
+              'Nullable return types types aren\'t supported for Pigeon methods: "${method.arguments[0].type.baseName}" in API: "${api.name}" method: "${method.name}"',
           lineNumber: _calculateLineNumberNullable(source, method.offset),
         ));
       }
-      if (method.isArgNullable) {
+      if (method.arguments.length > 1) {
         result.add(Error(
           message:
-              'Nullable argument types aren\'t supported for Pigeon methods: "${method.argType}" in API: "${api.name}" method: "${method.name}"',
+              'Multiple arguments aren\'t yet supported, in API: "${api.name}" method: "${method.name} (https://github.com/flutter/flutter/issues/86971)"',
           lineNumber: _calculateLineNumberNullable(source, method.offset),
         ));
       }
-      if (customEnums.contains(method.argType)) {
+      if (method.arguments.isNotEmpty &&
+          customEnums.contains(method.arguments[0].type.baseName)) {
         result.add(Error(
           message:
-              'Enums aren\'t yet supported for primitive arguments: "${method.argType}" in API: "${api.name}" method: "${method.name}" (https://github.com/flutter/flutter/issues/87307)',
+              'Enums aren\'t yet supported for primitive arguments: "${method.arguments[0]}" in API: "${api.name}" method: "${method.name}" (https://github.com/flutter/flutter/issues/87307)',
           lineNumber: _calculateLineNumberNullable(source, method.offset),
         ));
       }
-      if (customEnums.contains(method.returnType)) {
+      if (customEnums.contains(method.returnType.baseName)) {
         result.add(Error(
           message:
               'Enums aren\'t yet supported for primitive return types: "${method.returnType}" in API: "${api.name}" method: "${method.name}" (https://github.com/flutter/flutter/issues/87307)',
+        ));
+      }
+      if (method.arguments.isNotEmpty && method.arguments[0].type.isNullable) {
+        result.add(Error(
+          message:
+              'Nullable argument types aren\'t supported for Pigeon methods: "${method.arguments[0].type.baseName}" in API: "${api.name}" method: "${method.name}"',
           lineNumber: _calculateLineNumberNullable(source, method.offset),
         ));
       }
@@ -481,8 +494,10 @@
     final Set<String> referencedTypes = <String>{};
     for (final Api api in _apis) {
       for (final Method method in api.methods) {
-        referencedTypes.add(method.argType);
-        referencedTypes.add(method.returnType);
+        if (method.arguments.isNotEmpty) {
+          referencedTypes.add(method.arguments[0].type.baseName);
+        }
+        referencedTypes.add(method.returnType.baseName);
       }
     }
 
@@ -491,12 +506,12 @@
       final String next = classesToCheck.last;
       classesToCheck.removeLast();
       final Class aClass = _classes.firstWhere((Class x) => x.name == next,
-          orElse: () => Class(name: '', fields: <Field>[]));
-      for (final Field field in aClass.fields) {
-        if (!referencedTypes.contains(field.dataType) &&
-            !validTypes.contains(field.dataType)) {
-          referencedTypes.add(field.dataType);
-          classesToCheck.add(field.dataType);
+          orElse: () => Class(name: '', fields: <NamedType>[]));
+      for (final NamedType field in aClass.fields) {
+        if (!referencedTypes.contains(field.type.baseName) &&
+            !validTypes.contains(field.type.baseName)) {
+          referencedTypes.add(field.type.baseName);
+          classesToCheck.add(field.type.baseName);
         }
       }
     }
@@ -625,37 +640,59 @@
         );
       }
     } else {
-      _currentClass = Class(name: node.name.name, fields: <Field>[]);
+      _currentClass = Class(name: node.name.name, fields: <NamedType>[]);
     }
 
     node.visitChildren(this);
     return null;
   }
 
+  NamedType formalParameterToField(dart_ast.FormalParameter parameter) {
+    final dart_ast.TypeName typeName = parameter.childEntities.firstWhere(
+        (dart_ast_syntactic_entity.SyntacticEntity e) =>
+            e is dart_ast.TypeName) as dart_ast.TypeName;
+    final String argTypeBaseName = typeName.name.name;
+    final bool isNullable = typeName.question != null;
+    final List<TypeDeclaration>? argTypeArguments =
+        typeAnnotationsToTypeArguments(typeName.typeArguments);
+    return NamedType(
+        type: TypeDeclaration(
+            baseName: argTypeBaseName,
+            isNullable: isNullable,
+            typeArguments: argTypeArguments),
+        name: parameter.identifier?.name ?? '',
+        offset: null);
+  }
+
+  static T? getFirstChildOfType<T>(dart_ast.AstNode entity) {
+    for (final dart_ast_syntactic_entity.SyntacticEntity child
+        in entity.childEntities) {
+      if (child is T) {
+        return child as T;
+      }
+    }
+    return null;
+  }
+
   @override
   Object? visitMethodDeclaration(dart_ast.MethodDeclaration node) {
     final dart_ast.FormalParameterList parameters = node.parameters!;
-    late String argType;
-    bool isNullable = false;
-    if (parameters.parameters.isEmpty) {
-      argType = 'void';
-    } else {
-      final dart_ast.FormalParameter firstParameter =
-          parameters.parameters.first;
-      final dart_ast.TypeName typeName = firstParameter.childEntities
-          // ignore: always_specify_types
-          .firstWhere((e) => e is dart_ast.TypeName) as dart_ast.TypeName;
-      argType = typeName.name.name;
-      isNullable = typeName.question != null;
-    }
+    final List<NamedType> arguments =
+        parameters.parameters.map(formalParameterToField).toList();
     final bool isAsynchronous = _hasMetadata(node.metadata, 'async');
     if (_currentApi != null) {
+      // Methods without named return types aren't supported.
+      final dart_ast.TypeAnnotation returnType = node.returnType!;
+      final dart_ast.SimpleIdentifier returnTypeIdentifier =
+          getFirstChildOfType<dart_ast.SimpleIdentifier>(returnType)!;
       _currentApi!.methods.add(Method(
           name: node.name.name,
-          returnType: node.returnType.toString(),
-          argType: argType,
-          isReturnNullable: node.returnType!.question != null,
-          isArgNullable: isNullable,
+          returnType: TypeDeclaration(
+              baseName: returnTypeIdentifier.name,
+              typeArguments: typeAnnotationsToTypeArguments(
+                  (returnType as dart_ast.NamedType).typeArguments),
+              isNullable: returnType.question != null),
+          arguments: arguments,
           isAsynchronous: isAsynchronous,
           offset: node.offset));
     } else if (_currentClass != null) {
@@ -679,6 +716,23 @@
     return null;
   }
 
+  List<TypeDeclaration>? typeAnnotationsToTypeArguments(
+      dart_ast.TypeArgumentList? typeArguments) {
+    List<TypeDeclaration>? result;
+    if (typeArguments != null) {
+      for (final Object x in typeArguments.childEntities) {
+        if (x is dart_ast.TypeName) {
+          result ??= <TypeDeclaration>[];
+          result.add(TypeDeclaration(
+              baseName: x.name.name,
+              isNullable: x.question != null,
+              typeArguments: typeAnnotationsToTypeArguments(x.typeArguments)));
+        }
+      }
+    }
+    return result;
+  }
+
   @override
   Object? visitFieldDeclaration(dart_ast.FieldDeclaration node) {
     if (_currentClass != null) {
@@ -698,26 +752,13 @@
               lineNumber: _calculateLineNumber(source, node.offset)));
         } else {
           final dart_ast.TypeArgumentList? typeArguments = type.typeArguments;
-          _currentClass!.fields.add(Field(
-            name: node.fields.variables[0].name.name,
-            dataType: type.name.name,
-            isNullable: type.question != null,
-            // TODO(aaclarke): This probably has to be recursive at some point.
-            // ignore: prefer_null_aware_operators
-            typeArguments: typeArguments == null
-                ? null
-                : typeArguments.arguments
-                    .map((dart_ast.TypeAnnotation e) => Field(
-                          name: '',
-                          dataType: (e.childEntities.first
-                                  as dart_ast.SimpleIdentifier)
-                              .name,
-                          isNullable: e.question != null,
-                          offset: e.offset,
-                        ))
-                    .toList(),
-            offset: node.offset,
-          ));
+          _currentClass!.fields.add(NamedType(
+              type: TypeDeclaration(
+                  baseName: type.name.name,
+                  isNullable: type.question != null,
+                  typeArguments: typeAnnotationsToTypeArguments(typeArguments)),
+              name: node.fields.variables[0].name.name,
+              offset: node.offset));
         }
       } else {
         _errors.add(Error(
@@ -783,7 +824,7 @@
         final ParsedUnitResult result =
             session.getParsedUnit2(path) as ParsedUnitResult;
         if (result.errors.isEmpty) {
-          final CompilationUnit unit = result.unit;
+          final dart_ast.CompilationUnit unit = result.unit;
           unit.accept(rootBuilder);
         } else {
           for (final AnalysisError error in result.errors) {
diff --git a/packages/pigeon/pigeons/all_datatypes.dart b/packages/pigeon/pigeons/all_datatypes.dart
index 0a8c2c2..138b4ca 100644
--- a/packages/pigeon/pigeons/all_datatypes.dart
+++ b/packages/pigeon/pigeons/all_datatypes.dart
@@ -17,6 +17,8 @@
   List? aList;
   // ignore: always_specify_types
   Map? aMap;
+  List<List<bool?>?>? nestedList;
+  Map<String?, String?>? mapWithAnnotations;
 }
 
 @HostApi()
diff --git a/packages/pigeon/pigeons/primitive.dart b/packages/pigeon/pigeons/primitive.dart
index 2b45353..cc8eea5 100644
--- a/packages/pigeon/pigeons/primitive.dart
+++ b/packages/pigeon/pigeons/primitive.dart
@@ -15,6 +15,8 @@
   // ignore: always_specify_types
   List aList(List value);
   Int32List anInt32List(Int32List value);
+  List<bool?> aBoolList(List<bool?> value);
+  Map<String?, int?> aStringIntMap(Map<String?, int?> value);
 }
 
 @FlutterApi()
@@ -28,4 +30,6 @@
   // ignore: always_specify_types
   List aList(List value);
   Int32List anInt32List(Int32List value);
+  List<bool?> aBoolList(List<bool?> value);
+  Map<String?, int?> aStringIntMap(Map<String?, int?> value);
 }
diff --git a/packages/pigeon/platform_tests/android_unit_tests/android/app/src/test/java/com/example/android_unit_tests/PrimitiveTest.java b/packages/pigeon/platform_tests/android_unit_tests/android/app/src/test/java/com/example/android_unit_tests/PrimitiveTest.java
index 36ce51c..d557d21 100644
--- a/packages/pigeon/platform_tests/android_unit_tests/android/app/src/test/java/com/example/android_unit_tests/PrimitiveTest.java
+++ b/packages/pigeon/platform_tests/android_unit_tests/android/app/src/test/java/com/example/android_unit_tests/PrimitiveTest.java
@@ -37,10 +37,10 @@
     PrimitiveFlutterApi api = new PrimitiveFlutterApi(binaryMessenger);
     boolean[] didCall = {false};
     api.anInt(
-        1,
-        (Integer result) -> {
+        1L,
+        (Long result) -> {
           didCall[0] = true;
-          assertEquals(result, (Integer) 1);
+          assertEquals(result, (Long) 1L);
         });
     assertTrue(didCall[0]);
   }
@@ -94,7 +94,7 @@
     boolean[] didCall = {false};
     api.aMap(
         Collections.singletonMap("hello", 1),
-        (Map result) -> {
+        (Map<Object, Object> result) -> {
           didCall[0] = true;
           assertEquals(result, Collections.singletonMap("hello", 1));
         });
@@ -108,7 +108,7 @@
     boolean[] didCall = {false};
     api.aList(
         Collections.singletonList("hello"),
-        (List result) -> {
+        (List<Object> result) -> {
           didCall[0] = true;
           assertEquals(result, Collections.singletonList("hello"));
         });
diff --git a/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/all_datatypes.dart b/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/all_datatypes.dart
index 09db457..adf2c86 100644
--- a/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/all_datatypes.dart
+++ b/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/all_datatypes.dart
@@ -23,6 +23,8 @@
   Float64List? aFloatArray;
   List<Object?>? aList;
   Map<Object?, Object?>? aMap;
+  List<List<bool?>?>? nestedList;
+  Map<String?, String?>? mapWithAnnotations;
 
   Object encode() {
     final Map<Object?, Object?> pigeonMap = <Object?, Object?>{};
@@ -36,6 +38,8 @@
     pigeonMap['aFloatArray'] = aFloatArray;
     pigeonMap['aList'] = aList;
     pigeonMap['aMap'] = aMap;
+    pigeonMap['nestedList'] = nestedList;
+    pigeonMap['mapWithAnnotations'] = mapWithAnnotations;
     return pigeonMap;
   }
 
@@ -51,7 +55,12 @@
       ..a8ByteArray = pigeonMap['a8ByteArray'] as Int64List?
       ..aFloatArray = pigeonMap['aFloatArray'] as Float64List?
       ..aList = pigeonMap['aList'] as List<Object?>?
-      ..aMap = pigeonMap['aMap'] as Map<Object?, Object?>?;
+      ..aMap = pigeonMap['aMap'] as Map<Object?, Object?>?
+      ..nestedList =
+          (pigeonMap['nestedList'] as List<Object?>?)?.cast<List<bool?>?>()
+      ..mapWithAnnotations =
+          (pigeonMap['mapWithAnnotations'] as Map<Object?, Object?>?)
+              ?.cast<String?, String?>();
   }
 }
 
diff --git a/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/primitive.dart b/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/primitive.dart
new file mode 100644
index 0000000..ae36e10
--- /dev/null
+++ b/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/primitive.dart
@@ -0,0 +1,411 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// Autogenerated from Pigeon (v0.3.0), do not edit directly.
+// See also: https://pub.dev/packages/pigeon
+// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name
+// @dart = 2.12
+import 'dart:async';
+import 'dart:typed_data' show Uint8List, Int32List, Int64List, Float64List;
+
+import 'package:flutter/foundation.dart' show WriteBuffer, ReadBuffer;
+import 'package:flutter/services.dart';
+
+class _PrimitiveHostApiCodec extends StandardMessageCodec {
+  const _PrimitiveHostApiCodec();
+}
+
+class PrimitiveHostApi {
+  /// Constructor for [PrimitiveHostApi].  The [binaryMessenger] named argument is
+  /// available for dependency injection.  If it is left null, the default
+  /// BinaryMessenger will be used which routes to the host platform.
+  PrimitiveHostApi({BinaryMessenger? binaryMessenger})
+      : _binaryMessenger = binaryMessenger;
+
+  final BinaryMessenger? _binaryMessenger;
+
+  static const MessageCodec<Object?> codec = _PrimitiveHostApiCodec();
+
+  Future<int> anInt(int arg) async {
+    final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
+        'dev.flutter.pigeon.PrimitiveHostApi.anInt', codec,
+        binaryMessenger: _binaryMessenger);
+    final Map<Object?, Object?>? replyMap =
+        await channel.send(arg) as Map<Object?, Object?>?;
+    if (replyMap == null) {
+      throw PlatformException(
+        code: 'channel-error',
+        message: 'Unable to establish connection on channel.',
+        details: null,
+      );
+    } else if (replyMap['error'] != null) {
+      final Map<Object?, Object?> error =
+          (replyMap['error'] as Map<Object?, Object?>?)!;
+      throw PlatformException(
+        code: (error['code'] as String?)!,
+        message: error['message'] as String?,
+        details: error['details'],
+      );
+    } else {
+      return (replyMap['result'] as int?)!;
+    }
+  }
+
+  Future<bool> aBool(bool arg) async {
+    final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
+        'dev.flutter.pigeon.PrimitiveHostApi.aBool', codec,
+        binaryMessenger: _binaryMessenger);
+    final Map<Object?, Object?>? replyMap =
+        await channel.send(arg) as Map<Object?, Object?>?;
+    if (replyMap == null) {
+      throw PlatformException(
+        code: 'channel-error',
+        message: 'Unable to establish connection on channel.',
+        details: null,
+      );
+    } else if (replyMap['error'] != null) {
+      final Map<Object?, Object?> error =
+          (replyMap['error'] as Map<Object?, Object?>?)!;
+      throw PlatformException(
+        code: (error['code'] as String?)!,
+        message: error['message'] as String?,
+        details: error['details'],
+      );
+    } else {
+      return (replyMap['result'] as bool?)!;
+    }
+  }
+
+  Future<String> aString(String arg) async {
+    final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
+        'dev.flutter.pigeon.PrimitiveHostApi.aString', codec,
+        binaryMessenger: _binaryMessenger);
+    final Map<Object?, Object?>? replyMap =
+        await channel.send(arg) as Map<Object?, Object?>?;
+    if (replyMap == null) {
+      throw PlatformException(
+        code: 'channel-error',
+        message: 'Unable to establish connection on channel.',
+        details: null,
+      );
+    } else if (replyMap['error'] != null) {
+      final Map<Object?, Object?> error =
+          (replyMap['error'] as Map<Object?, Object?>?)!;
+      throw PlatformException(
+        code: (error['code'] as String?)!,
+        message: error['message'] as String?,
+        details: error['details'],
+      );
+    } else {
+      return (replyMap['result'] as String?)!;
+    }
+  }
+
+  Future<double> aDouble(double arg) async {
+    final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
+        'dev.flutter.pigeon.PrimitiveHostApi.aDouble', codec,
+        binaryMessenger: _binaryMessenger);
+    final Map<Object?, Object?>? replyMap =
+        await channel.send(arg) as Map<Object?, Object?>?;
+    if (replyMap == null) {
+      throw PlatformException(
+        code: 'channel-error',
+        message: 'Unable to establish connection on channel.',
+        details: null,
+      );
+    } else if (replyMap['error'] != null) {
+      final Map<Object?, Object?> error =
+          (replyMap['error'] as Map<Object?, Object?>?)!;
+      throw PlatformException(
+        code: (error['code'] as String?)!,
+        message: error['message'] as String?,
+        details: error['details'],
+      );
+    } else {
+      return (replyMap['result'] as double?)!;
+    }
+  }
+
+  Future<Map<Object?, Object?>> aMap(Map<Object?, Object?> arg) async {
+    final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
+        'dev.flutter.pigeon.PrimitiveHostApi.aMap', codec,
+        binaryMessenger: _binaryMessenger);
+    final Map<Object?, Object?>? replyMap =
+        await channel.send(arg) as Map<Object?, Object?>?;
+    if (replyMap == null) {
+      throw PlatformException(
+        code: 'channel-error',
+        message: 'Unable to establish connection on channel.',
+        details: null,
+      );
+    } else if (replyMap['error'] != null) {
+      final Map<Object?, Object?> error =
+          (replyMap['error'] as Map<Object?, Object?>?)!;
+      throw PlatformException(
+        code: (error['code'] as String?)!,
+        message: error['message'] as String?,
+        details: error['details'],
+      );
+    } else {
+      return (replyMap['result'] as Map<Object?, Object?>?)!;
+    }
+  }
+
+  Future<List<Object?>> aList(List<Object?> arg) async {
+    final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
+        'dev.flutter.pigeon.PrimitiveHostApi.aList', codec,
+        binaryMessenger: _binaryMessenger);
+    final Map<Object?, Object?>? replyMap =
+        await channel.send(arg) as Map<Object?, Object?>?;
+    if (replyMap == null) {
+      throw PlatformException(
+        code: 'channel-error',
+        message: 'Unable to establish connection on channel.',
+        details: null,
+      );
+    } else if (replyMap['error'] != null) {
+      final Map<Object?, Object?> error =
+          (replyMap['error'] as Map<Object?, Object?>?)!;
+      throw PlatformException(
+        code: (error['code'] as String?)!,
+        message: error['message'] as String?,
+        details: error['details'],
+      );
+    } else {
+      return (replyMap['result'] as List<Object?>?)!;
+    }
+  }
+
+  Future<Int32List> anInt32List(Int32List arg) async {
+    final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
+        'dev.flutter.pigeon.PrimitiveHostApi.anInt32List', codec,
+        binaryMessenger: _binaryMessenger);
+    final Map<Object?, Object?>? replyMap =
+        await channel.send(arg) as Map<Object?, Object?>?;
+    if (replyMap == null) {
+      throw PlatformException(
+        code: 'channel-error',
+        message: 'Unable to establish connection on channel.',
+        details: null,
+      );
+    } else if (replyMap['error'] != null) {
+      final Map<Object?, Object?> error =
+          (replyMap['error'] as Map<Object?, Object?>?)!;
+      throw PlatformException(
+        code: (error['code'] as String?)!,
+        message: error['message'] as String?,
+        details: error['details'],
+      );
+    } else {
+      return (replyMap['result'] as Int32List?)!;
+    }
+  }
+
+  Future<List<bool?>> aBoolList(List<bool?> arg) async {
+    final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
+        'dev.flutter.pigeon.PrimitiveHostApi.aBoolList', codec,
+        binaryMessenger: _binaryMessenger);
+    final Map<Object?, Object?>? replyMap =
+        await channel.send(arg) as Map<Object?, Object?>?;
+    if (replyMap == null) {
+      throw PlatformException(
+        code: 'channel-error',
+        message: 'Unable to establish connection on channel.',
+        details: null,
+      );
+    } else if (replyMap['error'] != null) {
+      final Map<Object?, Object?> error =
+          (replyMap['error'] as Map<Object?, Object?>?)!;
+      throw PlatformException(
+        code: (error['code'] as String?)!,
+        message: error['message'] as String?,
+        details: error['details'],
+      );
+    } else {
+      return (replyMap['result'] as List<Object?>?)!.cast<bool?>();
+    }
+  }
+
+  Future<Map<String?, int?>> aStringIntMap(Map<String?, int?> arg) async {
+    final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
+        'dev.flutter.pigeon.PrimitiveHostApi.aStringIntMap', codec,
+        binaryMessenger: _binaryMessenger);
+    final Map<Object?, Object?>? replyMap =
+        await channel.send(arg) as Map<Object?, Object?>?;
+    if (replyMap == null) {
+      throw PlatformException(
+        code: 'channel-error',
+        message: 'Unable to establish connection on channel.',
+        details: null,
+      );
+    } else if (replyMap['error'] != null) {
+      final Map<Object?, Object?> error =
+          (replyMap['error'] as Map<Object?, Object?>?)!;
+      throw PlatformException(
+        code: (error['code'] as String?)!,
+        message: error['message'] as String?,
+        details: error['details'],
+      );
+    } else {
+      return (replyMap['result'] as Map<Object?, Object?>?)!
+          .cast<String?, int?>();
+    }
+  }
+}
+
+class _PrimitiveFlutterApiCodec extends StandardMessageCodec {
+  const _PrimitiveFlutterApiCodec();
+}
+
+abstract class PrimitiveFlutterApi {
+  static const MessageCodec<Object?> codec = _PrimitiveFlutterApiCodec();
+
+  int anInt(int arg);
+  bool aBool(bool arg);
+  String aString(String arg);
+  double aDouble(double arg);
+  Map<Object?, Object?> aMap(Map<Object?, Object?> arg);
+  List<Object?> aList(List<Object?> arg);
+  Int32List anInt32List(Int32List arg);
+  List<bool?> aBoolList(List<bool?> arg);
+  Map<String?, int?> aStringIntMap(Map<String?, int?> arg);
+  static void setup(PrimitiveFlutterApi? api) {
+    {
+      const BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
+          'dev.flutter.pigeon.PrimitiveFlutterApi.anInt', codec);
+      if (api == null) {
+        channel.setMessageHandler(null);
+      } else {
+        channel.setMessageHandler((Object? message) async {
+          assert(message != null,
+              'Argument for dev.flutter.pigeon.PrimitiveFlutterApi.anInt was null. Expected int.');
+          final int input = (message as int?)!;
+          final int output = api.anInt(input);
+          return output;
+        });
+      }
+    }
+    {
+      const BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
+          'dev.flutter.pigeon.PrimitiveFlutterApi.aBool', codec);
+      if (api == null) {
+        channel.setMessageHandler(null);
+      } else {
+        channel.setMessageHandler((Object? message) async {
+          assert(message != null,
+              'Argument for dev.flutter.pigeon.PrimitiveFlutterApi.aBool was null. Expected bool.');
+          final bool input = (message as bool?)!;
+          final bool output = api.aBool(input);
+          return output;
+        });
+      }
+    }
+    {
+      const BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
+          'dev.flutter.pigeon.PrimitiveFlutterApi.aString', codec);
+      if (api == null) {
+        channel.setMessageHandler(null);
+      } else {
+        channel.setMessageHandler((Object? message) async {
+          assert(message != null,
+              'Argument for dev.flutter.pigeon.PrimitiveFlutterApi.aString was null. Expected String.');
+          final String input = (message as String?)!;
+          final String output = api.aString(input);
+          return output;
+        });
+      }
+    }
+    {
+      const BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
+          'dev.flutter.pigeon.PrimitiveFlutterApi.aDouble', codec);
+      if (api == null) {
+        channel.setMessageHandler(null);
+      } else {
+        channel.setMessageHandler((Object? message) async {
+          assert(message != null,
+              'Argument for dev.flutter.pigeon.PrimitiveFlutterApi.aDouble was null. Expected double.');
+          final double input = (message as double?)!;
+          final double output = api.aDouble(input);
+          return output;
+        });
+      }
+    }
+    {
+      const BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
+          'dev.flutter.pigeon.PrimitiveFlutterApi.aMap', codec);
+      if (api == null) {
+        channel.setMessageHandler(null);
+      } else {
+        channel.setMessageHandler((Object? message) async {
+          assert(message != null,
+              'Argument for dev.flutter.pigeon.PrimitiveFlutterApi.aMap was null. Expected Map<Object?, Object?>.');
+          final Map<Object?, Object?> input =
+              (message as Map<Object?, Object?>?)!;
+          final Map<Object?, Object?> output = api.aMap(input);
+          return output;
+        });
+      }
+    }
+    {
+      const BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
+          'dev.flutter.pigeon.PrimitiveFlutterApi.aList', codec);
+      if (api == null) {
+        channel.setMessageHandler(null);
+      } else {
+        channel.setMessageHandler((Object? message) async {
+          assert(message != null,
+              'Argument for dev.flutter.pigeon.PrimitiveFlutterApi.aList was null. Expected List<Object?>.');
+          final List<Object?> input = (message as List<Object?>?)!;
+          final List<Object?> output = api.aList(input);
+          return output;
+        });
+      }
+    }
+    {
+      const BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
+          'dev.flutter.pigeon.PrimitiveFlutterApi.anInt32List', codec);
+      if (api == null) {
+        channel.setMessageHandler(null);
+      } else {
+        channel.setMessageHandler((Object? message) async {
+          assert(message != null,
+              'Argument for dev.flutter.pigeon.PrimitiveFlutterApi.anInt32List was null. Expected Int32List.');
+          final Int32List input = (message as Int32List?)!;
+          final Int32List output = api.anInt32List(input);
+          return output;
+        });
+      }
+    }
+    {
+      const BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
+          'dev.flutter.pigeon.PrimitiveFlutterApi.aBoolList', codec);
+      if (api == null) {
+        channel.setMessageHandler(null);
+      } else {
+        channel.setMessageHandler((Object? message) async {
+          assert(message != null,
+              'Argument for dev.flutter.pigeon.PrimitiveFlutterApi.aBoolList was null. Expected List<bool?>.');
+          final List<bool?> input = (message as List<bool?>?)!;
+          final List<bool?> output = api.aBoolList(input);
+          return output;
+        });
+      }
+    }
+    {
+      const BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
+          'dev.flutter.pigeon.PrimitiveFlutterApi.aStringIntMap', codec);
+      if (api == null) {
+        channel.setMessageHandler(null);
+      } else {
+        channel.setMessageHandler((Object? message) async {
+          assert(message != null,
+              'Argument for dev.flutter.pigeon.PrimitiveFlutterApi.aStringIntMap was null. Expected Map<String?, int?>.');
+          final Map<String?, int?> input = (message as Map<String?, int?>?)!;
+          final Map<String?, int?> output = api.aStringIntMap(input);
+          return output;
+        });
+      }
+    }
+  }
+}
diff --git a/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/test/all_datatypes_test.dart b/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/test/all_datatypes_test.dart
index 7061fb8..4cad76d 100644
--- a/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/test/all_datatypes_test.dart
+++ b/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/test/all_datatypes_test.dart
@@ -35,6 +35,8 @@
     expect(result.aFloatArray, isNull);
     expect(result.aList, isNull);
     expect(result.aMap, isNull);
+    expect(result.nestedList, isNull);
+    expect(result.mapWithAnnotations, isNull);
   });
 
   test('with values', () async {
@@ -50,6 +52,10 @@
         Float64List.fromList(<double>[1.0, 2.5, 3.0, 4.25]);
     everything.aList = <int>[1, 2, 3, 4];
     everything.aMap = <String, int>{'hello': 1234};
+    everything.aList = <List<bool?>>[
+      <bool?>[true]
+    ];
+    everything.mapWithAnnotations = <String?, String?>{'hello': 'world'};
     final BinaryMessenger mockMessenger = MockBinaryMessenger();
     when(mockMessenger.send('dev.flutter.pigeon.HostEverything.echo', any))
         .thenAnswer((Invocation realInvocation) async {
@@ -70,5 +76,7 @@
     expect(result.aFloatArray, everything.aFloatArray);
     expect(result.aList, everything.aList);
     expect(result.aMap, everything.aMap);
+    expect(result.aList, everything.aList);
+    expect(result.mapWithAnnotations, everything.mapWithAnnotations);
   });
 }
diff --git a/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/test/primitive_test.dart b/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/test/primitive_test.dart
new file mode 100644
index 0000000..3c20172
--- /dev/null
+++ b/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/test/primitive_test.dart
@@ -0,0 +1,61 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'package:flutter/services.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:flutter_unit_tests/primitive.dart';
+import 'package:mockito/annotations.dart';
+import 'package:mockito/mockito.dart';
+import 'primitive_test.mocks.dart';
+
+@GenerateMocks(<Type>[BinaryMessenger])
+void main() {
+  test('test anInt', () async {
+    final BinaryMessenger mockMessenger = MockBinaryMessenger();
+    when(mockMessenger.send('dev.flutter.pigeon.PrimitiveHostApi.anInt', any))
+        .thenAnswer((Invocation realInvocation) async {
+      const MessageCodec<Object?> codec = PrimitiveHostApi.codec;
+      final Object? input =
+          codec.decodeMessage(realInvocation.positionalArguments[1]);
+      return codec.encodeMessage(<String, Object>{'result': input!});
+    });
+    final PrimitiveHostApi api =
+        PrimitiveHostApi(binaryMessenger: mockMessenger);
+    final int result = await api.anInt(1);
+    expect(result, 1);
+  });
+
+  test('test List<bool>', () async {
+    final BinaryMessenger mockMessenger = MockBinaryMessenger();
+    when(mockMessenger.send(
+            'dev.flutter.pigeon.PrimitiveHostApi.aBoolList', any))
+        .thenAnswer((Invocation realInvocation) async {
+      const MessageCodec<Object?> codec = PrimitiveHostApi.codec;
+      final Object? input =
+          codec.decodeMessage(realInvocation.positionalArguments[1]);
+      return codec.encodeMessage(<String, Object>{'result': input!});
+    });
+    final PrimitiveHostApi api =
+        PrimitiveHostApi(binaryMessenger: mockMessenger);
+    final List<bool?> result = await api.aBoolList(<bool?>[true]);
+    expect(result[0], true);
+  });
+
+  test('test Map<String?, int?>', () async {
+    final BinaryMessenger mockMessenger = MockBinaryMessenger();
+    when(mockMessenger.send(
+            'dev.flutter.pigeon.PrimitiveHostApi.aStringIntMap', any))
+        .thenAnswer((Invocation realInvocation) async {
+      const MessageCodec<Object?> codec = PrimitiveHostApi.codec;
+      final Object? input =
+          codec.decodeMessage(realInvocation.positionalArguments[1]);
+      return codec.encodeMessage(<String, Object>{'result': input!});
+    });
+    final PrimitiveHostApi api =
+        PrimitiveHostApi(binaryMessenger: mockMessenger);
+    final Map<String?, int?> result =
+        await api.aStringIntMap(<String?, int?>{'hello': 1});
+    expect(result['hello'], 1);
+  });
+}
diff --git a/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/test/primitive_test.mocks.dart b/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/test/primitive_test.mocks.dart
new file mode 100644
index 0000000..814ea41
--- /dev/null
+++ b/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/test/primitive_test.mocks.dart
@@ -0,0 +1,44 @@
+// Mocks generated by Mockito 5.0.7 from annotations
+// in flutter_unit_tests/test/null_safe_test.dart.
+// Do not manually edit this file.
+
+import 'dart:async' as _i3;
+import 'dart:typed_data' as _i4;
+import 'dart:ui' as _i5;
+
+import 'package:flutter/src/services/binary_messenger.dart' as _i2;
+import 'package:mockito/mockito.dart' as _i1;
+
+// ignore_for_file: comment_references
+// ignore_for_file: unnecessary_parenthesis
+// ignore_for_file: always_specify_types
+// Added manually; several methods have moved to
+// flutter_test/lib/src/deprecated.dart on master, but that hasn't reached
+// stable yet.
+// ignore_for_file: override_on_non_overriding_member
+
+/// A class which mocks [BinaryMessenger].
+///
+/// See the documentation for Mockito's code generation for more information.
+class MockBinaryMessenger extends _i1.Mock implements _i2.BinaryMessenger {
+  MockBinaryMessenger() {
+    _i1.throwOnMissingStub(this);
+  }
+
+  @override
+  _i3.Future<void> handlePlatformMessage(String? channel, _i4.ByteData? data,
+          _i5.PlatformMessageResponseCallback? callback) =>
+      (super.noSuchMethod(
+          Invocation.method(#handlePlatformMessage, [channel, data, callback]),
+          returnValue: Future<void>.value(null),
+          returnValueForMissingStub:
+              Future<dynamic>.value()) as _i3.Future<void>);
+  @override
+  _i3.Future<_i4.ByteData?>? send(String? channel, _i4.ByteData? message) =>
+      (super.noSuchMethod(Invocation.method(#send, [channel, message]))
+          as _i3.Future<_i4.ByteData?>?);
+  @override
+  void setMessageHandler(String? channel, _i2.MessageHandler? handler) => super
+      .noSuchMethod(Invocation.method(#setMessageHandler, [channel, handler]),
+          returnValueForMissingStub: null);
+}
diff --git a/packages/pigeon/run_tests.sh b/packages/pigeon/run_tests.sh
index 2a12484..93d3a1a 100755
--- a/packages/pigeon/run_tests.sh
+++ b/packages/pigeon/run_tests.sh
@@ -204,17 +204,22 @@
 }
 
 run_flutter_unittests() {
+  local flutter_tests="platform_tests/flutter_null_safe_unit_tests"
   pushd $PWD
   $run_pigeon \
     --input pigeons/flutter_unittests.dart \
-    --dart_out platform_tests/flutter_null_safe_unit_tests/lib/null_safe_pigeon.dart
+    --dart_out "$flutter_tests/lib/null_safe_pigeon.dart"
   $run_pigeon \
     --input pigeons/all_datatypes.dart \
-    --dart_out platform_tests/flutter_null_safe_unit_tests/lib/all_datatypes.dart
-  cd platform_tests/flutter_null_safe_unit_tests
+    --dart_out "$flutter_tests/lib/all_datatypes.dart"
+  $run_pigeon \
+    --input pigeons/primitive.dart \
+    --dart_out "$flutter_tests/lib/primitive.dart"
+  cd "$flutter_tests"
   flutter pub get
   flutter test test/null_safe_test.dart
   flutter test test/all_datatypes_test.dart
+  flutter test test/primitive_test.dart
   popd
 }
 
diff --git a/packages/pigeon/test/dart_generator_test.dart b/packages/pigeon/test/dart_generator_test.dart
index 04b3488..bcbb332 100644
--- a/packages/pigeon/test/dart_generator_test.dart
+++ b/packages/pigeon/test/dart_generator_test.dart
@@ -11,12 +11,12 @@
   test('gen one class', () {
     final Class klass = Class(
       name: 'Foobar',
-      fields: <Field>[
-        Field(
-          name: 'field1',
-          dataType: 'dataType1',
-          isNullable: true,
-        ),
+      fields: <NamedType>[
+        NamedType(
+            type: TypeDeclaration(
+                baseName: 'dataType1', isNullable: true, typeArguments: null),
+            name: 'field1',
+            offset: null),
       ],
     );
     final Root root = Root(
@@ -57,55 +57,60 @@
       Api(name: 'Api', location: ApiLocation.host, methods: <Method>[
         Method(
           name: 'doSomething',
-          argType: 'Input',
-          isArgNullable: false,
-          returnType: 'Output',
+          arguments: <NamedType>[
+            NamedType(
+                type: TypeDeclaration(
+                    baseName: 'Input', isNullable: false, typeArguments: null),
+                name: 'input',
+                offset: null)
+          ],
+          returnType: TypeDeclaration(baseName: 'Output', isNullable: false),
           isAsynchronous: false,
         )
       ])
     ], classes: <Class>[
-      Class(name: 'Input', fields: <Field>[
-        Field(
-          name: 'input',
-          dataType: 'String',
-          isNullable: true,
-        )
+      Class(name: 'Input', fields: <NamedType>[
+        NamedType(
+            type: TypeDeclaration(
+                baseName: 'String', isNullable: true, typeArguments: null),
+            name: 'input',
+            offset: null)
       ]),
-      Class(name: 'Output', fields: <Field>[
-        Field(
-          name: 'output',
-          dataType: 'String',
-          isNullable: true,
-        )
+      Class(name: 'Output', fields: <NamedType>[
+        NamedType(
+            type: TypeDeclaration(
+                baseName: 'String', isNullable: true, typeArguments: null),
+            name: 'output',
+            offset: null)
       ])
     ], enums: <Enum>[]);
     final StringBuffer sink = StringBuffer();
     generateDart(const DartOptions(isNullSafe: false), root, sink);
     final String code = sink.toString();
     expect(code, contains('class Api'));
-    expect(code, matches('Output.*doSomething.*Input'));
+    expect(code, contains('Future<Output> doSomething(Input input)'));
   });
 
   test('nested class', () {
     final Root root = Root(apis: <Api>[], classes: <Class>[
       Class(
         name: 'Input',
-        fields: <Field>[
-          Field(
-            name: 'input',
-            dataType: 'String',
-            isNullable: true,
-          )
+        fields: <NamedType>[
+          NamedType(
+              type: TypeDeclaration(
+                  baseName: 'String', isNullable: true, typeArguments: null),
+              name: 'input',
+              offset: null)
         ],
       ),
       Class(
         name: 'Nested',
-        fields: <Field>[
-          Field(
-            name: 'nested',
-            dataType: 'Input',
-            isNullable: true,
-          )
+        fields: <NamedType>[
+          NamedType(
+              type: TypeDeclaration(
+                  baseName: 'Input', isNullable: true, typeArguments: null),
+              name: 'nested',
+              offset: null)
         ],
       )
     ], enums: <Enum>[]);
@@ -131,26 +136,31 @@
       Api(name: 'Api', location: ApiLocation.flutter, methods: <Method>[
         Method(
           name: 'doSomething',
-          argType: 'Input',
-          isArgNullable: false,
-          returnType: 'Output',
+          arguments: <NamedType>[
+            NamedType(
+                type: TypeDeclaration(
+                    baseName: 'Input', isNullable: false, typeArguments: null),
+                name: 'input',
+                offset: null)
+          ],
+          returnType: TypeDeclaration(baseName: 'Output', isNullable: false),
           isAsynchronous: false,
         )
       ])
     ], classes: <Class>[
-      Class(name: 'Input', fields: <Field>[
-        Field(
-          name: 'input',
-          dataType: 'String',
-          isNullable: true,
-        )
+      Class(name: 'Input', fields: <NamedType>[
+        NamedType(
+            type: TypeDeclaration(
+                baseName: 'String', isNullable: true, typeArguments: null),
+            name: 'input',
+            offset: null)
       ]),
-      Class(name: 'Output', fields: <Field>[
-        Field(
-          name: 'output',
-          dataType: 'String',
-          isNullable: true,
-        )
+      Class(name: 'Output', fields: <NamedType>[
+        NamedType(
+            type: TypeDeclaration(
+                baseName: 'String', isNullable: true, typeArguments: null),
+            name: 'output',
+            offset: null)
       ])
     ], enums: <Enum>[]);
     final StringBuffer sink = StringBuffer();
@@ -158,6 +168,7 @@
     final String code = sink.toString();
     expect(code, contains('abstract class Api'));
     expect(code, contains('static void setup(Api'));
+    expect(code, contains('Output doSomething(Input input)'));
   });
 
   test('host void', () {
@@ -165,19 +176,24 @@
       Api(name: 'Api', location: ApiLocation.host, methods: <Method>[
         Method(
           name: 'doSomething',
-          argType: 'Input',
-          isArgNullable: false,
-          returnType: 'void',
+          arguments: <NamedType>[
+            NamedType(
+                type: TypeDeclaration(
+                    baseName: 'Input', isNullable: false, typeArguments: null),
+                name: '',
+                offset: null)
+          ],
+          returnType: TypeDeclaration(baseName: 'void', isNullable: false),
           isAsynchronous: false,
         )
       ])
     ], classes: <Class>[
-      Class(name: 'Input', fields: <Field>[
-        Field(
-          name: 'input',
-          dataType: 'String',
-          isNullable: true,
-        )
+      Class(name: 'Input', fields: <NamedType>[
+        NamedType(
+            type: TypeDeclaration(
+                baseName: 'String', isNullable: true, typeArguments: null),
+            name: 'input',
+            offset: null)
       ]),
     ], enums: <Enum>[]);
     final StringBuffer sink = StringBuffer();
@@ -192,19 +208,24 @@
       Api(name: 'Api', location: ApiLocation.flutter, methods: <Method>[
         Method(
           name: 'doSomething',
-          argType: 'Input',
-          isArgNullable: false,
-          returnType: 'void',
+          arguments: <NamedType>[
+            NamedType(
+                type: TypeDeclaration(
+                    baseName: 'Input', isNullable: false, typeArguments: null),
+                name: '',
+                offset: null)
+          ],
+          returnType: TypeDeclaration(baseName: 'void', isNullable: false),
           isAsynchronous: false,
         )
       ])
     ], classes: <Class>[
-      Class(name: 'Input', fields: <Field>[
-        Field(
-          name: 'input',
-          dataType: 'String',
-          isNullable: true,
-        )
+      Class(name: 'Input', fields: <NamedType>[
+        NamedType(
+            type: TypeDeclaration(
+                baseName: 'String', isNullable: true, typeArguments: null),
+            name: 'input',
+            offset: null)
       ]),
     ], enums: <Enum>[]);
     final StringBuffer sink = StringBuffer();
@@ -222,19 +243,18 @@
       Api(name: 'Api', location: ApiLocation.flutter, methods: <Method>[
         Method(
           name: 'doSomething',
-          argType: 'void',
-          isArgNullable: false,
-          returnType: 'Output',
+          arguments: <NamedType>[],
+          returnType: TypeDeclaration(baseName: 'Output', isNullable: false),
           isAsynchronous: false,
         )
       ])
     ], classes: <Class>[
-      Class(name: 'Output', fields: <Field>[
-        Field(
-          name: 'output',
-          dataType: 'String',
-          isNullable: true,
-        )
+      Class(name: 'Output', fields: <NamedType>[
+        NamedType(
+            type: TypeDeclaration(
+                baseName: 'String', isNullable: true, typeArguments: null),
+            name: 'output',
+            offset: null)
       ]),
     ], enums: <Enum>[]);
     final StringBuffer sink = StringBuffer();
@@ -249,19 +269,26 @@
       Api(name: 'Api', location: ApiLocation.flutter, methods: <Method>[
         Method(
           name: 'doSomething',
-          argType: 'EnumClass',
-          isArgNullable: false,
-          returnType: 'EnumClass',
+          arguments: <NamedType>[
+            NamedType(
+                type: TypeDeclaration(
+                    baseName: 'EnumClass',
+                    isNullable: false,
+                    typeArguments: null),
+                name: '',
+                offset: null)
+          ],
+          returnType: TypeDeclaration(baseName: 'EnumClass', isNullable: false),
           isAsynchronous: false,
         )
       ])
     ], classes: <Class>[
-      Class(name: 'EnumClass', fields: <Field>[
-        Field(
-          name: 'enum1',
-          dataType: 'Enum',
-          isNullable: true,
-        )
+      Class(name: 'EnumClass', fields: <NamedType>[
+        NamedType(
+            type: TypeDeclaration(
+                baseName: 'Enum', isNullable: true, typeArguments: null),
+            name: 'enum1',
+            offset: null)
       ]),
     ], enums: <Enum>[
       Enum(
@@ -286,19 +313,26 @@
       Api(name: 'Api', location: ApiLocation.flutter, methods: <Method>[
         Method(
           name: 'doSomething',
-          argType: 'EnumClass',
-          isArgNullable: false,
-          returnType: 'EnumClass',
+          arguments: <NamedType>[
+            NamedType(
+                type: TypeDeclaration(
+                    baseName: 'EnumClass',
+                    isNullable: false,
+                    typeArguments: null),
+                name: '',
+                offset: null)
+          ],
+          returnType: TypeDeclaration(baseName: 'EnumClass', isNullable: false),
           isAsynchronous: false,
         )
       ])
     ], classes: <Class>[
-      Class(name: 'EnumClass', fields: <Field>[
-        Field(
-          name: 'enum1',
-          dataType: 'Enum',
-          isNullable: true,
-        )
+      Class(name: 'EnumClass', fields: <NamedType>[
+        NamedType(
+            type: TypeDeclaration(
+                baseName: 'Enum', isNullable: true, typeArguments: null),
+            name: 'enum1',
+            offset: null)
       ]),
     ], enums: <Enum>[
       Enum(
@@ -325,19 +359,18 @@
       Api(name: 'Api', location: ApiLocation.host, methods: <Method>[
         Method(
           name: 'doSomething',
-          argType: 'void',
-          isArgNullable: false,
-          returnType: 'Output',
+          arguments: <NamedType>[],
+          returnType: TypeDeclaration(baseName: 'Output', isNullable: false),
           isAsynchronous: false,
         )
       ])
     ], classes: <Class>[
-      Class(name: 'Output', fields: <Field>[
-        Field(
-          name: 'output',
-          dataType: 'String',
-          isNullable: true,
-        )
+      Class(name: 'Output', fields: <NamedType>[
+        NamedType(
+            type: TypeDeclaration(
+                baseName: 'String', isNullable: true, typeArguments: null),
+            name: 'output',
+            offset: null)
       ]),
     ], enums: <Enum>[]);
     final StringBuffer sink = StringBuffer();
@@ -355,33 +388,48 @@
           methods: <Method>[
             Method(
               name: 'doSomething',
-              argType: 'Input',
-              isArgNullable: false,
-              returnType: 'Output',
+              arguments: <NamedType>[
+                NamedType(
+                    type: TypeDeclaration(
+                        baseName: 'Input',
+                        isNullable: false,
+                        typeArguments: null),
+                    name: '',
+                    offset: null)
+              ],
+              returnType:
+                  TypeDeclaration(baseName: 'Output', isNullable: false),
               isAsynchronous: false,
             ),
             Method(
               name: 'voidReturner',
-              argType: 'Input',
-              isArgNullable: false,
-              returnType: 'void',
+              arguments: <NamedType>[
+                NamedType(
+                    type: TypeDeclaration(
+                        baseName: 'Input',
+                        isNullable: false,
+                        typeArguments: null),
+                    name: '',
+                    offset: null)
+              ],
+              returnType: TypeDeclaration(baseName: 'void', isNullable: false),
               isAsynchronous: false,
             )
           ])
     ], classes: <Class>[
-      Class(name: 'Input', fields: <Field>[
-        Field(
-          name: 'input',
-          dataType: 'String',
-          isNullable: true,
-        )
+      Class(name: 'Input', fields: <NamedType>[
+        NamedType(
+            type: TypeDeclaration(
+                baseName: 'String', isNullable: true, typeArguments: null),
+            name: 'input',
+            offset: null)
       ]),
-      Class(name: 'Output', fields: <Field>[
-        Field(
-          name: 'output',
-          dataType: 'String',
-          isNullable: true,
-        )
+      Class(name: 'Output', fields: <NamedType>[
+        NamedType(
+            type: TypeDeclaration(
+                baseName: 'String', isNullable: true, typeArguments: null),
+            name: 'output',
+            offset: null)
       ])
     ], enums: <Enum>[]);
     final StringBuffer mainCodeSink = StringBuffer();
@@ -408,12 +456,12 @@
   test('opt out of nndb', () {
     final Class klass = Class(
       name: 'Foobar',
-      fields: <Field>[
-        Field(
-          name: 'field1',
-          dataType: 'dataType1',
-          isNullable: true,
-        ),
+      fields: <NamedType>[
+        NamedType(
+            type: TypeDeclaration(
+                baseName: 'dataType1', isNullable: true, typeArguments: null),
+            name: 'field1',
+            offset: null),
       ],
     );
     final Root root = Root(
@@ -432,26 +480,31 @@
       Api(name: 'Api', location: ApiLocation.flutter, methods: <Method>[
         Method(
           name: 'doSomething',
-          argType: 'Input',
-          isArgNullable: false,
-          returnType: 'Output',
+          arguments: <NamedType>[
+            NamedType(
+                type: TypeDeclaration(
+                    baseName: 'Input', isNullable: false, typeArguments: null),
+                name: '',
+                offset: null)
+          ],
+          returnType: TypeDeclaration(baseName: 'Output', isNullable: false),
           isAsynchronous: true,
         )
       ])
     ], classes: <Class>[
-      Class(name: 'Input', fields: <Field>[
-        Field(
-          name: 'input',
-          dataType: 'String',
-          isNullable: true,
-        )
+      Class(name: 'Input', fields: <NamedType>[
+        NamedType(
+            type: TypeDeclaration(
+                baseName: 'String', isNullable: true, typeArguments: null),
+            name: 'input',
+            offset: null)
       ]),
-      Class(name: 'Output', fields: <Field>[
-        Field(
-          name: 'output',
-          dataType: 'String',
-          isNullable: true,
-        )
+      Class(name: 'Output', fields: <NamedType>[
+        NamedType(
+            type: TypeDeclaration(
+                baseName: 'String', isNullable: true, typeArguments: null),
+            name: 'output',
+            offset: null)
       ])
     ], enums: <Enum>[]);
     final StringBuffer sink = StringBuffer();
@@ -468,26 +521,31 @@
       Api(name: 'Api', location: ApiLocation.flutter, methods: <Method>[
         Method(
           name: 'doSomething',
-          argType: 'Input',
-          isArgNullable: false,
-          returnType: 'void',
+          arguments: <NamedType>[
+            NamedType(
+                type: TypeDeclaration(
+                    baseName: 'Input', isNullable: false, typeArguments: null),
+                name: '',
+                offset: null)
+          ],
+          returnType: TypeDeclaration(baseName: 'void', isNullable: false),
           isAsynchronous: true,
         )
       ])
     ], classes: <Class>[
-      Class(name: 'Input', fields: <Field>[
-        Field(
-          name: 'input',
-          dataType: 'String',
-          isNullable: true,
-        )
+      Class(name: 'Input', fields: <NamedType>[
+        NamedType(
+            type: TypeDeclaration(
+                baseName: 'String', isNullable: true, typeArguments: null),
+            name: 'input',
+            offset: null)
       ]),
-      Class(name: 'Output', fields: <Field>[
-        Field(
-          name: 'output',
-          dataType: 'String',
-          isNullable: true,
-        )
+      Class(name: 'Output', fields: <NamedType>[
+        NamedType(
+            type: TypeDeclaration(
+                baseName: 'String', isNullable: true, typeArguments: null),
+            name: 'output',
+            offset: null)
       ])
     ], enums: <Enum>[]);
     final StringBuffer sink = StringBuffer();
@@ -503,26 +561,31 @@
       Api(name: 'Api', location: ApiLocation.host, methods: <Method>[
         Method(
           name: 'doSomething',
-          argType: 'Input',
-          isArgNullable: false,
-          returnType: 'Output',
+          arguments: <NamedType>[
+            NamedType(
+                type: TypeDeclaration(
+                    baseName: 'Input', isNullable: false, typeArguments: null),
+                name: '',
+                offset: null)
+          ],
+          returnType: TypeDeclaration(baseName: 'Output', isNullable: false),
           isAsynchronous: true,
         )
       ])
     ], classes: <Class>[
-      Class(name: 'Input', fields: <Field>[
-        Field(
-          name: 'input',
-          dataType: 'String',
-          isNullable: true,
-        )
+      Class(name: 'Input', fields: <NamedType>[
+        NamedType(
+            type: TypeDeclaration(
+                baseName: 'String', isNullable: true, typeArguments: null),
+            name: 'input',
+            offset: null)
       ]),
-      Class(name: 'Output', fields: <Field>[
-        Field(
-          name: 'output',
-          dataType: 'String',
-          isNullable: true,
-        )
+      Class(name: 'Output', fields: <NamedType>[
+        NamedType(
+            type: TypeDeclaration(
+                baseName: 'String', isNullable: true, typeArguments: null),
+            name: 'output',
+            offset: null)
       ])
     ], enums: <Enum>[]);
     final StringBuffer sink = StringBuffer();
@@ -537,19 +600,18 @@
       Api(name: 'Api', location: ApiLocation.host, methods: <Method>[
         Method(
           name: 'doSomething',
-          argType: 'void',
-          isArgNullable: false,
-          returnType: 'Output',
+          arguments: <NamedType>[],
+          returnType: TypeDeclaration(baseName: 'Output', isNullable: false),
           isAsynchronous: true,
         )
       ])
     ], classes: <Class>[
-      Class(name: 'Output', fields: <Field>[
-        Field(
-          name: 'output',
-          dataType: 'String',
-          isNullable: true,
-        )
+      Class(name: 'Output', fields: <NamedType>[
+        NamedType(
+            type: TypeDeclaration(
+                baseName: 'String', isNullable: true, typeArguments: null),
+            name: 'output',
+            offset: null)
       ]),
     ], enums: <Enum>[]);
     final StringBuffer sink = StringBuffer();
@@ -574,4 +636,182 @@
     final String code = sink.toString();
     expect(code, startsWith('// hello world'));
   });
+
+  test('generics', () {
+    final Class klass = Class(
+      name: 'Foobar',
+      fields: <NamedType>[
+        NamedType(
+            type: TypeDeclaration(
+                baseName: 'List',
+                isNullable: true,
+                typeArguments: <TypeDeclaration>[
+                  TypeDeclaration(baseName: 'int', isNullable: true)
+                ]),
+            name: 'field1',
+            offset: null),
+      ],
+    );
+    final Root root = Root(
+      apis: <Api>[],
+      classes: <Class>[klass],
+      enums: <Enum>[],
+    );
+    final StringBuffer sink = StringBuffer();
+    generateDart(const DartOptions(isNullSafe: true), root, sink);
+    final String code = sink.toString();
+    expect(code, contains('class Foobar'));
+    expect(code, contains('  List<int?>? field1;'));
+  });
+
+  test('map generics', () {
+    final Class klass = Class(
+      name: 'Foobar',
+      fields: <NamedType>[
+        NamedType(
+            type: TypeDeclaration(
+                baseName: 'Map',
+                isNullable: true,
+                typeArguments: <TypeDeclaration>[
+                  TypeDeclaration(baseName: 'String', isNullable: true),
+                  TypeDeclaration(baseName: 'int', isNullable: true),
+                ]),
+            name: 'field1',
+            offset: null),
+      ],
+    );
+    final Root root = Root(
+      apis: <Api>[],
+      classes: <Class>[klass],
+      enums: <Enum>[],
+    );
+    final StringBuffer sink = StringBuffer();
+    generateDart(const DartOptions(isNullSafe: true), root, sink);
+    final String code = sink.toString();
+    expect(code, contains('class Foobar'));
+    expect(code, contains('  Map<String?, int?>? field1;'));
+  });
+
+  test('host generics argument', () {
+    final Root root = Root(
+      apis: <Api>[
+        Api(name: 'Api', location: ApiLocation.host, methods: <Method>[
+          Method(
+              name: 'doit',
+              returnType: TypeDeclaration(baseName: 'void', isNullable: false),
+              arguments: <NamedType>[
+                NamedType(
+                    type: TypeDeclaration(
+                        baseName: 'List',
+                        isNullable: false,
+                        typeArguments: <TypeDeclaration>[
+                          TypeDeclaration(baseName: 'int', isNullable: true)
+                        ]),
+                    name: 'arg',
+                    offset: null)
+              ])
+        ])
+      ],
+      classes: <Class>[],
+      enums: <Enum>[],
+    );
+    final StringBuffer sink = StringBuffer();
+    generateDart(const DartOptions(isNullSafe: true), root, sink);
+    final String code = sink.toString();
+    expect(code, contains('doit(List<int?> arg'));
+  });
+
+  test('flutter generics argument', () {
+    final Root root = Root(
+      apis: <Api>[
+        Api(name: 'Api', location: ApiLocation.flutter, methods: <Method>[
+          Method(
+              name: 'doit',
+              returnType: TypeDeclaration(baseName: 'void', isNullable: false),
+              arguments: <NamedType>[
+                NamedType(
+                    type: TypeDeclaration(
+                        baseName: 'List',
+                        isNullable: false,
+                        typeArguments: <TypeDeclaration>[
+                          TypeDeclaration(baseName: 'int', isNullable: true)
+                        ]),
+                    name: 'arg',
+                    offset: null)
+              ])
+        ])
+      ],
+      classes: <Class>[],
+      enums: <Enum>[],
+    );
+    final StringBuffer sink = StringBuffer();
+    generateDart(const DartOptions(isNullSafe: true), root, sink);
+    final String code = sink.toString();
+    expect(code, contains('doit(List<int?> arg'));
+  });
+
+  test('host generics return', () {
+    final Root root = Root(
+      apis: <Api>[
+        Api(name: 'Api', location: ApiLocation.host, methods: <Method>[
+          Method(
+              name: 'doit',
+              returnType: TypeDeclaration(
+                  baseName: 'List',
+                  isNullable: false,
+                  typeArguments: <TypeDeclaration>[
+                    TypeDeclaration(baseName: 'int', isNullable: true)
+                  ]),
+              arguments: <NamedType>[])
+        ])
+      ],
+      classes: <Class>[],
+      enums: <Enum>[],
+    );
+    final StringBuffer sink = StringBuffer();
+    generateDart(const DartOptions(isNullSafe: true), root, sink);
+    final String code = sink.toString();
+    expect(code, contains('Future<List<int?>> doit('));
+    expect(
+        code,
+        contains(
+            'return (replyMap[\'result\'] as List<Object?>?)!.cast<int?>();'));
+  });
+
+  test('host generics return', () {
+    final Root root = Root(
+      apis: <Api>[
+        Api(name: 'Api', location: ApiLocation.flutter, methods: <Method>[
+          Method(
+              name: 'doit',
+              returnType: TypeDeclaration(
+                  baseName: 'List',
+                  isNullable: false,
+                  typeArguments: <TypeDeclaration>[
+                    TypeDeclaration(baseName: 'int', isNullable: true)
+                  ]),
+              arguments: <NamedType>[
+                NamedType(
+                    type: TypeDeclaration(
+                        baseName: 'List',
+                        isNullable: false,
+                        typeArguments: <TypeDeclaration>[
+                          TypeDeclaration(baseName: 'int', isNullable: true)
+                        ]),
+                    name: 'arg',
+                    offset: null)
+              ])
+        ])
+      ],
+      classes: <Class>[],
+      enums: <Enum>[],
+    );
+    final StringBuffer sink = StringBuffer();
+    generateDart(const DartOptions(isNullSafe: true), root, sink);
+    final String code = sink.toString();
+    expect(code, contains('List<int?> doit('));
+    expect(
+        code, contains('final List<int?> input = (message as List<int?>?)!'));
+    expect(code, contains('final List<int?> output = api.doit(input)'));
+  });
 }
diff --git a/packages/pigeon/test/java_generator_test.dart b/packages/pigeon/test/java_generator_test.dart
index 8533fe3..f0710d3 100644
--- a/packages/pigeon/test/java_generator_test.dart
+++ b/packages/pigeon/test/java_generator_test.dart
@@ -10,12 +10,12 @@
   test('gen one class', () {
     final Class klass = Class(
       name: 'Foobar',
-      fields: <Field>[
-        Field(
-          name: 'field1',
-          dataType: 'int',
-          isNullable: true,
-        ),
+      fields: <NamedType>[
+        NamedType(
+            type: TypeDeclaration(
+                baseName: 'int', isNullable: true, typeArguments: null),
+            name: 'field1',
+            offset: null),
       ],
     );
     final Root root = Root(
@@ -60,12 +60,12 @@
   test('package', () {
     final Class klass = Class(
       name: 'Foobar',
-      fields: <Field>[
-        Field(
-          name: 'field1',
-          dataType: 'int',
-          isNullable: true,
-        )
+      fields: <NamedType>[
+        NamedType(
+            type: TypeDeclaration(
+                baseName: 'int', isNullable: true, typeArguments: null),
+            name: 'field1',
+            offset: null)
       ],
     );
     final Root root = Root(
@@ -87,18 +87,31 @@
       Api(name: 'Api', location: ApiLocation.host, methods: <Method>[
         Method(
           name: 'doSomething',
-          argType: 'Input',
-          isArgNullable: true,
-          returnType: 'Output',
+          arguments: <NamedType>[
+            NamedType(
+                type: TypeDeclaration(
+                    baseName: 'Input', isNullable: false, typeArguments: null),
+                name: '',
+                offset: null)
+          ],
+          returnType: TypeDeclaration(baseName: 'Output', isNullable: false),
           isAsynchronous: false,
         )
       ])
     ], classes: <Class>[
-      Class(name: 'Input', fields: <Field>[
-        Field(name: 'input', dataType: 'String', isNullable: true)
+      Class(name: 'Input', fields: <NamedType>[
+        NamedType(
+            type: TypeDeclaration(
+                baseName: 'String', isNullable: true, typeArguments: null),
+            name: 'input',
+            offset: null)
       ]),
-      Class(name: 'Output', fields: <Field>[
-        Field(name: 'output', dataType: 'String', isNullable: true)
+      Class(name: 'Output', fields: <NamedType>[
+        NamedType(
+            type: TypeDeclaration(
+                baseName: 'String', isNullable: true, typeArguments: null),
+            name: 'output',
+            offset: null)
       ])
     ], enums: <Enum>[]);
     final StringBuffer sink = StringBuffer();
@@ -112,15 +125,47 @@
 
   test('all the simple datatypes header', () {
     final Root root = Root(apis: <Api>[], classes: <Class>[
-      Class(name: 'Foobar', fields: <Field>[
-        Field(name: 'aBool', dataType: 'bool', isNullable: true),
-        Field(name: 'aInt', dataType: 'int', isNullable: true),
-        Field(name: 'aDouble', dataType: 'double', isNullable: true),
-        Field(name: 'aString', dataType: 'String', isNullable: true),
-        Field(name: 'aUint8List', dataType: 'Uint8List', isNullable: true),
-        Field(name: 'aInt32List', dataType: 'Int32List', isNullable: true),
-        Field(name: 'aInt64List', dataType: 'Int64List', isNullable: true),
-        Field(name: 'aFloat64List', dataType: 'Float64List', isNullable: true),
+      Class(name: 'Foobar', fields: <NamedType>[
+        NamedType(
+            type: TypeDeclaration(
+                baseName: 'bool', isNullable: true, typeArguments: null),
+            name: 'aBool',
+            offset: null),
+        NamedType(
+            type: TypeDeclaration(
+                baseName: 'int', isNullable: true, typeArguments: null),
+            name: 'aInt',
+            offset: null),
+        NamedType(
+            type: TypeDeclaration(
+                baseName: 'double', isNullable: true, typeArguments: null),
+            name: 'aDouble',
+            offset: null),
+        NamedType(
+            type: TypeDeclaration(
+                baseName: 'String', isNullable: true, typeArguments: null),
+            name: 'aString',
+            offset: null),
+        NamedType(
+            type: TypeDeclaration(
+                baseName: 'Uint8List', isNullable: true, typeArguments: null),
+            name: 'aUint8List',
+            offset: null),
+        NamedType(
+            type: TypeDeclaration(
+                baseName: 'Int32List', isNullable: true, typeArguments: null),
+            name: 'aInt32List',
+            offset: null),
+        NamedType(
+            type: TypeDeclaration(
+                baseName: 'Int64List', isNullable: true, typeArguments: null),
+            name: 'aInt64List',
+            offset: null),
+        NamedType(
+            type: TypeDeclaration(
+                baseName: 'Float64List', isNullable: true, typeArguments: null),
+            name: 'aFloat64List',
+            offset: null),
       ]),
     ], enums: <Enum>[]);
 
@@ -143,18 +188,31 @@
       Api(name: 'Api', location: ApiLocation.flutter, methods: <Method>[
         Method(
           name: 'doSomething',
-          argType: 'Input',
-          isArgNullable: false,
-          returnType: 'Output',
+          arguments: <NamedType>[
+            NamedType(
+                type: TypeDeclaration(
+                    baseName: 'Input', isNullable: false, typeArguments: null),
+                name: '',
+                offset: null)
+          ],
+          returnType: TypeDeclaration(baseName: 'Output', isNullable: false),
           isAsynchronous: false,
         )
       ])
     ], classes: <Class>[
-      Class(name: 'Input', fields: <Field>[
-        Field(name: 'input', dataType: 'String', isNullable: true)
+      Class(name: 'Input', fields: <NamedType>[
+        NamedType(
+            type: TypeDeclaration(
+                baseName: 'String', isNullable: true, typeArguments: null),
+            name: 'input',
+            offset: null)
       ]),
-      Class(name: 'Output', fields: <Field>[
-        Field(name: 'output', dataType: 'String', isNullable: true)
+      Class(name: 'Output', fields: <NamedType>[
+        NamedType(
+            type: TypeDeclaration(
+                baseName: 'String', isNullable: true, typeArguments: null),
+            name: 'output',
+            offset: null)
       ])
     ], enums: <Enum>[]);
     final StringBuffer sink = StringBuffer();
@@ -170,15 +228,24 @@
       Api(name: 'Api', location: ApiLocation.host, methods: <Method>[
         Method(
           name: 'doSomething',
-          argType: 'Input',
-          isArgNullable: false,
-          returnType: 'void',
+          arguments: <NamedType>[
+            NamedType(
+                type: TypeDeclaration(
+                    baseName: 'Input', isNullable: false, typeArguments: null),
+                name: '',
+                offset: null)
+          ],
+          returnType: TypeDeclaration(baseName: 'void', isNullable: false),
           isAsynchronous: false,
         )
       ])
     ], classes: <Class>[
-      Class(name: 'Input', fields: <Field>[
-        Field(name: 'input', dataType: 'String', isNullable: true)
+      Class(name: 'Input', fields: <NamedType>[
+        NamedType(
+            type: TypeDeclaration(
+                baseName: 'String', isNullable: true, typeArguments: null),
+            name: 'input',
+            offset: null)
       ]),
     ], enums: <Enum>[]);
     final StringBuffer sink = StringBuffer();
@@ -194,15 +261,24 @@
       Api(name: 'Api', location: ApiLocation.flutter, methods: <Method>[
         Method(
           name: 'doSomething',
-          argType: 'Input',
-          isArgNullable: false,
-          returnType: 'void',
+          arguments: <NamedType>[
+            NamedType(
+                type: TypeDeclaration(
+                    baseName: 'Input', isNullable: false, typeArguments: null),
+                name: '',
+                offset: null)
+          ],
+          returnType: TypeDeclaration(baseName: 'void', isNullable: false),
           isAsynchronous: false,
         )
       ])
     ], classes: <Class>[
-      Class(name: 'Input', fields: <Field>[
-        Field(name: 'input', dataType: 'String', isNullable: true)
+      Class(name: 'Input', fields: <NamedType>[
+        NamedType(
+            type: TypeDeclaration(
+                baseName: 'String', isNullable: true, typeArguments: null),
+            name: 'input',
+            offset: null)
       ]),
     ], enums: <Enum>[]);
     final StringBuffer sink = StringBuffer();
@@ -218,15 +294,18 @@
       Api(name: 'Api', location: ApiLocation.host, methods: <Method>[
         Method(
           name: 'doSomething',
-          argType: 'void',
-          isArgNullable: false,
-          returnType: 'Output',
+          arguments: <NamedType>[],
+          returnType: TypeDeclaration(baseName: 'Output', isNullable: false),
           isAsynchronous: false,
         )
       ])
     ], classes: <Class>[
-      Class(name: 'Output', fields: <Field>[
-        Field(name: 'output', dataType: 'String', isNullable: true)
+      Class(name: 'Output', fields: <NamedType>[
+        NamedType(
+            type: TypeDeclaration(
+                baseName: 'String', isNullable: true, typeArguments: null),
+            name: 'output',
+            offset: null)
       ]),
     ], enums: <Enum>[]);
     final StringBuffer sink = StringBuffer();
@@ -242,15 +321,18 @@
       Api(name: 'Api', location: ApiLocation.flutter, methods: <Method>[
         Method(
           name: 'doSomething',
-          argType: 'void',
-          isArgNullable: false,
-          returnType: 'Output',
+          arguments: <NamedType>[],
+          returnType: TypeDeclaration(baseName: 'Output', isNullable: false),
           isAsynchronous: false,
         )
       ])
     ], classes: <Class>[
-      Class(name: 'Output', fields: <Field>[
-        Field(name: 'output', dataType: 'String', isNullable: true)
+      Class(name: 'Output', fields: <NamedType>[
+        NamedType(
+            type: TypeDeclaration(
+                baseName: 'String', isNullable: true, typeArguments: null),
+            name: 'output',
+            offset: null)
       ]),
     ], enums: <Enum>[]);
     final StringBuffer sink = StringBuffer();
@@ -263,8 +345,12 @@
 
   test('gen list', () {
     final Root root = Root(apis: <Api>[], classes: <Class>[
-      Class(name: 'Foobar', fields: <Field>[
-        Field(name: 'field1', dataType: 'List', isNullable: true)
+      Class(name: 'Foobar', fields: <NamedType>[
+        NamedType(
+            type: TypeDeclaration(
+                baseName: 'List', isNullable: true, typeArguments: null),
+            name: 'field1',
+            offset: null)
       ]),
     ], enums: <Enum>[]);
     final StringBuffer sink = StringBuffer();
@@ -277,8 +363,12 @@
 
   test('gen map', () {
     final Root root = Root(apis: <Api>[], classes: <Class>[
-      Class(name: 'Foobar', fields: <Field>[
-        Field(name: 'field1', dataType: 'Map', isNullable: true)
+      Class(name: 'Foobar', fields: <NamedType>[
+        NamedType(
+            type: TypeDeclaration(
+                baseName: 'Map', isNullable: true, typeArguments: null),
+            name: 'field1',
+            offset: null)
       ]),
     ], enums: <Enum>[]);
     final StringBuffer sink = StringBuffer();
@@ -292,22 +382,22 @@
   test('gen nested', () {
     final Class klass = Class(
       name: 'Outer',
-      fields: <Field>[
-        Field(
-          name: 'nested',
-          dataType: 'Nested',
-          isNullable: true,
-        )
+      fields: <NamedType>[
+        NamedType(
+            type: TypeDeclaration(
+                baseName: 'Nested', isNullable: true, typeArguments: null),
+            name: 'nested',
+            offset: null)
       ],
     );
     final Class nestedClass = Class(
       name: 'Nested',
-      fields: <Field>[
-        Field(
-          name: 'data',
-          dataType: 'int',
-          isNullable: true,
-        )
+      fields: <NamedType>[
+        NamedType(
+            type: TypeDeclaration(
+                baseName: 'int', isNullable: true, typeArguments: null),
+            name: 'data',
+            offset: null)
       ],
     );
     final Root root = Root(
@@ -333,18 +423,31 @@
       Api(name: 'Api', location: ApiLocation.host, methods: <Method>[
         Method(
           name: 'doSomething',
-          argType: 'Input',
-          isArgNullable: false,
-          returnType: 'Output',
+          arguments: <NamedType>[
+            NamedType(
+                type: TypeDeclaration(
+                    baseName: 'Input', isNullable: false, typeArguments: null),
+                name: 'arg',
+                offset: null)
+          ],
+          returnType: TypeDeclaration(baseName: 'Output', isNullable: false),
           isAsynchronous: true,
         )
       ])
     ], classes: <Class>[
-      Class(name: 'Input', fields: <Field>[
-        Field(name: 'input', dataType: 'String', isNullable: true)
+      Class(name: 'Input', fields: <NamedType>[
+        NamedType(
+            type: TypeDeclaration(
+                baseName: 'String', isNullable: true, typeArguments: null),
+            name: 'input',
+            offset: null)
       ]),
-      Class(name: 'Output', fields: <Field>[
-        Field(name: 'output', dataType: 'String', isNullable: true)
+      Class(name: 'Output', fields: <NamedType>[
+        NamedType(
+            type: TypeDeclaration(
+                baseName: 'String', isNullable: true, typeArguments: null),
+            name: 'output',
+            offset: null)
       ])
     ], enums: <Enum>[]);
     final StringBuffer sink = StringBuffer();
@@ -367,18 +470,31 @@
       Api(name: 'Api', location: ApiLocation.flutter, methods: <Method>[
         Method(
           name: 'doSomething',
-          argType: 'Input',
-          isArgNullable: false,
-          returnType: 'Output',
+          arguments: <NamedType>[
+            NamedType(
+                type: TypeDeclaration(
+                    baseName: 'Input', isNullable: false, typeArguments: null),
+                name: '',
+                offset: null)
+          ],
+          returnType: TypeDeclaration(baseName: 'Output', isNullable: false),
           isAsynchronous: true,
         )
       ])
     ], classes: <Class>[
-      Class(name: 'Input', fields: <Field>[
-        Field(name: 'input', dataType: 'String', isNullable: true)
+      Class(name: 'Input', fields: <NamedType>[
+        NamedType(
+            type: TypeDeclaration(
+                baseName: 'String', isNullable: true, typeArguments: null),
+            name: 'input',
+            offset: null)
       ]),
-      Class(name: 'Output', fields: <Field>[
-        Field(name: 'output', dataType: 'String', isNullable: true)
+      Class(name: 'Output', fields: <NamedType>[
+        NamedType(
+            type: TypeDeclaration(
+                baseName: 'String', isNullable: true, typeArguments: null),
+            name: 'output',
+            offset: null)
       ])
     ], enums: <Enum>[]);
     final StringBuffer sink = StringBuffer();
@@ -399,12 +515,12 @@
     );
     final Class klass = Class(
       name: 'EnumClass',
-      fields: <Field>[
-        Field(
-          name: 'enum1',
-          dataType: 'Enum1',
-          isNullable: true,
-        ),
+      fields: <NamedType>[
+        NamedType(
+            type: TypeDeclaration(
+                baseName: 'Enum1', isNullable: true, typeArguments: null),
+            name: 'enum1',
+            offset: null),
       ],
     );
     final Root root = Root(
@@ -442,4 +558,144 @@
     final String code = sink.toString();
     expect(code, startsWith('// hello world'));
   });
+
+  test('generics', () {
+    final Class klass = Class(
+      name: 'Foobar',
+      fields: <NamedType>[
+        NamedType(
+            type: TypeDeclaration(
+                baseName: 'List',
+                isNullable: true,
+                typeArguments: <TypeDeclaration>[
+                  TypeDeclaration(baseName: 'int', isNullable: true)
+                ]),
+            name: 'field1',
+            offset: null),
+      ],
+    );
+    final Root root = Root(
+      apis: <Api>[],
+      classes: <Class>[klass],
+      enums: <Enum>[],
+    );
+    final StringBuffer sink = StringBuffer();
+    const JavaOptions javaOptions = JavaOptions(className: 'Messages');
+    generateJava(javaOptions, root, sink);
+    final String code = sink.toString();
+    expect(code, contains('class Foobar'));
+    expect(code, contains('List<Long> field1;'));
+  });
+
+  test('host generics argument', () {
+    final Root root = Root(
+      apis: <Api>[
+        Api(name: 'Api', location: ApiLocation.host, methods: <Method>[
+          Method(
+              name: 'doit',
+              returnType: TypeDeclaration(baseName: 'void', isNullable: false),
+              arguments: <NamedType>[
+                NamedType(
+                    type: TypeDeclaration(
+                        baseName: 'List',
+                        isNullable: false,
+                        typeArguments: <TypeDeclaration>[
+                          TypeDeclaration(baseName: 'int', isNullable: true)
+                        ]),
+                    name: 'arg',
+                    offset: null)
+              ])
+        ])
+      ],
+      classes: <Class>[],
+      enums: <Enum>[],
+    );
+    final StringBuffer sink = StringBuffer();
+    const JavaOptions javaOptions = JavaOptions(className: 'Messages');
+    generateJava(javaOptions, root, sink);
+    final String code = sink.toString();
+    expect(code, contains('doit(List<Long> arg'));
+  });
+
+  test('flutter generics argument', () {
+    final Root root = Root(
+      apis: <Api>[
+        Api(name: 'Api', location: ApiLocation.flutter, methods: <Method>[
+          Method(
+              name: 'doit',
+              returnType: TypeDeclaration(baseName: 'void', isNullable: false),
+              arguments: <NamedType>[
+                NamedType(
+                    type: TypeDeclaration(
+                        baseName: 'List',
+                        isNullable: false,
+                        typeArguments: <TypeDeclaration>[
+                          TypeDeclaration(baseName: 'int', isNullable: true)
+                        ]),
+                    name: 'arg',
+                    offset: null)
+              ])
+        ])
+      ],
+      classes: <Class>[],
+      enums: <Enum>[],
+    );
+    final StringBuffer sink = StringBuffer();
+    const JavaOptions javaOptions = JavaOptions(className: 'Messages');
+    generateJava(javaOptions, root, sink);
+    final String code = sink.toString();
+    expect(code, contains('doit(List<Long> arg'));
+  });
+
+  test('host generics return', () {
+    final Root root = Root(
+      apis: <Api>[
+        Api(name: 'Api', location: ApiLocation.host, methods: <Method>[
+          Method(
+              name: 'doit',
+              returnType: TypeDeclaration(
+                  baseName: 'List',
+                  isNullable: false,
+                  typeArguments: <TypeDeclaration>[
+                    TypeDeclaration(baseName: 'int', isNullable: true)
+                  ]),
+              arguments: <NamedType>[])
+        ])
+      ],
+      classes: <Class>[],
+      enums: <Enum>[],
+    );
+    final StringBuffer sink = StringBuffer();
+    const JavaOptions javaOptions = JavaOptions(className: 'Messages');
+    generateJava(javaOptions, root, sink);
+    final String code = sink.toString();
+    expect(code, contains('List<Long> doit('));
+    expect(code, contains('List<Long> output ='));
+  });
+
+  test('flutter generics return', () {
+    final Root root = Root(
+      apis: <Api>[
+        Api(name: 'Api', location: ApiLocation.flutter, methods: <Method>[
+          Method(
+              name: 'doit',
+              returnType: TypeDeclaration(
+                  baseName: 'List',
+                  isNullable: false,
+                  typeArguments: <TypeDeclaration>[
+                    TypeDeclaration(baseName: 'int', isNullable: true)
+                  ]),
+              arguments: <NamedType>[])
+        ])
+      ],
+      classes: <Class>[],
+      enums: <Enum>[],
+    );
+    final StringBuffer sink = StringBuffer();
+    const JavaOptions javaOptions = JavaOptions(className: 'Messages');
+    generateJava(javaOptions, root, sink);
+    final String code = sink.toString();
+    expect(code, contains('doit(Reply<List<Long>> callback)'));
+    expect(code, contains('List<Long> output ='));
+  });
 }
diff --git a/packages/pigeon/test/objc_generator_test.dart b/packages/pigeon/test/objc_generator_test.dart
index 8acf439..a8321d1 100644
--- a/packages/pigeon/test/objc_generator_test.dart
+++ b/packages/pigeon/test/objc_generator_test.dart
@@ -9,12 +9,12 @@
 void main() {
   test('gen one class header', () {
     final Root root = Root(apis: <Api>[], classes: <Class>[
-      Class(name: 'Foobar', fields: <Field>[
-        Field(
-          name: 'field1',
-          dataType: 'String',
-          isNullable: true,
-        )
+      Class(name: 'Foobar', fields: <NamedType>[
+        NamedType(
+            type: TypeDeclaration(
+                baseName: 'String', isNullable: true, typeArguments: null),
+            name: 'field1',
+            offset: null)
       ]),
     ], enums: <Enum>[]);
     final StringBuffer sink = StringBuffer();
@@ -26,12 +26,12 @@
 
   test('gen one class source', () {
     final Root root = Root(apis: <Api>[], classes: <Class>[
-      Class(name: 'Foobar', fields: <Field>[
-        Field(
-          name: 'field1',
-          dataType: 'String',
-          isNullable: true,
-        )
+      Class(name: 'Foobar', fields: <NamedType>[
+        NamedType(
+            type: TypeDeclaration(
+                baseName: 'String', isNullable: true, typeArguments: null),
+            name: 'field1',
+            offset: null)
       ]),
     ], enums: <Enum>[]);
     final StringBuffer sink = StringBuffer();
@@ -83,17 +83,17 @@
       classes: <Class>[
         Class(
           name: 'Foobar',
-          fields: <Field>[
-            Field(
-              name: 'field1',
-              dataType: 'String',
-              isNullable: true,
-            ),
-            Field(
-              name: 'enum1',
-              dataType: 'Enum1',
-              isNullable: true,
-            ),
+          fields: <NamedType>[
+            NamedType(
+                type: TypeDeclaration(
+                    baseName: 'String', isNullable: true, typeArguments: null),
+                name: 'field1',
+                offset: null),
+            NamedType(
+                type: TypeDeclaration(
+                    baseName: 'Enum1', isNullable: true, typeArguments: null),
+                name: 'enum1',
+                offset: null),
           ],
         ),
       ],
@@ -115,29 +115,72 @@
     expect(code, contains('result.enum1 = [dict[@"enum1"] integerValue];'));
   });
 
+  test('gen one class header with enum', () {
+    final Root root = Root(
+      apis: <Api>[],
+      classes: <Class>[
+        Class(
+          name: 'Foobar',
+          fields: <NamedType>[
+            NamedType(
+                type: TypeDeclaration(
+                    baseName: 'String', isNullable: true, typeArguments: null),
+                name: 'field1',
+                offset: null),
+            NamedType(
+                type: TypeDeclaration(
+                    baseName: 'Enum1', isNullable: true, typeArguments: null),
+                name: 'enum1',
+                offset: null),
+          ],
+        ),
+      ],
+      enums: <Enum>[
+        Enum(
+          name: 'Enum1',
+          members: <String>[
+            'one',
+            'two',
+          ],
+        )
+      ],
+    );
+    final StringBuffer sink = StringBuffer();
+    generateObjcHeader(const ObjcOptions(header: 'foo.h'), root, sink);
+    final String code = sink.toString();
+    expect(code, contains('@property(nonatomic, assign) Enum1 enum1'));
+  });
+
   test('gen one api header', () {
     final Root root = Root(apis: <Api>[
       Api(name: 'Api', location: ApiLocation.host, methods: <Method>[
         Method(
             name: 'doSomething',
-            argType: 'Input',
-            isArgNullable: false,
-            returnType: 'Output')
+            arguments: <NamedType>[
+              NamedType(
+                  type: TypeDeclaration(
+                      baseName: 'Input',
+                      isNullable: false,
+                      typeArguments: null),
+                  name: '',
+                  offset: null)
+            ],
+            returnType: TypeDeclaration(baseName: 'Output', isNullable: false))
       ])
     ], classes: <Class>[
-      Class(name: 'Input', fields: <Field>[
-        Field(
-          name: 'input',
-          dataType: 'String',
-          isNullable: true,
-        )
+      Class(name: 'Input', fields: <NamedType>[
+        NamedType(
+            type: TypeDeclaration(
+                baseName: 'String', isNullable: true, typeArguments: null),
+            name: 'input',
+            offset: null)
       ]),
-      Class(name: 'Output', fields: <Field>[
-        Field(
-          name: 'output',
-          dataType: 'String',
-          isNullable: true,
-        )
+      Class(name: 'Output', fields: <NamedType>[
+        NamedType(
+            type: TypeDeclaration(
+                baseName: 'String', isNullable: true, typeArguments: null),
+            name: 'output',
+            offset: null)
       ])
     ], enums: <Enum>[]);
     final StringBuffer sink = StringBuffer();
@@ -155,24 +198,31 @@
       Api(name: 'Api', location: ApiLocation.host, methods: <Method>[
         Method(
             name: 'doSomething',
-            argType: 'Input',
-            isArgNullable: false,
-            returnType: 'Output')
+            arguments: <NamedType>[
+              NamedType(
+                  type: TypeDeclaration(
+                      baseName: 'Input',
+                      isNullable: false,
+                      typeArguments: null),
+                  name: '',
+                  offset: null)
+            ],
+            returnType: TypeDeclaration(baseName: 'Output', isNullable: false))
       ])
     ], classes: <Class>[
-      Class(name: 'Input', fields: <Field>[
-        Field(
-          name: 'input',
-          dataType: 'String',
-          isNullable: true,
-        )
+      Class(name: 'Input', fields: <NamedType>[
+        NamedType(
+            type: TypeDeclaration(
+                baseName: 'String', isNullable: true, typeArguments: null),
+            name: 'input',
+            offset: null)
       ]),
-      Class(name: 'Output', fields: <Field>[
-        Field(
-          name: 'output',
-          dataType: 'String',
-          isNullable: true,
-        )
+      Class(name: 'Output', fields: <NamedType>[
+        NamedType(
+            type: TypeDeclaration(
+                baseName: 'String', isNullable: true, typeArguments: null),
+            name: 'output',
+            offset: null)
       ])
     ], enums: <Enum>[]);
     final StringBuffer sink = StringBuffer();
@@ -186,47 +236,47 @@
 
   test('all the simple datatypes header', () {
     final Root root = Root(apis: <Api>[], classes: <Class>[
-      Class(name: 'Foobar', fields: <Field>[
-        Field(
-          name: 'aBool',
-          dataType: 'bool',
-          isNullable: true,
-        ),
-        Field(
-          name: 'aInt',
-          dataType: 'int',
-          isNullable: true,
-        ),
-        Field(
-          name: 'aDouble',
-          dataType: 'double',
-          isNullable: true,
-        ),
-        Field(
-          name: 'aString',
-          dataType: 'String',
-          isNullable: true,
-        ),
-        Field(
-          name: 'aUint8List',
-          dataType: 'Uint8List',
-          isNullable: true,
-        ),
-        Field(
-          name: 'aInt32List',
-          dataType: 'Int32List',
-          isNullable: true,
-        ),
-        Field(
-          name: 'aInt64List',
-          dataType: 'Int64List',
-          isNullable: true,
-        ),
-        Field(
-          name: 'aFloat64List',
-          dataType: 'Float64List',
-          isNullable: true,
-        ),
+      Class(name: 'Foobar', fields: <NamedType>[
+        NamedType(
+            type: TypeDeclaration(
+                baseName: 'bool', isNullable: true, typeArguments: null),
+            name: 'aBool',
+            offset: null),
+        NamedType(
+            type: TypeDeclaration(
+                baseName: 'int', isNullable: true, typeArguments: null),
+            name: 'aInt',
+            offset: null),
+        NamedType(
+            type: TypeDeclaration(
+                baseName: 'double', isNullable: true, typeArguments: null),
+            name: 'aDouble',
+            offset: null),
+        NamedType(
+            type: TypeDeclaration(
+                baseName: 'String', isNullable: true, typeArguments: null),
+            name: 'aString',
+            offset: null),
+        NamedType(
+            type: TypeDeclaration(
+                baseName: 'Uint8List', isNullable: true, typeArguments: null),
+            name: 'aUint8List',
+            offset: null),
+        NamedType(
+            type: TypeDeclaration(
+                baseName: 'Int32List', isNullable: true, typeArguments: null),
+            name: 'aInt32List',
+            offset: null),
+        NamedType(
+            type: TypeDeclaration(
+                baseName: 'Int64List', isNullable: true, typeArguments: null),
+            name: 'aInt64List',
+            offset: null),
+        NamedType(
+            type: TypeDeclaration(
+                baseName: 'Float64List', isNullable: true, typeArguments: null),
+            name: 'aFloat64List',
+            offset: null),
       ]),
     ], enums: <Enum>[]);
 
@@ -251,12 +301,12 @@
 
   test('bool source', () {
     final Root root = Root(apis: <Api>[], classes: <Class>[
-      Class(name: 'Foobar', fields: <Field>[
-        Field(
-          name: 'aBool',
-          dataType: 'bool',
-          isNullable: true,
-        ),
+      Class(name: 'Foobar', fields: <NamedType>[
+        NamedType(
+            type: TypeDeclaration(
+                baseName: 'bool', isNullable: true, typeArguments: null),
+            name: 'aBool',
+            offset: null),
       ]),
     ], enums: <Enum>[]);
 
@@ -269,19 +319,19 @@
 
   test('nested class header', () {
     final Root root = Root(apis: <Api>[], classes: <Class>[
-      Class(name: 'Input', fields: <Field>[
-        Field(
-          name: 'input',
-          dataType: 'String',
-          isNullable: true,
-        )
+      Class(name: 'Input', fields: <NamedType>[
+        NamedType(
+            type: TypeDeclaration(
+                baseName: 'String', isNullable: true, typeArguments: null),
+            name: 'input',
+            offset: null)
       ]),
-      Class(name: 'Nested', fields: <Field>[
-        Field(
-          name: 'nested',
-          dataType: 'Input',
-          isNullable: true,
-        )
+      Class(name: 'Nested', fields: <NamedType>[
+        NamedType(
+            type: TypeDeclaration(
+                baseName: 'Input', isNullable: true, typeArguments: null),
+            name: 'nested',
+            offset: null)
       ])
     ], enums: <Enum>[]);
     final StringBuffer sink = StringBuffer();
@@ -293,19 +343,19 @@
 
   test('nested class source', () {
     final Root root = Root(apis: <Api>[], classes: <Class>[
-      Class(name: 'Input', fields: <Field>[
-        Field(
-          name: 'input',
-          dataType: 'String',
-          isNullable: true,
-        )
+      Class(name: 'Input', fields: <NamedType>[
+        NamedType(
+            type: TypeDeclaration(
+                baseName: 'String', isNullable: true, typeArguments: null),
+            name: 'input',
+            offset: null)
       ]),
-      Class(name: 'Nested', fields: <Field>[
-        Field(
-          name: 'nested',
-          dataType: 'Input',
-          isNullable: true,
-        )
+      Class(name: 'Nested', fields: <NamedType>[
+        NamedType(
+            type: TypeDeclaration(
+                baseName: 'Input', isNullable: true, typeArguments: null),
+            name: 'nested',
+            offset: null)
       ])
     ], enums: <Enum>[]);
     final StringBuffer sink = StringBuffer();
@@ -317,12 +367,12 @@
 
   test('prefix class header', () {
     final Root root = Root(apis: <Api>[], classes: <Class>[
-      Class(name: 'Foobar', fields: <Field>[
-        Field(
-          name: 'field1',
-          dataType: 'String',
-          isNullable: true,
-        )
+      Class(name: 'Foobar', fields: <NamedType>[
+        NamedType(
+            type: TypeDeclaration(
+                baseName: 'String', isNullable: true, typeArguments: null),
+            name: 'field1',
+            offset: null)
       ]),
     ], enums: <Enum>[]);
     final StringBuffer sink = StringBuffer();
@@ -333,12 +383,12 @@
 
   test('prefix class source', () {
     final Root root = Root(apis: <Api>[], classes: <Class>[
-      Class(name: 'Foobar', fields: <Field>[
-        Field(
-          name: 'field1',
-          dataType: 'String',
-          isNullable: true,
-        )
+      Class(name: 'Foobar', fields: <NamedType>[
+        NamedType(
+            type: TypeDeclaration(
+                baseName: 'String', isNullable: true, typeArguments: null),
+            name: 'field1',
+            offset: null)
       ]),
     ], enums: <Enum>[]);
     final StringBuffer sink = StringBuffer();
@@ -352,24 +402,31 @@
       Api(name: 'Api', location: ApiLocation.host, methods: <Method>[
         Method(
             name: 'doSomething',
-            argType: 'Input',
-            isArgNullable: false,
-            returnType: 'Nested')
+            arguments: <NamedType>[
+              NamedType(
+                  type: TypeDeclaration(
+                      baseName: 'Input',
+                      isNullable: false,
+                      typeArguments: null),
+                  name: '',
+                  offset: null)
+            ],
+            returnType: TypeDeclaration(baseName: 'Nested', isNullable: false))
       ])
     ], classes: <Class>[
-      Class(name: 'Input', fields: <Field>[
-        Field(
-          name: 'input',
-          dataType: 'String',
-          isNullable: true,
-        )
+      Class(name: 'Input', fields: <NamedType>[
+        NamedType(
+            type: TypeDeclaration(
+                baseName: 'String', isNullable: true, typeArguments: null),
+            name: 'input',
+            offset: null)
       ]),
-      Class(name: 'Nested', fields: <Field>[
-        Field(
-          name: 'nested',
-          dataType: 'Input',
-          isNullable: true,
-        )
+      Class(name: 'Nested', fields: <NamedType>[
+        NamedType(
+            type: TypeDeclaration(
+                baseName: 'Input', isNullable: true, typeArguments: null),
+            name: 'nested',
+            offset: null)
       ])
     ], enums: <Enum>[]);
     final StringBuffer sink = StringBuffer();
@@ -385,24 +442,31 @@
       Api(name: 'Api', location: ApiLocation.host, methods: <Method>[
         Method(
             name: 'doSomething',
-            argType: 'Input',
-            isArgNullable: false,
-            returnType: 'Nested')
+            arguments: <NamedType>[
+              NamedType(
+                  type: TypeDeclaration(
+                      baseName: 'Input',
+                      isNullable: false,
+                      typeArguments: null),
+                  name: '',
+                  offset: null)
+            ],
+            returnType: TypeDeclaration(baseName: 'Nested', isNullable: false))
       ])
     ], classes: <Class>[
-      Class(name: 'Input', fields: <Field>[
-        Field(
-          name: 'input',
-          dataType: 'String',
-          isNullable: true,
-        )
+      Class(name: 'Input', fields: <NamedType>[
+        NamedType(
+            type: TypeDeclaration(
+                baseName: 'String', isNullable: true, typeArguments: null),
+            name: 'input',
+            offset: null)
       ]),
-      Class(name: 'Nested', fields: <Field>[
-        Field(
-          name: 'nested',
-          dataType: 'Input',
-          isNullable: true,
-        )
+      Class(name: 'Nested', fields: <NamedType>[
+        NamedType(
+            type: TypeDeclaration(
+                baseName: 'Input', isNullable: true, typeArguments: null),
+            name: 'nested',
+            offset: null)
       ])
     ], enums: <Enum>[]);
     final StringBuffer sink = StringBuffer();
@@ -418,24 +482,31 @@
       Api(name: 'Api', location: ApiLocation.flutter, methods: <Method>[
         Method(
             name: 'doSomething',
-            argType: 'Input',
-            isArgNullable: false,
-            returnType: 'Output')
+            arguments: <NamedType>[
+              NamedType(
+                  type: TypeDeclaration(
+                      baseName: 'Input',
+                      isNullable: false,
+                      typeArguments: null),
+                  name: '',
+                  offset: null)
+            ],
+            returnType: TypeDeclaration(baseName: 'Output', isNullable: false))
       ])
     ], classes: <Class>[
-      Class(name: 'Input', fields: <Field>[
-        Field(
-          name: 'input',
-          dataType: 'String',
-          isNullable: true,
-        )
+      Class(name: 'Input', fields: <NamedType>[
+        NamedType(
+            type: TypeDeclaration(
+                baseName: 'String', isNullable: true, typeArguments: null),
+            name: 'input',
+            offset: null)
       ]),
-      Class(name: 'Output', fields: <Field>[
-        Field(
-          name: 'output',
-          dataType: 'String',
-          isNullable: true,
-        )
+      Class(name: 'Output', fields: <NamedType>[
+        NamedType(
+            type: TypeDeclaration(
+                baseName: 'String', isNullable: true, typeArguments: null),
+            name: 'output',
+            offset: null)
       ])
     ], enums: <Enum>[]);
     final StringBuffer sink = StringBuffer();
@@ -454,24 +525,31 @@
       Api(name: 'Api', location: ApiLocation.flutter, methods: <Method>[
         Method(
             name: 'doSomething',
-            argType: 'Input',
-            isArgNullable: false,
-            returnType: 'Output')
+            arguments: <NamedType>[
+              NamedType(
+                  type: TypeDeclaration(
+                      baseName: 'Input',
+                      isNullable: false,
+                      typeArguments: null),
+                  name: '',
+                  offset: null)
+            ],
+            returnType: TypeDeclaration(baseName: 'Output', isNullable: false))
       ])
     ], classes: <Class>[
-      Class(name: 'Input', fields: <Field>[
-        Field(
-          name: 'input',
-          dataType: 'String',
-          isNullable: true,
-        )
+      Class(name: 'Input', fields: <NamedType>[
+        NamedType(
+            type: TypeDeclaration(
+                baseName: 'String', isNullable: true, typeArguments: null),
+            name: 'input',
+            offset: null)
       ]),
-      Class(name: 'Output', fields: <Field>[
-        Field(
-          name: 'output',
-          dataType: 'String',
-          isNullable: true,
-        )
+      Class(name: 'Output', fields: <NamedType>[
+        NamedType(
+            type: TypeDeclaration(
+                baseName: 'String', isNullable: true, typeArguments: null),
+            name: 'output',
+            offset: null)
       ])
     ], enums: <Enum>[]);
     final StringBuffer sink = StringBuffer();
@@ -486,17 +564,24 @@
       Api(name: 'Api', location: ApiLocation.host, methods: <Method>[
         Method(
             name: 'doSomething',
-            argType: 'Input',
-            isArgNullable: false,
-            returnType: 'void')
+            arguments: <NamedType>[
+              NamedType(
+                  type: TypeDeclaration(
+                      baseName: 'Input',
+                      isNullable: false,
+                      typeArguments: null),
+                  name: '',
+                  offset: null)
+            ],
+            returnType: TypeDeclaration(baseName: 'void', isNullable: false))
       ])
     ], classes: <Class>[
-      Class(name: 'Input', fields: <Field>[
-        Field(
-          name: 'input',
-          dataType: 'String',
-          isNullable: true,
-        )
+      Class(name: 'Input', fields: <NamedType>[
+        NamedType(
+            type: TypeDeclaration(
+                baseName: 'String', isNullable: true, typeArguments: null),
+            name: 'input',
+            offset: null)
       ]),
     ], enums: <Enum>[]);
     final StringBuffer sink = StringBuffer();
@@ -511,17 +596,24 @@
       Api(name: 'Api', location: ApiLocation.host, methods: <Method>[
         Method(
             name: 'doSomething',
-            argType: 'Input',
-            isArgNullable: false,
-            returnType: 'void')
+            arguments: <NamedType>[
+              NamedType(
+                  type: TypeDeclaration(
+                      baseName: 'Input',
+                      isNullable: false,
+                      typeArguments: null),
+                  name: '',
+                  offset: null)
+            ],
+            returnType: TypeDeclaration(baseName: 'void', isNullable: false))
       ])
     ], classes: <Class>[
-      Class(name: 'Input', fields: <Field>[
-        Field(
-          name: 'input',
-          dataType: 'String',
-          isNullable: true,
-        )
+      Class(name: 'Input', fields: <NamedType>[
+        NamedType(
+            type: TypeDeclaration(
+                baseName: 'String', isNullable: true, typeArguments: null),
+            name: 'input',
+            offset: null)
       ]),
     ], enums: <Enum>[]);
     final StringBuffer sink = StringBuffer();
@@ -538,17 +630,24 @@
       Api(name: 'Api', location: ApiLocation.flutter, methods: <Method>[
         Method(
             name: 'doSomething',
-            argType: 'Input',
-            isArgNullable: false,
-            returnType: 'void')
+            arguments: <NamedType>[
+              NamedType(
+                  type: TypeDeclaration(
+                      baseName: 'Input',
+                      isNullable: false,
+                      typeArguments: null),
+                  name: '',
+                  offset: null)
+            ],
+            returnType: TypeDeclaration(baseName: 'void', isNullable: false))
       ])
     ], classes: <Class>[
-      Class(name: 'Input', fields: <Field>[
-        Field(
-          name: 'input',
-          dataType: 'String',
-          isNullable: true,
-        )
+      Class(name: 'Input', fields: <NamedType>[
+        NamedType(
+            type: TypeDeclaration(
+                baseName: 'String', isNullable: true, typeArguments: null),
+            name: 'input',
+            offset: null)
       ]),
     ], enums: <Enum>[]);
     final StringBuffer sink = StringBuffer();
@@ -563,17 +662,24 @@
       Api(name: 'Api', location: ApiLocation.flutter, methods: <Method>[
         Method(
             name: 'doSomething',
-            argType: 'Input',
-            isArgNullable: false,
-            returnType: 'void')
+            arguments: <NamedType>[
+              NamedType(
+                  type: TypeDeclaration(
+                      baseName: 'Input',
+                      isNullable: false,
+                      typeArguments: null),
+                  name: '',
+                  offset: null)
+            ],
+            returnType: TypeDeclaration(baseName: 'void', isNullable: false))
       ])
     ], classes: <Class>[
-      Class(name: 'Input', fields: <Field>[
-        Field(
-          name: 'input',
-          dataType: 'String',
-          isNullable: true,
-        )
+      Class(name: 'Input', fields: <NamedType>[
+        NamedType(
+            type: TypeDeclaration(
+                baseName: 'String', isNullable: true, typeArguments: null),
+            name: 'input',
+            offset: null)
       ]),
     ], enums: <Enum>[]);
     final StringBuffer sink = StringBuffer();
@@ -589,17 +695,16 @@
       Api(name: 'Api', location: ApiLocation.host, methods: <Method>[
         Method(
             name: 'doSomething',
-            argType: 'void',
-            isArgNullable: false,
-            returnType: 'Output')
+            arguments: <NamedType>[],
+            returnType: TypeDeclaration(baseName: 'Output', isNullable: false))
       ])
     ], classes: <Class>[
-      Class(name: 'Output', fields: <Field>[
-        Field(
-          name: 'output',
-          dataType: 'String',
-          isNullable: true,
-        )
+      Class(name: 'Output', fields: <NamedType>[
+        NamedType(
+            type: TypeDeclaration(
+                baseName: 'String', isNullable: true, typeArguments: null),
+            name: 'output',
+            offset: null)
       ]),
     ], enums: <Enum>[]);
     final StringBuffer sink = StringBuffer();
@@ -614,17 +719,16 @@
       Api(name: 'Api', location: ApiLocation.host, methods: <Method>[
         Method(
             name: 'doSomething',
-            argType: 'void',
-            isArgNullable: false,
-            returnType: 'Output')
+            arguments: <NamedType>[],
+            returnType: TypeDeclaration(baseName: 'Output', isNullable: false))
       ])
     ], classes: <Class>[
-      Class(name: 'Output', fields: <Field>[
-        Field(
-          name: 'output',
-          dataType: 'String',
-          isNullable: true,
-        )
+      Class(name: 'Output', fields: <NamedType>[
+        NamedType(
+            type: TypeDeclaration(
+                baseName: 'String', isNullable: true, typeArguments: null),
+            name: 'output',
+            offset: null)
       ]),
     ], enums: <Enum>[]);
     final StringBuffer sink = StringBuffer();
@@ -639,17 +743,16 @@
       Api(name: 'Api', location: ApiLocation.flutter, methods: <Method>[
         Method(
             name: 'doSomething',
-            argType: 'void',
-            isArgNullable: false,
-            returnType: 'Output')
+            arguments: <NamedType>[],
+            returnType: TypeDeclaration(baseName: 'Output', isNullable: false))
       ])
     ], classes: <Class>[
-      Class(name: 'Output', fields: <Field>[
-        Field(
-          name: 'output',
-          dataType: 'String',
-          isNullable: true,
-        )
+      Class(name: 'Output', fields: <NamedType>[
+        NamedType(
+            type: TypeDeclaration(
+                baseName: 'String', isNullable: true, typeArguments: null),
+            name: 'output',
+            offset: null)
       ]),
     ], enums: <Enum>[]);
     final StringBuffer sink = StringBuffer();
@@ -667,17 +770,16 @@
       Api(name: 'Api', location: ApiLocation.flutter, methods: <Method>[
         Method(
             name: 'doSomething',
-            argType: 'void',
-            isArgNullable: false,
-            returnType: 'Output')
+            arguments: <NamedType>[],
+            returnType: TypeDeclaration(baseName: 'Output', isNullable: false))
       ])
     ], classes: <Class>[
-      Class(name: 'Output', fields: <Field>[
-        Field(
-          name: 'output',
-          dataType: 'String',
-          isNullable: true,
-        )
+      Class(name: 'Output', fields: <NamedType>[
+        NamedType(
+            type: TypeDeclaration(
+                baseName: 'String', isNullable: true, typeArguments: null),
+            name: 'output',
+            offset: null)
       ]),
     ], enums: <Enum>[]);
     final StringBuffer sink = StringBuffer();
@@ -693,12 +795,12 @@
 
   test('gen list', () {
     final Root root = Root(apis: <Api>[], classes: <Class>[
-      Class(name: 'Foobar', fields: <Field>[
-        Field(
-          name: 'field1',
-          dataType: 'List',
-          isNullable: true,
-        )
+      Class(name: 'Foobar', fields: <NamedType>[
+        NamedType(
+            type: TypeDeclaration(
+                baseName: 'List', isNullable: true, typeArguments: null),
+            name: 'field1',
+            offset: null)
       ]),
     ], enums: <Enum>[]);
     final StringBuffer sink = StringBuffer();
@@ -710,12 +812,12 @@
 
   test('gen map', () {
     final Root root = Root(apis: <Api>[], classes: <Class>[
-      Class(name: 'Foobar', fields: <Field>[
-        Field(
-          name: 'field1',
-          dataType: 'Map',
-          isNullable: true,
-        )
+      Class(name: 'Foobar', fields: <NamedType>[
+        NamedType(
+            type: TypeDeclaration(
+                baseName: 'Map', isNullable: true, typeArguments: null),
+            name: 'field1',
+            offset: null)
       ]),
     ], enums: <Enum>[]);
     final StringBuffer sink = StringBuffer();
@@ -730,25 +832,32 @@
       Api(name: 'Api', location: ApiLocation.host, methods: <Method>[
         Method(
             name: 'doSomething',
-            argType: 'Input',
-            isArgNullable: false,
-            returnType: 'void',
+            arguments: <NamedType>[
+              NamedType(
+                  type: TypeDeclaration(
+                      baseName: 'Input',
+                      isNullable: false,
+                      typeArguments: null),
+                  name: '',
+                  offset: null)
+            ],
+            returnType: TypeDeclaration(baseName: 'void', isNullable: false),
             isAsynchronous: true)
       ])
     ], classes: <Class>[
-      Class(name: 'Input', fields: <Field>[
-        Field(
-          name: 'input',
-          dataType: 'String',
-          isNullable: true,
-        )
+      Class(name: 'Input', fields: <NamedType>[
+        NamedType(
+            type: TypeDeclaration(
+                baseName: 'String', isNullable: true, typeArguments: null),
+            name: 'input',
+            offset: null)
       ]),
-      Class(name: 'Output', fields: <Field>[
-        Field(
-          name: 'output',
-          dataType: 'String',
-          isNullable: true,
-        )
+      Class(name: 'Output', fields: <NamedType>[
+        NamedType(
+            type: TypeDeclaration(
+                baseName: 'String', isNullable: true, typeArguments: null),
+            name: 'output',
+            offset: null)
       ]),
     ], enums: <Enum>[]);
     final StringBuffer sink = StringBuffer();
@@ -766,25 +875,32 @@
       Api(name: 'Api', location: ApiLocation.host, methods: <Method>[
         Method(
             name: 'doSomething',
-            argType: 'Input',
-            isArgNullable: false,
-            returnType: 'Output',
+            arguments: <NamedType>[
+              NamedType(
+                  type: TypeDeclaration(
+                      baseName: 'Input',
+                      isNullable: false,
+                      typeArguments: null),
+                  name: '',
+                  offset: null)
+            ],
+            returnType: TypeDeclaration(baseName: 'Output', isNullable: false),
             isAsynchronous: true)
       ])
     ], classes: <Class>[
-      Class(name: 'Input', fields: <Field>[
-        Field(
-          name: 'input',
-          dataType: 'String',
-          isNullable: true,
-        )
+      Class(name: 'Input', fields: <NamedType>[
+        NamedType(
+            type: TypeDeclaration(
+                baseName: 'String', isNullable: true, typeArguments: null),
+            name: 'input',
+            offset: null)
       ]),
-      Class(name: 'Output', fields: <Field>[
-        Field(
-          name: 'output',
-          dataType: 'String',
-          isNullable: true,
-        )
+      Class(name: 'Output', fields: <NamedType>[
+        NamedType(
+            type: TypeDeclaration(
+                baseName: 'String', isNullable: true, typeArguments: null),
+            name: 'output',
+            offset: null)
       ]),
     ], enums: <Enum>[]);
     final StringBuffer sink = StringBuffer();
@@ -802,18 +918,17 @@
       Api(name: 'Api', location: ApiLocation.host, methods: <Method>[
         Method(
             name: 'doSomething',
-            argType: 'void',
-            isArgNullable: false,
-            returnType: 'Output',
+            arguments: <NamedType>[],
+            returnType: TypeDeclaration(baseName: 'Output', isNullable: false),
             isAsynchronous: true)
       ])
     ], classes: <Class>[
-      Class(name: 'Output', fields: <Field>[
-        Field(
-          name: 'output',
-          dataType: 'String',
-          isNullable: true,
-        )
+      Class(name: 'Output', fields: <NamedType>[
+        NamedType(
+            type: TypeDeclaration(
+                baseName: 'String', isNullable: true, typeArguments: null),
+            name: 'output',
+            offset: null)
       ]),
     ], enums: <Enum>[]);
     final StringBuffer sink = StringBuffer();
@@ -831,9 +946,8 @@
       Api(name: 'Api', location: ApiLocation.host, methods: <Method>[
         Method(
             name: 'doSomething',
-            argType: 'void',
-            isArgNullable: false,
-            returnType: 'void',
+            arguments: <NamedType>[],
+            returnType: TypeDeclaration(baseName: 'void', isNullable: false),
             isAsynchronous: true)
       ])
     ], classes: <Class>[], enums: <Enum>[]);
@@ -852,25 +966,32 @@
       Api(name: 'Api', location: ApiLocation.host, methods: <Method>[
         Method(
             name: 'doSomething',
-            argType: 'Input',
-            isArgNullable: false,
-            returnType: 'Output',
+            arguments: <NamedType>[
+              NamedType(
+                  type: TypeDeclaration(
+                      baseName: 'Input',
+                      isNullable: false,
+                      typeArguments: null),
+                  name: '',
+                  offset: null)
+            ],
+            returnType: TypeDeclaration(baseName: 'Output', isNullable: false),
             isAsynchronous: true)
       ])
     ], classes: <Class>[
-      Class(name: 'Input', fields: <Field>[
-        Field(
-          name: 'input',
-          dataType: 'String',
-          isNullable: true,
-        )
+      Class(name: 'Input', fields: <NamedType>[
+        NamedType(
+            type: TypeDeclaration(
+                baseName: 'String', isNullable: true, typeArguments: null),
+            name: 'input',
+            offset: null)
       ]),
-      Class(name: 'Output', fields: <Field>[
-        Field(
-          name: 'output',
-          dataType: 'String',
-          isNullable: true,
-        )
+      Class(name: 'Output', fields: <NamedType>[
+        NamedType(
+            type: TypeDeclaration(
+                baseName: 'String', isNullable: true, typeArguments: null),
+            name: 'output',
+            offset: null)
       ]),
     ], enums: <Enum>[]);
     final StringBuffer sink = StringBuffer();
@@ -888,25 +1009,32 @@
       Api(name: 'Api', location: ApiLocation.host, methods: <Method>[
         Method(
             name: 'doSomething',
-            argType: 'Input',
-            isArgNullable: false,
-            returnType: 'void',
+            arguments: <NamedType>[
+              NamedType(
+                  type: TypeDeclaration(
+                      baseName: 'Input',
+                      isNullable: false,
+                      typeArguments: null),
+                  name: '',
+                  offset: null)
+            ],
+            returnType: TypeDeclaration(baseName: 'void', isNullable: false),
             isAsynchronous: true)
       ])
     ], classes: <Class>[
-      Class(name: 'Input', fields: <Field>[
-        Field(
-          name: 'input',
-          dataType: 'String',
-          isNullable: true,
-        )
+      Class(name: 'Input', fields: <NamedType>[
+        NamedType(
+            type: TypeDeclaration(
+                baseName: 'String', isNullable: true, typeArguments: null),
+            name: 'input',
+            offset: null)
       ]),
-      Class(name: 'Output', fields: <Field>[
-        Field(
-          name: 'output',
-          dataType: 'String',
-          isNullable: true,
-        )
+      Class(name: 'Output', fields: <NamedType>[
+        NamedType(
+            type: TypeDeclaration(
+                baseName: 'String', isNullable: true, typeArguments: null),
+            name: 'output',
+            offset: null)
       ]),
     ], enums: <Enum>[]);
     final StringBuffer sink = StringBuffer();
@@ -924,9 +1052,8 @@
       Api(name: 'Api', location: ApiLocation.host, methods: <Method>[
         Method(
             name: 'doSomething',
-            argType: 'void',
-            isArgNullable: false,
-            returnType: 'void',
+            arguments: <NamedType>[],
+            returnType: TypeDeclaration(baseName: 'void', isNullable: false),
             isAsynchronous: true)
       ])
     ], classes: <Class>[], enums: <Enum>[]);
@@ -943,18 +1070,17 @@
       Api(name: 'Api', location: ApiLocation.host, methods: <Method>[
         Method(
             name: 'doSomething',
-            argType: 'void',
-            isArgNullable: false,
-            returnType: 'Output',
+            arguments: <NamedType>[],
+            returnType: TypeDeclaration(baseName: 'Output', isNullable: false),
             isAsynchronous: true)
       ])
     ], classes: <Class>[
-      Class(name: 'Output', fields: <Field>[
-        Field(
-          name: 'output',
-          dataType: 'String',
-          isNullable: true,
-        )
+      Class(name: 'Output', fields: <NamedType>[
+        NamedType(
+            type: TypeDeclaration(
+                baseName: 'String', isNullable: true, typeArguments: null),
+            name: 'output',
+            offset: null)
       ]),
     ], enums: <Enum>[]);
     final StringBuffer sink = StringBuffer();
@@ -1000,4 +1126,215 @@
     final String code = sink.toString();
     expect(code, startsWith('// hello world'));
   });
+
+  test('field generics', () {
+    final Class klass = Class(
+      name: 'Foobar',
+      fields: <NamedType>[
+        NamedType(
+            type: TypeDeclaration(
+                baseName: 'List',
+                isNullable: true,
+                typeArguments: <TypeDeclaration>[
+                  TypeDeclaration(baseName: 'int', isNullable: true)
+                ]),
+            name: 'field1',
+            offset: null),
+      ],
+    );
+    final Root root = Root(
+      apis: <Api>[],
+      classes: <Class>[klass],
+      enums: <Enum>[],
+    );
+    final StringBuffer sink = StringBuffer();
+    generateObjcHeader(
+        const ObjcOptions(header: 'foo.h', prefix: 'ABC'), root, sink);
+    final String code = sink.toString();
+    expect(code, contains('NSArray<NSNumber *> * field1'));
+  });
+
+  test('host generics argument', () {
+    final Root root = Root(
+      apis: <Api>[
+        Api(name: 'Api', location: ApiLocation.host, methods: <Method>[
+          Method(
+              name: 'doit',
+              returnType: TypeDeclaration(baseName: 'void', isNullable: false),
+              arguments: <NamedType>[
+                NamedType(
+                    type: TypeDeclaration(
+                        baseName: 'List',
+                        isNullable: false,
+                        typeArguments: <TypeDeclaration>[
+                          TypeDeclaration(baseName: 'int', isNullable: true)
+                        ]),
+                    name: 'arg',
+                    offset: null)
+              ])
+        ])
+      ],
+      classes: <Class>[],
+      enums: <Enum>[],
+    );
+    {
+      final StringBuffer sink = StringBuffer();
+      generateObjcHeader(
+          const ObjcOptions(header: 'foo.h', prefix: 'ABC'), root, sink);
+      final String code = sink.toString();
+      expect(code, contains('doit:(NSArray<NSNumber *>*)input'));
+    }
+    {
+      final StringBuffer sink = StringBuffer();
+      generateObjcSource(
+          const ObjcOptions(header: 'foo.h', prefix: 'ABC'), root, sink);
+      final String code = sink.toString();
+      expect(code, contains('NSArray<NSNumber *> *input = message'));
+    }
+  });
+
+  test('flutter generics argument', () {
+    final Root root = Root(
+      apis: <Api>[
+        Api(name: 'Api', location: ApiLocation.flutter, methods: <Method>[
+          Method(
+              name: 'doit',
+              returnType: TypeDeclaration(baseName: 'void', isNullable: false),
+              arguments: <NamedType>[
+                NamedType(
+                    type: TypeDeclaration(
+                        baseName: 'List',
+                        isNullable: false,
+                        typeArguments: <TypeDeclaration>[
+                          TypeDeclaration(baseName: 'int', isNullable: true)
+                        ]),
+                    name: 'arg',
+                    offset: null)
+              ])
+        ])
+      ],
+      classes: <Class>[],
+      enums: <Enum>[],
+    );
+    {
+      final StringBuffer sink = StringBuffer();
+      generateObjcHeader(
+          const ObjcOptions(header: 'foo.h', prefix: 'ABC'), root, sink);
+      final String code = sink.toString();
+      expect(code, contains('doit:(NSArray<NSNumber *>*)input'));
+    }
+    {
+      final StringBuffer sink = StringBuffer();
+      generateObjcSource(
+          const ObjcOptions(header: 'foo.h', prefix: 'ABC'), root, sink);
+      final String code = sink.toString();
+      expect(code, contains('doit:(NSArray<NSNumber *>*)input'));
+    }
+  });
+
+  test('host nested generic argument', () {
+    final Root root = Root(
+      apis: <Api>[
+        Api(name: 'Api', location: ApiLocation.host, methods: <Method>[
+          Method(
+              name: 'doit',
+              returnType: TypeDeclaration(baseName: 'void', isNullable: false),
+              arguments: <NamedType>[
+                NamedType(
+                    type: TypeDeclaration(
+                        baseName: 'List',
+                        isNullable: false,
+                        typeArguments: <TypeDeclaration>[
+                          TypeDeclaration(
+                              baseName: 'List',
+                              isNullable: true,
+                              typeArguments: <TypeDeclaration>[
+                                TypeDeclaration(
+                                    baseName: 'bool', isNullable: true)
+                              ]),
+                        ]),
+                    name: 'arg',
+                    offset: null)
+              ])
+        ])
+      ],
+      classes: <Class>[],
+      enums: <Enum>[],
+    );
+    {
+      final StringBuffer sink = StringBuffer();
+      generateObjcHeader(
+          const ObjcOptions(header: 'foo.h', prefix: 'ABC'), root, sink);
+      final String code = sink.toString();
+      expect(code, contains('doit:(NSArray<NSArray<NSNumber *> *>*)input'));
+    }
+  });
+
+  test('host generics return', () {
+    final Root root = Root(
+      apis: <Api>[
+        Api(name: 'Api', location: ApiLocation.host, methods: <Method>[
+          Method(
+              name: 'doit',
+              returnType: TypeDeclaration(
+                  baseName: 'List',
+                  isNullable: false,
+                  typeArguments: <TypeDeclaration>[
+                    TypeDeclaration(baseName: 'int', isNullable: true)
+                  ]),
+              arguments: <NamedType>[])
+        ])
+      ],
+      classes: <Class>[],
+      enums: <Enum>[],
+    );
+    {
+      final StringBuffer sink = StringBuffer();
+      generateObjcHeader(
+          const ObjcOptions(header: 'foo.h', prefix: 'ABC'), root, sink);
+      final String code = sink.toString();
+      expect(code, contains('-(nullable NSArray<NSNumber *> *)doit:'));
+    }
+    {
+      final StringBuffer sink = StringBuffer();
+      generateObjcSource(
+          const ObjcOptions(header: 'foo.h', prefix: 'ABC'), root, sink);
+      final String code = sink.toString();
+      expect(code, contains('NSArray<NSNumber *> *output ='));
+    }
+  });
+
+  test('host generics return', () {
+    final Root root = Root(
+      apis: <Api>[
+        Api(name: 'Api', location: ApiLocation.flutter, methods: <Method>[
+          Method(
+              name: 'doit',
+              returnType: TypeDeclaration(
+                  baseName: 'List',
+                  isNullable: false,
+                  typeArguments: <TypeDeclaration>[
+                    TypeDeclaration(baseName: 'int', isNullable: true)
+                  ]),
+              arguments: <NamedType>[])
+        ])
+      ],
+      classes: <Class>[],
+      enums: <Enum>[],
+    );
+    {
+      final StringBuffer sink = StringBuffer();
+      generateObjcHeader(
+          const ObjcOptions(header: 'foo.h', prefix: 'ABC'), root, sink);
+      final String code = sink.toString();
+      expect(code, contains('doit:(void(^)(NSArray<NSNumber *>*'));
+    }
+    {
+      final StringBuffer sink = StringBuffer();
+      generateObjcSource(
+          const ObjcOptions(header: 'foo.h', prefix: 'ABC'), root, sink);
+      final String code = sink.toString();
+      expect(code, contains('doit:(void(^)(NSArray<NSNumber *>*'));
+    }
+  });
 }
diff --git a/packages/pigeon/test/pigeon_lib_test.dart b/packages/pigeon/test/pigeon_lib_test.dart
index 278e0d4..dd07556 100644
--- a/packages/pigeon/test/pigeon_lib_test.dart
+++ b/packages/pigeon/test/pigeon_lib_test.dart
@@ -98,8 +98,10 @@
     expect(root.apis[0].name, equals('Api1'));
     expect(root.apis[0].methods.length, equals(1));
     expect(root.apis[0].methods[0].name, equals('doit'));
-    expect(root.apis[0].methods[0].argType, equals('Input1'));
-    expect(root.apis[0].methods[0].returnType, equals('Output1'));
+    expect(root.apis[0].methods[0].arguments[0].name, equals('input'));
+    expect(
+        root.apis[0].methods[0].arguments[0].type.baseName, equals('Input1'));
+    expect(root.apis[0].methods[0].returnType.baseName, equals('Output1'));
 
     Class? input;
     Class? output;
@@ -115,13 +117,13 @@
 
     expect(input?.fields.length, equals(1));
     expect(input?.fields[0].name, equals('input'));
-    expect(input?.fields[0].dataType, equals('String'));
-    expect(input?.fields[0].isNullable, isTrue);
+    expect(input?.fields[0].type.baseName, equals('String'));
+    expect(input?.fields[0].type.isNullable, isTrue);
 
     expect(output?.fields.length, equals(1));
     expect(output?.fields[0].name, equals('output'));
-    expect(output?.fields[0].dataType, equals('String'));
-    expect(output?.fields[0].isNullable, isTrue);
+    expect(output?.fields[0].type.baseName, equals('String'));
+    expect(output?.fields[0].type.isNullable, isTrue);
   });
 
   test('invalid datatype', () {
@@ -162,8 +164,8 @@
     expect(results.root.classes.length, equals(1));
     expect(results.root.classes[0].name, equals('ClassWithEnum'));
     expect(results.root.classes[0].fields.length, equals(1));
-    expect(results.root.classes[0].fields[0].dataType, equals('Enum1'));
-    expect(results.root.classes[0].fields[0].isNullable, isTrue);
+    expect(results.root.classes[0].fields[0].type.baseName, equals('Enum1'));
+    expect(results.root.classes[0].fields[0].type.isNullable, isTrue);
     expect(results.root.classes[0].fields[0].name, equals('enum1'));
   });
 
@@ -208,8 +210,8 @@
     final Class nested =
         results.root.classes.firstWhere((Class x) => x.name == 'Nested');
     expect(nested.fields.length, equals(1));
-    expect(nested.fields[0].dataType, equals('Input1'));
-    expect(nested.fields[0].isNullable, isTrue);
+    expect(nested.fields[0].type.baseName, equals('Input1'));
+    expect(nested.fields[0].type.isNullable, isTrue);
   });
 
   test('flutter api', () {
@@ -246,7 +248,7 @@
     expect(results.root.apis.length, equals(1));
     expect(results.root.apis[0].methods.length, equals(1));
     expect(results.root.apis[0].name, equals('VoidApi'));
-    expect(results.root.apis[0].methods[0].returnType, equals('void'));
+    expect(results.root.apis[0].methods[0].returnType.baseName, equals('void'));
   });
 
   test('void arg host api', () {
@@ -265,8 +267,9 @@
     expect(results.root.apis.length, equals(1));
     expect(results.root.apis[0].methods.length, equals(1));
     expect(results.root.apis[0].name, equals('VoidArgApi'));
-    expect(results.root.apis[0].methods[0].returnType, equals('Output1'));
-    expect(results.root.apis[0].methods[0].argType, equals('void'));
+    expect(
+        results.root.apis[0].methods[0].returnType.baseName, equals('Output1'));
+    expect(results.root.apis[0].methods[0].arguments.isEmpty, isTrue);
   });
 
   test('mockDartClass', () {
@@ -414,7 +417,7 @@
     final Class foo =
         results.root.classes.firstWhere((Class aClass) => aClass.name == 'Foo');
     expect(foo.fields.length, 1);
-    expect(foo.fields[0].dataType, 'Bar');
+    expect(foo.fields[0].type.baseName, 'Bar');
   });
 
   test('test compilation error', () {
@@ -542,23 +545,6 @@
     expect(parseResults.errors.length, 0);
   });
 
-  test('error with generics', () {
-    const String code = '''
-class WithTemplate {
-  List<int>? list;
-}
-
-@HostApi()
-abstract class WithTemplateApi {
-  void doit(WithTemplate withTemplate);
-}
-''';
-    final ParseResults parseResult = _parseSource(code);
-    expect(parseResult.errors.length, equals(1));
-    expect(parseResult.errors[0].message, contains('Generic fields'));
-    expect(parseResult.errors[0].lineNumber, isNotNull);
-  });
-
   test('error with static field', () {
     const String code = '''
 class WithStaticField {
@@ -577,6 +563,62 @@
     expect(parseResult.errors[0].lineNumber, isNotNull);
   });
 
+  test('parse generics', () {
+    const String code = '''
+class Foo {
+  List<int?>? list;
+}
+
+@HostApi()
+abstract class Api {
+  void doit(Foo foo);
+}
+''';
+    final ParseResults parseResult = _parseSource(code);
+    expect(parseResult.errors.length, equals(0));
+    final NamedType field = parseResult.root.classes[0].fields[0];
+    expect(field.type.typeArguments!.length, 1);
+    expect(field.type.typeArguments![0].baseName, 'int');
+  });
+
+  test('parse recursive generics', () {
+    const String code = '''
+class Foo {
+  List<List<int?>?>? list;
+}
+
+@HostApi()
+abstract class Api {
+  void doit(Foo foo);
+}
+''';
+    final ParseResults parseResult = _parseSource(code);
+    expect(parseResult.errors.length, equals(0));
+    final NamedType field = parseResult.root.classes[0].fields[0];
+    expect(field.type.typeArguments!.length, 1);
+    expect(field.type.typeArguments![0].baseName, 'List');
+    expect(field.type.typeArguments![0].typeArguments![0].baseName, 'int');
+  });
+
+  test('error nonnull type argument', () {
+    const String code = '''
+class Foo {
+  List<int> list;
+}
+
+@HostApi()
+abstract class Api {
+  void doit(Foo foo);
+}
+''';
+    final ParseResults parseResult = _parseSource(code);
+    expect(parseResult.errors.length, equals(1));
+    expect(parseResult.errors[0].message,
+        contains('Generic type arguments must be nullable'));
+    expect(parseResult.errors[0].message, contains('"list"'));
+    expect(parseResult.errors[0].lineNumber, 2);
+  });
+
   test('enums argument', () {
     // TODO(gaaclarke): Make this not an error: https://github.com/flutter/flutter/issues/87307
     const String code = '''
@@ -614,4 +656,83 @@
     expect(parseResult.errors.length, equals(1));
     expect(parseResult.errors[0].message, contains('Enums'));
   });
+
+  test('return type generics', () {
+    const String code = '''
+@HostApi()
+abstract class Api {
+  List<double?> doit();
+}
+''';
+    final ParseResults parseResult = _parseSource(code);
+    expect(parseResult.root.apis[0].methods[0].returnType.baseName, 'List');
+    expect(
+        parseResult
+            .root.apis[0].methods[0].returnType.typeArguments![0].baseName,
+        'double');
+    expect(
+        parseResult
+            .root.apis[0].methods[0].returnType.typeArguments![0].isNullable,
+        isTrue);
+  });
+
+  test('argument generics', () {
+    const String code = '''
+@HostApi()
+abstract class Api {
+  void doit(List<double?> value);
+}
+''';
+    final ParseResults parseResult = _parseSource(code);
+    expect(
+        parseResult.root.apis[0].methods[0].arguments[0].type.baseName, 'List');
+    expect(
+        parseResult.root.apis[0].methods[0].arguments[0].type.typeArguments![0]
+            .baseName,
+        'double');
+    expect(
+        parseResult.root.apis[0].methods[0].arguments[0].type.typeArguments![0]
+            .isNullable,
+        isTrue);
+  });
+
+  test('map generics', () {
+    const String code = '''
+class Foo {
+  Map<String?, int?> map;
+}
+
+@HostApi()
+abstract class Api {
+  void doit(Foo foo);
+}
+''';
+    final ParseResults parseResult = _parseSource(code);
+    final NamedType field = parseResult.root.classes[0].fields[0];
+    expect(field.type.typeArguments!.length, 2);
+    expect(field.type.typeArguments![0].baseName, 'String');
+    expect(field.type.typeArguments![1].baseName, 'int');
+  });
+
+  test('two arguments', () {
+    const String code = '''
+class Input {
+  String? input;
+}
+
+@HostApi()
+abstract class Api {
+  void method(Input input1, Input input2);
+}
+''';
+    final ParseResults results = _parseSource(code);
+    expect(results.errors.length, 1);
+    expect(results.errors[0].lineNumber, 7);
+    expect(results.errors[0].message, contains('Multiple arguments'));
+    // TODO(gaaclarke): Make this not an error, https://github.com/flutter/flutter/issues/86971.
+    // expect(results.root.apis.length, 1);
+    // expect(results.root.apis[0].methods.length, equals(1));
+    // expect(results.root.apis[0].methods[0].name, equals('method'));
+    // expect(results.root.apis[0].methods[0].arguments.length, 2);
+  });
 }