Skip to content

Commit

Permalink
fix: allow record types as params and return types (#4)
Browse files Browse the repository at this point in the history
This allows more complex types to be used in
return types and parameter types for bottom
sheets, dialogs, and routes.
Types like `(int, int, {String foobar})` or
referenced types (imports from other files) are
supported.
  • Loading branch information
buehler committed Mar 6, 2024
1 parent c51e29f commit fc946a5
Show file tree
Hide file tree
Showing 8 changed files with 1,529 additions and 41 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import 'package:fluorflow/fluorflow.dart';
import 'package:flutter/material.dart';

class Foobar {}

final class ComplexReturnTypeSheet
extends FluorFlowSimpleBottomSheet<(int, {Foobar f})> {
const ComplexReturnTypeSheet({super.key, required super.completer});

@override
Widget build(BuildContext context) => const Placeholder();
}
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ class BottomSheetBuilder implements Builder {
..positionalFieldTypes.add(refer('bool?'))
..positionalFieldTypes.add(recursiveTypeReference(
lib, sheetReturnType,
typeRefUpdates: (b) => b.isNullable = true)));
forceNullable: true)));
final params = sheetClass.constructors.first.parameters
.where(
(p) => p.displayName != 'key' && p.displayName != 'completer')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ class DialogBuilder implements Builder {
..positionalFieldTypes.add(refer('bool?'))
..positionalFieldTypes.add(recursiveTypeReference(
lib, dialogReturnType,
typeRefUpdates: (b) => b.isNullable = true)));
forceNullable: true)));
final params = dialogClass.constructors.first.parameters
.where(
(p) => p.displayName != 'key' && p.displayName != 'completer')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,7 @@ class RouterBuilder implements Builder {
..fields.addAll(params.map((p) => Field((b) => b
..name = p.displayName
..modifier = FieldModifier.final$
..type = refer(p.type.getDisplayString(withNullability: true),
lib.pathToElement(p.type.element!).toString()))))
..type = recursiveTypeReference(lib, p.type))))
..constructors.add(Constructor((b) => b
..constant = true
..optionalParameters.addAll(params.map((p) => Parameter((b) => b
Expand Down Expand Up @@ -229,8 +228,7 @@ class RouterBuilder implements Builder {
])
..optionalParameters.addAll(params.map((p) => Parameter((b) => b
..name = p.name
..type = refer(p.type.getDisplayString(withNullability: true),
lib.pathToElement(p.type.element!).toString())
..type = recursiveTypeReference(lib, p.type)
..required = p.isRequired
..defaultTo = p.hasDefaultValue ? Code(p.defaultValueCode!) : null
..named = true)))
Expand Down
88 changes: 53 additions & 35 deletions packages/fluorflow_generator/lib/src/utils.dart
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ TEnum getEnumFromAnnotation<TEnum extends Enum>(
cb.Reference recursiveTypeReference(
LibraryReader lib,
DartType t, {
dynamic Function(cb.TypeReferenceBuilder)? typeRefUpdates,
bool forceNullable = false,
}) {
cb.Reference mapRef(DartType t) => recursiveTypeReference(lib, t);

Expand All @@ -27,40 +27,58 @@ cb.Reference recursiveTypeReference(
cb.refer(t.getDisplayString(withNullability: false)),
DartType(alias: InstantiatedTypeAliasElement(:final element)) =>
cb.refer(element.name, lib.pathToElement(element).toString()),
final FunctionType f => cb.FunctionType((b) => b
..returnType = mapRef(f.returnType)
..requiredParameters.addAll(f.parameters
.where((p) => p.isRequiredPositional)
.map((p) => p.type)
.map(mapRef))
..optionalParameters.addAll(f.parameters
.where((p) => p.isOptionalPositional)
.map((p) => p.type)
.map(mapRef))
..namedRequiredParameters.addAll({
for (final p in f.parameters.where((p) => p.isRequiredNamed))
p.name: mapRef(p.type)
})
..namedParameters.addAll({
for (final p in f.parameters.where((p) => p.isOptionalNamed))
p.name: mapRef(p.type)
})
..types.addAll(f.typeFormals
.where((tf) => tf.bound != null)
.map((tf) => tf.bound!)
.map(mapRef))),
FunctionType(
:final returnType,
:final parameters,
:final typeFormals,
:final nullabilitySuffix
) =>
cb.FunctionType((b) => b
..isNullable =
forceNullable || nullabilitySuffix == NullabilitySuffix.question
..returnType = mapRef(returnType)
..requiredParameters.addAll(parameters
.where((p) => p.isRequiredPositional)
.map((p) => p.type)
.map(mapRef))
..optionalParameters.addAll(parameters
.where((p) => p.isOptionalPositional)
.map((p) => p.type)
.map(mapRef))
..namedRequiredParameters.addAll({
for (final p in parameters.where((p) => p.isRequiredNamed))
p.name: mapRef(p.type)
})
..namedParameters.addAll({
for (final p in parameters.where((p) => p.isOptionalNamed))
p.name: mapRef(p.type)
})
..types.addAll(typeFormals
.where((tf) => tf.bound != null)
.map((tf) => tf.bound!)
.map(mapRef))),
RecordType(
:final positionalFields,
:final namedFields,
:final nullabilitySuffix
) =>
cb.RecordType((b) => b
..isNullable =
forceNullable || nullabilitySuffix == NullabilitySuffix.question
..positionalFieldTypes
.addAll(positionalFields.map((f) => mapRef(f.type)))
..namedFieldTypes.addIterable(namedFields,
key: (f) => f.name, value: (f) => mapRef(f.type))),
_ => cb.TypeReference((b) => b
..isNullable = t.nullabilitySuffix == NullabilitySuffix.question
..symbol =
t.element?.name ?? t.getDisplayString(withNullability: false)
..types.addAll(switch (t) {
ParameterizedType(:final typeArguments) =>
typeArguments.map(mapRef).toList(),
_ => [],
})
..url = t.element == null
? null
: lib.pathToElement(t.element!).toString())
.rebuild(typeRefUpdates ?? (b) => b),
..isNullable =
forceNullable || t.nullabilitySuffix == NullabilitySuffix.question
..symbol = t.element?.name ?? t.getDisplayString(withNullability: false)
..types.addAll(switch (t) {
ParameterizedType(:final typeArguments) =>
typeArguments.map(mapRef).toList(),
_ => [],
})
..url =
t.element == null ? null : lib.pathToElement(t.element!).toString()),
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -1143,6 +1143,134 @@ extension BottomSheets on _i1.BottomSheetService {
reader: await PackageAssetReader.currentIsolate()));
});

group('for Bottom Sheet with special return types', () {
test(
'should generate sheet method that returns record type.',
() async => await testBuilder(
BottomSheetBuilder(BuilderOptions.empty),
{
'a|lib/a.dart': '''
import 'package:fluorflow/fluorflow.dart';
class MySheet extends FluorFlowSimpleBottomSheet<(int, int)> {
const MySheet({super.key, required this.completer});
}
'''
},
outputs: {
'a|lib/app.bottom_sheets.dart': r'''
// ignore_for_file: type=lint
// ignore_for_file: no_leading_underscores_for_library_prefixes
import 'dart:ui' as _i2;
import 'package:a/a.dart' as _i3;
import 'package:fluorflow/fluorflow.dart' as _i1;
extension BottomSheets on _i1.BottomSheetService {
Future<(bool?, (int, int)?)> showMySheet({
_i2.Color barrierColor = const _i2.Color(0x80000000),
bool fullscreen = false,
bool ignoreSafeArea = true,
bool draggable = true,
}) =>
showBottomSheet<(bool?, (int, int)?), _i3.MySheet>(
_i3.MySheet(completer: closeSheet),
barrierColor: barrierColor,
fullscreen: fullscreen,
draggable: draggable,
ignoreSafeArea: ignoreSafeArea,
).then((r) => (r?.$1, r?.$2));
}
'''
},
reader: await PackageAssetReader.currentIsolate()));

test(
'should generate sheet method that returns named record type.',
() async => await testBuilder(
BottomSheetBuilder(BuilderOptions.empty),
{
'a|lib/a.dart': '''
import 'package:fluorflow/fluorflow.dart';
class MySheet extends FluorFlowSimpleBottomSheet<({int a})> {
const MySheet({super.key, required this.completer});
}
'''
},
outputs: {
'a|lib/app.bottom_sheets.dart': r'''
// ignore_for_file: type=lint
// ignore_for_file: no_leading_underscores_for_library_prefixes
import 'dart:ui' as _i2;
import 'package:a/a.dart' as _i3;
import 'package:fluorflow/fluorflow.dart' as _i1;
extension BottomSheets on _i1.BottomSheetService {
Future<(bool?, ({int a})?)> showMySheet({
_i2.Color barrierColor = const _i2.Color(0x80000000),
bool fullscreen = false,
bool ignoreSafeArea = true,
bool draggable = true,
}) =>
showBottomSheet<(bool?, ({int a})?), _i3.MySheet>(
_i3.MySheet(completer: closeSheet),
barrierColor: barrierColor,
fullscreen: fullscreen,
draggable: draggable,
ignoreSafeArea: ignoreSafeArea,
).then((r) => (r?.$1, r?.$2));
}
'''
},
reader: await PackageAssetReader.currentIsolate()));

test(
'should generate sheet method that returns function type.',
() async => await testBuilder(
BottomSheetBuilder(BuilderOptions.empty),
{
'a|lib/a.dart': '''
import 'package:fluorflow/fluorflow.dart';
class MySheet extends FluorFlowSimpleBottomSheet<void Function()> {
const MySheet({super.key, required this.completer});
}
'''
},
outputs: {
'a|lib/app.bottom_sheets.dart': r'''
// ignore_for_file: type=lint
// ignore_for_file: no_leading_underscores_for_library_prefixes
import 'dart:ui' as _i2;
import 'package:a/a.dart' as _i3;
import 'package:fluorflow/fluorflow.dart' as _i1;
extension BottomSheets on _i1.BottomSheetService {
Future<(bool?, void Function()?)> showMySheet({
_i2.Color barrierColor = const _i2.Color(0x80000000),
bool fullscreen = false,
bool ignoreSafeArea = true,
bool draggable = true,
}) =>
showBottomSheet<(bool?, void Function()?), _i3.MySheet>(
_i3.MySheet(completer: closeSheet),
barrierColor: barrierColor,
fullscreen: fullscreen,
draggable: draggable,
ignoreSafeArea: ignoreSafeArea,
).then((r) => (r?.$1, r?.$2));
}
'''
},
reader: await PackageAssetReader.currentIsolate()));
});

group('with @BottomSheetConfig()', () {
test(
'should generate sheet method with custom default options.',
Expand Down
Loading

0 comments on commit fc946a5

Please sign in to comment.