schultek / dart_mappable

Improved json serialization and data classes with full support for generics, inheritance, customization and more.
https://pub.dev/packages/dart_mappable
MIT License
164 stars 23 forks source link

Bug: Build Runner Generates Incorrect .mapper File With Nullable Generics #220

Closed gabrielmcreynolds closed 1 month ago

gabrielmcreynolds commented 3 months ago

When there is nullable generics the build runner is generating a .mapper file that contains an error. Below is a minimally reproducable example:

// nullable_generics.dart
import 'package:dart_mappable/dart_mappable.dart';

part 'nullable_generics.mapper.dart';

@MappableClass()
// in real-world it is T extends AnotherClass but using the default Object for simplicity here
class NullableGenerics<T extends Object> with NullableGenericsMappable<T> {
  final T? value;

  const NullableGenerics({required this.value});
}
nullable_generics.mapper.dart ```dart // coverage:ignore-file // GENERATED CODE - DO NOT MODIFY BY HAND // ignore_for_file: type=lint // ignore_for_file: unused_element, unnecessary_cast, override_on_non_overriding_member // ignore_for_file: strict_raw_type, inference_failure_on_untyped_parameter part of 'nullable_generics.dart'; class NullableGenericsMapper extends ClassMapperBase { NullableGenericsMapper._(); static NullableGenericsMapper? _instance; static NullableGenericsMapper ensureInitialized() { if (_instance == null) { MapperBase.addType(); MapperContainer.globals.use(_instance = NullableGenericsMapper._()); } return _instance!; } @override final String id = 'NullableGenerics'; @override Function get typeFactory => (f) => f>(); static Object _$value(NullableGenerics v) => v.value; static dynamic _arg$value(f) => f(); static const Field _f$value = Field('value', _$value, arg: _arg$value); @override final MappableFields fields = const { #value: _f$value, }; static NullableGenerics _instantiate(DecodingData data) { return NullableGenerics(value: data.dec(_f$value)); } @override final Function instantiate = _instantiate; static NullableGenerics fromMap( Map map) { return ensureInitialized().decodeMap>(map); } static NullableGenerics fromJson(String json) { return ensureInitialized().decodeJson>(json); } } mixin NullableGenericsMappable { String toJson() { return NullableGenericsMapper.ensureInitialized() .encodeJson>(this as NullableGenerics); } Map toMap() { return NullableGenericsMapper.ensureInitialized() .encodeMap>(this as NullableGenerics); } NullableGenericsCopyWith, NullableGenerics, NullableGenerics, T> get copyWith => _NullableGenericsCopyWithImpl( this as NullableGenerics, $identity, $identity); @override String toString() { return NullableGenericsMapper.ensureInitialized() .stringifyValue(this as NullableGenerics); } @override bool operator ==(Object other) { return NullableGenericsMapper.ensureInitialized() .equalsValue(this as NullableGenerics, other); } @override int get hashCode { return NullableGenericsMapper.ensureInitialized() .hashValue(this as NullableGenerics); } } extension NullableGenericsValueCopy<$R, $Out, T extends Object> on ObjectCopyWith<$R, NullableGenerics, $Out> { NullableGenericsCopyWith<$R, NullableGenerics, $Out, T> get $asNullableGenerics => $base.as((v, t, t2) => _NullableGenericsCopyWithImpl(v, t, t2)); } abstract class NullableGenericsCopyWith<$R, $In extends NullableGenerics, $Out, T extends Object> implements ClassCopyWith<$R, $In, $Out> { $R call({T? value}); NullableGenericsCopyWith<$R2, $In, $Out2, T> $chain<$R2, $Out2>( Then<$Out2, $R2> t); } class _NullableGenericsCopyWithImpl<$R, $Out, T extends Object> extends ClassCopyWithBase<$R, NullableGenerics, $Out> implements NullableGenericsCopyWith<$R, NullableGenerics, $Out, T> { _NullableGenericsCopyWithImpl(super.value, super.then, super.then2); @override late final ClassMapperBase $mapper = NullableGenericsMapper.ensureInitialized(); @override $R call({Object? value = $none}) => $apply(FieldCopyWithData({if (value != $none) #value: value})); @override NullableGenerics $make(CopyWithData data) => NullableGenerics(value: data.get(#value, or: $value.value)); @override NullableGenericsCopyWith<$R2, NullableGenerics, $Out2, T> $chain<$R2, $Out2>(Then<$Out2, $R2> t) => _NullableGenericsCopyWithImpl($value, $cast, t); } ```

The error being thrown is

A value of type 'Object?' can't be returned from the method '_$value' because it has a return type of 'Object'.

I'm assuming this is coming from the T extends Object line and it is just not checking if the generic type is nullable.

nathan-dd commented 1 month ago

I have the exact same issue! I think this is a reasonable and rather common pattern, too.

Is this a regression? @gabrielmcreynolds did you find a workaround?

nathan-dd commented 1 month ago

Confirmed not working at least from 4.1.0 to 4.2.3.

schultek commented 1 month ago

Indeed a bug, will work on a fix.

gabrielmcreynolds commented 1 month ago

I have the exact same issue! I think this is a reasonable and rather common pattern, too.

Is this a regression? @gabrielmcreynolds did you find a workaround?

No, I was not able to find a work around.

schultek commented 1 month ago

Fixed in v4.3.0