diff --git a/packages/dart_mappable/lib/src/mappers/simple_mapper.dart b/packages/dart_mappable/lib/src/mappers/simple_mapper.dart index 25fe56dd..9f310adb 100644 --- a/packages/dart_mappable/lib/src/mappers/simple_mapper.dart +++ b/packages/dart_mappable/lib/src/mappers/simple_mapper.dart @@ -61,6 +61,8 @@ abstract class SimpleMapper1Bounded /// {@category Custom Mappers} abstract class SimpleMapper1 extends SimpleMapper1Bounded { + const SimpleMapper1(); + /// Override as `MyClass decode(Object value)` @override T decode(Object value); @@ -104,6 +106,8 @@ abstract class SimpleMapper2Bounded /// {@category Custom Mappers} abstract class SimpleMapper2 extends SimpleMapper2Bounded { + const SimpleMapper2(); + /// Override as `MyClass decode(Object value)` @override T decode(Object value); diff --git a/packages/dart_mappable/test/generics/generics_test.dart b/packages/dart_mappable/test/generics/generics_test.dart index 257cdac9..12d11440 100644 --- a/packages/dart_mappable/test/generics/generics_test.dart +++ b/packages/dart_mappable/test/generics/generics_test.dart @@ -93,6 +93,13 @@ class NullableGenerics with NullableGenericsMappable { const NullableGenerics({required this.value}); } +@MappableClass() +class FunctionContainer with FunctionContainerMappable { + String Function(T) genericFunction; + + FunctionContainer(this.genericFunction); +} + void main() { group('Generic classes', () { test('Should encode generic objects', () { @@ -205,5 +212,12 @@ void main() { isNull, ); }); + + test('Should generate generic function field', () { + expect( + FunctionContainer((int a) => a.toString()).toString(), + equals('FunctionContainer(genericFunction: Closure: (int) => String)'), + ); + }); }); } diff --git a/packages/dart_mappable/test/generics/generics_test.mapper.dart b/packages/dart_mappable/test/generics/generics_test.mapper.dart index 02dc4065..3f5b977a 100644 --- a/packages/dart_mappable/test/generics/generics_test.mapper.dart +++ b/packages/dart_mappable/test/generics/generics_test.mapper.dart @@ -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 { + 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 => (f) => f>(); + + static Function _$genericFunction(FunctionContainer v) => + (v as dynamic).genericFunction as Function; + static dynamic _arg$genericFunction(f) => f(); + static const Field _f$genericFunction = + Field('genericFunction', _$genericFunction, arg: _arg$genericFunction); + + @override + final MappableFields fields = const { + #genericFunction: _f$genericFunction, + }; + + static FunctionContainer _instantiate(DecodingData data) { + return FunctionContainer(data.dec(_f$genericFunction)); + } + + @override + final Function instantiate = _instantiate; + + static FunctionContainer fromMap(Map map) { + return ensureInitialized().decodeMap>(map); + } + + static FunctionContainer fromJson(String json) { + return ensureInitialized().decodeJson>(json); + } +} + +mixin FunctionContainerMappable { + String toJson() { + return FunctionContainerMapper.ensureInitialized() + .encodeJson>(this as FunctionContainer); + } + + Map toMap() { + return FunctionContainerMapper.ensureInitialized() + .encodeMap>(this as FunctionContainer); + } + + FunctionContainerCopyWith, FunctionContainer, + FunctionContainer, T> + get copyWith => _FunctionContainerCopyWithImpl( + this as FunctionContainer, $identity, $identity); + @override + String toString() { + return FunctionContainerMapper.ensureInitialized() + .stringifyValue(this as FunctionContainer); + } + + @override + bool operator ==(Object other) { + return FunctionContainerMapper.ensureInitialized() + .equalsValue(this as FunctionContainer, other); + } + + @override + int get hashCode { + return FunctionContainerMapper.ensureInitialized() + .hashValue(this as FunctionContainer); + } +} + +extension FunctionContainerValueCopy<$R, $Out, T> + on ObjectCopyWith<$R, FunctionContainer, $Out> { + FunctionContainerCopyWith<$R, FunctionContainer, $Out, T> + get $asFunctionContainer => + $base.as((v, t, t2) => _FunctionContainerCopyWithImpl(v, t, t2)); +} + +abstract class FunctionContainerCopyWith<$R, $In extends FunctionContainer, + $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, $Out> + implements FunctionContainerCopyWith<$R, FunctionContainer, $Out, T> { + _FunctionContainerCopyWithImpl(super.value, super.then, super.then2); + + @override + late final ClassMapperBase $mapper = + FunctionContainerMapper.ensureInitialized(); + @override + $R call({String Function(T)? genericFunction}) => $apply(FieldCopyWithData( + {if (genericFunction != null) #genericFunction: genericFunction})); + @override + FunctionContainer $make(CopyWithData data) => + FunctionContainer(data.get(#genericFunction, or: $value.genericFunction)); + + @override + FunctionContainerCopyWith<$R2, FunctionContainer, $Out2, T> + $chain<$R2, $Out2>(Then<$Out2, $R2> t) => + _FunctionContainerCopyWithImpl($value, $cast, t); +} diff --git a/packages/dart_mappable/test/initializer/init_package_test.init.dart b/packages/dart_mappable/test/initializer/init_package_test.init.dart index 3d2010ed..9a13e9e1 100644 --- a/packages/dart_mappable/test/initializer/init_package_test.init.dart +++ b/packages/dart_mappable/test/initializer/init_package_test.init.dart @@ -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(); diff --git a/packages/dart_mappable_builder/lib/src/elements/field/class_mapper_field_element.dart b/packages/dart_mappable_builder/lib/src/elements/field/class_mapper_field_element.dart index 66d05d89..d43f762a 100644 --- a/packages/dart_mappable_builder/lib/src/elements/field/class_mapper_field_element.dart +++ b/packages/dart_mappable_builder/lib/src/elements/field/class_mapper_field_element.dart @@ -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); }(); @@ -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); }(); diff --git a/packages/dart_mappable_builder/lib/src/elements/field/mapper_field_element.dart b/packages/dart_mappable_builder/lib/src/elements/field/mapper_field_element.dart index 3f3c1e51..98ab36b1 100644 --- a/packages/dart_mappable_builder/lib/src/elements/field/mapper_field_element.dart +++ b/packages/dart_mappable_builder/lib/src/elements/field/mapper_field_element.dart @@ -67,4 +67,10 @@ class IsGenericTypeVisitor extends UnifyingTypeVisitor { 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)); + } } diff --git a/packages/dart_mappable_builder/lib/src/generators/extensions/fields_extension.dart b/packages/dart_mappable_builder/lib/src/generators/extensions/fields_extension.dart index 4e56d82b..14428ceb 100644 --- a/packages/dart_mappable_builder/lib/src/generators/extensions/fields_extension.dart +++ b/packages/dart_mappable_builder/lib/src/generators/extensions/fields_extension.dart @@ -9,7 +9,12 @@ extension FieldsExtension 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( diff --git a/packages/dart_mappable_builder/lib/src/mapper_group.dart b/packages/dart_mappable_builder/lib/src/mapper_group.dart index a9a6812d..851e9b5d 100644 --- a/packages/dart_mappable_builder/lib/src/mapper_group.dart +++ b/packages/dart_mappable_builder/lib/src/mapper_group.dart @@ -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); }