Skip to content

Commit

Permalink
fix mapper generation for generic function
Browse files Browse the repository at this point in the history
  • Loading branch information
schultek committed Oct 19, 2024
1 parent f1f5e96 commit a682900
Show file tree
Hide file tree
Showing 8 changed files with 180 additions and 1 deletion.
4 changes: 4 additions & 0 deletions packages/dart_mappable/lib/src/mappers/simple_mapper.dart
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ abstract class SimpleMapper1Bounded<T extends Object, B1>
/// {@category Custom Mappers}
abstract class SimpleMapper1<T extends Object>
extends SimpleMapper1Bounded<T, dynamic> {
const SimpleMapper1();

/// Override as `MyClass<A> decode<A>(Object value)`
@override
T decode<A>(Object value);
Expand Down Expand Up @@ -104,6 +106,8 @@ abstract class SimpleMapper2Bounded<T extends Object, B1, B2>
/// {@category Custom Mappers}
abstract class SimpleMapper2<T extends Object>
extends SimpleMapper2Bounded<T, dynamic, dynamic> {
const SimpleMapper2();

/// Override as `MyClass<A, B> decode<A, B>(Object value)`
@override
T decode<A, B>(Object value);
Expand Down
14 changes: 14 additions & 0 deletions packages/dart_mappable/test/generics/generics_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,13 @@ class NullableGenerics<T extends Object> with NullableGenericsMappable<T> {
const NullableGenerics({required this.value});
}

@MappableClass()
class FunctionContainer<T> with FunctionContainerMappable<T> {
String Function(T) genericFunction;

FunctionContainer(this.genericFunction);
}

void main() {
group('Generic classes', () {
test('Should encode generic objects', () {
Expand Down Expand Up @@ -205,5 +212,12 @@ void main() {
isNull,
);
});

test('Should generate generic function field', () {
expect(
FunctionContainer<int>((int a) => a.toString()).toString(),
equals('FunctionContainer(genericFunction: Closure: (int) => String)'),
);
});
});
}
112 changes: 112 additions & 0 deletions packages/dart_mappable/test/generics/generics_test.mapper.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1058,3 +1058,115 @@ class _NullableGenericsCopyWithImpl<$R, $Out, T extends Object>
$chain<$R2, $Out2>(Then<$Out2, $R2> t) =>
_NullableGenericsCopyWithImpl($value, $cast, t);
}

class FunctionContainerMapper extends ClassMapperBase<FunctionContainer> {
FunctionContainerMapper._();

static FunctionContainerMapper? _instance;
static FunctionContainerMapper ensureInitialized() {
if (_instance == null) {
MapperContainer.globals.use(_instance = FunctionContainerMapper._());
}
return _instance!;
}

@override
final String id = 'FunctionContainer';
@override
Function get typeFactory => <T>(f) => f<FunctionContainer<T>>();

static Function _$genericFunction(FunctionContainer v) =>
(v as dynamic).genericFunction as Function;
static dynamic _arg$genericFunction<T>(f) => f<String Function(T)>();
static const Field<FunctionContainer, Function> _f$genericFunction =
Field('genericFunction', _$genericFunction, arg: _arg$genericFunction);

@override
final MappableFields<FunctionContainer> fields = const {
#genericFunction: _f$genericFunction,
};

static FunctionContainer<T> _instantiate<T>(DecodingData data) {
return FunctionContainer(data.dec(_f$genericFunction));
}

@override
final Function instantiate = _instantiate;

static FunctionContainer<T> fromMap<T>(Map<String, dynamic> map) {
return ensureInitialized().decodeMap<FunctionContainer<T>>(map);
}

static FunctionContainer<T> fromJson<T>(String json) {
return ensureInitialized().decodeJson<FunctionContainer<T>>(json);
}
}

mixin FunctionContainerMappable<T> {
String toJson() {
return FunctionContainerMapper.ensureInitialized()
.encodeJson<FunctionContainer<T>>(this as FunctionContainer<T>);
}

Map<String, dynamic> toMap() {
return FunctionContainerMapper.ensureInitialized()
.encodeMap<FunctionContainer<T>>(this as FunctionContainer<T>);
}

FunctionContainerCopyWith<FunctionContainer<T>, FunctionContainer<T>,
FunctionContainer<T>, T>
get copyWith => _FunctionContainerCopyWithImpl(
this as FunctionContainer<T>, $identity, $identity);
@override
String toString() {
return FunctionContainerMapper.ensureInitialized()
.stringifyValue(this as FunctionContainer<T>);
}

@override
bool operator ==(Object other) {
return FunctionContainerMapper.ensureInitialized()
.equalsValue(this as FunctionContainer<T>, other);
}

@override
int get hashCode {
return FunctionContainerMapper.ensureInitialized()
.hashValue(this as FunctionContainer<T>);
}
}

extension FunctionContainerValueCopy<$R, $Out, T>
on ObjectCopyWith<$R, FunctionContainer<T>, $Out> {
FunctionContainerCopyWith<$R, FunctionContainer<T>, $Out, T>
get $asFunctionContainer =>
$base.as((v, t, t2) => _FunctionContainerCopyWithImpl(v, t, t2));
}

abstract class FunctionContainerCopyWith<$R, $In extends FunctionContainer<T>,
$Out, T> implements ClassCopyWith<$R, $In, $Out> {
$R call({String Function(T)? genericFunction});
FunctionContainerCopyWith<$R2, $In, $Out2, T> $chain<$R2, $Out2>(
Then<$Out2, $R2> t);
}

class _FunctionContainerCopyWithImpl<$R, $Out, T>
extends ClassCopyWithBase<$R, FunctionContainer<T>, $Out>
implements FunctionContainerCopyWith<$R, FunctionContainer<T>, $Out, T> {
_FunctionContainerCopyWithImpl(super.value, super.then, super.then2);

@override
late final ClassMapperBase<FunctionContainer> $mapper =
FunctionContainerMapper.ensureInitialized();
@override
$R call({String Function(T)? genericFunction}) => $apply(FieldCopyWithData(
{if (genericFunction != null) #genericFunction: genericFunction}));
@override
FunctionContainer<T> $make(CopyWithData data) =>
FunctionContainer(data.get(#genericFunction, or: $value.genericFunction));

@override
FunctionContainerCopyWith<$R2, FunctionContainer<T>, $Out2, T>
$chain<$R2, $Out2>(Then<$Out2, $R2> t) =>
_FunctionContainerCopyWithImpl($value, $cast, t);
}
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ void initializeMappers() {
p18.BMapper.ensureInitialized();
p18.AssetMapper.ensureInitialized();
p18.NullableGenericsMapper.ensureInitialized();
p18.FunctionContainerMapper.ensureInitialized();
p19.GameMapper.ensureInitialized();
p19.CardGameMapper.ensureInitialized();
p19.PlayerMapper.ensureInitialized();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ class ClassMapperFieldElement extends MapperFieldElement {

@override
late final String staticGetterType = () {
if (resolvedType is FunctionType) {
return 'Function';
}
return parent.parent.prefixedType(resolvedType, resolveBounds: true);
}();

Expand All @@ -59,6 +62,9 @@ class ClassMapperFieldElement extends MapperFieldElement {

@override
late final String staticArgType = () {
if (resolvedType is FunctionType) {
return 'Function';
}
return parent.parent.prefixedType(param?.type ?? resolvedType,
withNullability: false, resolveBounds: true);
}();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,4 +67,10 @@ class IsGenericTypeVisitor extends UnifyingTypeVisitor<bool> {
return type.positionalFields.any((f) => f.type.accept(this)) ||
type.namedFields.any((f) => f.type.accept(this));
}

@override
bool visitFunctionType(FunctionType type) {
return type.returnType.accept(this) ||
type.parameters.any((p) => p.type.accept(this));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,12 @@ extension FieldsExtension<T extends InterfaceMapperElement>
for (var f in fields) {
if (f.needsGetter) {
output.write(
' static ${f.staticGetterType} _\$${f.name}(${element.prefixedClassName} v) => v.${f.name};\n');
' static ${f.staticGetterType} _\$${f.name}(${element.prefixedClassName} v) => ');
if (f.staticGetterType == 'Function') {
output.write('(v as dynamic).${f.name} as Function;\n');
} else {
output.write('v.${f.name};\n');
}
}
if (f.needsArg) {
output.write(
Expand Down
31 changes: 31 additions & 0 deletions packages/dart_mappable_builder/lib/src/mapper_group.dart
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,37 @@ class MapperElementGroup {
return type;
}

if (t is FunctionType) {
var returnType = prefixedType(t.returnType, resolveBounds: resolveBounds);

var typeArgs = '';
if (t.typeFormals.isNotEmpty) {
typeArgs =
'<${t.typeFormals.map((t) => t.name + (t.bound != null ? ' extends ${prefixedType(t.bound!, resolveBounds: resolveBounds)}' : '')).join(', ')}>';
}

var args = t.normalParameterTypes
.map((t) => prefixedType(t, resolveBounds: resolveBounds))
.join(', ');
if (t.optionalParameterTypes.isNotEmpty) {
if (args.isNotEmpty) args += ', ';
args +=
'[${t.optionalParameterTypes.map((t) => prefixedType(t, resolveBounds: resolveBounds)).join(', ')}]';
}
if (t.namedParameterTypes.isNotEmpty) {
if (args.isNotEmpty) args += ', ';
args +=
'{${t.namedParameterTypes.entries.map((e) => '${e.key}: ${prefixedType(e.value, resolveBounds: resolveBounds)}').join(', ')}}';
}

var type = '$returnType Function$typeArgs($args)';
if (withNullability && t.isNullable) {
type += '?';
}

return type;
}

return t.getDisplayString(withNullability: withNullability);
}

Expand Down

0 comments on commit a682900

Please sign in to comment.