Description
Most likely, that this issue is related to dart-lang/language#524 (Sound declaration-site variance).
Tested in DartPad (Dart SDK 2.19.6).
Brief:
Case 1: generic processing is encapsulated in the delegate.
Expected: compiles, runs without errors
Actual: compiles, runs without errors
class EntityDelegate<T extends Base> {
final T Function(dynamic) fromValue;
final Dao<T> Function() createDao;
EntityDelegate(this.fromValue, this.createDao);
Future<List<T>> convertAndSave(List<dynamic> list) async {
final List<T> convertedList = list.map(fromValue).toList();
final Dao<T> dao = createDao();
await dao.insert(convertedList);
return convertedList;
}
}
// ...
final convertedList = await delegate.convertAndSave(sourceList);
Case 2: generic processing is not in the delegate class, delegate's fields are used directly.
Expected: compiles, runs without errors
Actual: compiles, runs with runtime error
Compiles, but throws at runtime:
TypeError: Instance of 'JSArray<Base>': type 'JSArray<Base>' is not a subtype of type 'List<EntityA>
final List<T> convertedList = sourceList.map(delegate.fromValue).toList();
final Dao<T> dao = delegate.createDao();
await dao.insert(convertedList); // fails here
In general, questions are:
- is it a bug or intended behavior?
- If bug - which issue to address?
- If intended behavior - what is the explanation of why type is not inferred?
Full code snippet:
void main() async {
print('code below works');
await testProcessing(worksFine);
print('code below fails. why? 0_o');
await testProcessing(throwsTypeError);
}
// Entities
abstract class Base {}
class EntityA extends Base {
final String valueA;
EntityA(this.valueA);
factory EntityA.fromValue(dynamic value) => EntityA(value);
@override
String toString() => "EntityA($valueA)";
}
class EntityB extends Base {
final String valueB;
EntityB(this.valueB);
factory EntityB.fromValue(dynamic value) => EntityB(value);
@override
String toString() => "EntityB($valueB)";
}
abstract class Dao<T> {
Future<void> insert(List<T> list);
}
class BaseDao<T extends Base> extends Dao<T> {
BaseDao();
@override
Future<void> insert(List<T> list) async {
print("inserting [$T]: $list");
}
}
class EntityDelegate<T extends Base> {
final T Function(dynamic) fromValue;
final Dao<T> Function() createDao;
EntityDelegate(this.fromValue, this.createDao);
Future<List<T>> convertAndSave(List<dynamic> list) async {
final List<T> convertedList = list.map(fromValue).toList();
final Dao<T> dao = createDao();
await dao.insert(convertedList);
return convertedList;
}
}
// Magic below
final List<EntityDelegate> delegates = [
EntityDelegate<EntityA>(EntityA.fromValue, () => BaseDao<EntityA>()),
EntityDelegate<EntityB>(EntityB.fromValue, () => BaseDao<EntityB>()),
];
typedef ProcessCallback<T extends Base> = Future<List<T>> Function(
EntityDelegate<T> delegate,
List<dynamic> sourceList,
);
Future<void> testProcessing(ProcessCallback process) async {
final List<dynamic> list = ["a", "b", "c"];
for (final delegate in delegates) {
final processedList = await process(delegate, list);
print("Processed: $processedList");
}
}
Future<List<T>> worksFine<T extends Base>(
EntityDelegate<T> delegate,
List<dynamic> sourceList,
) async {
final convertedList = await delegate.convertAndSave(sourceList);
print("Converted: $convertedList");
return convertedList;
}
Future<List<T>> throwsTypeError<T extends Base>(
EntityDelegate<T> delegate,
List<dynamic> sourceList,
) async {
final List<T> convertedList = sourceList.map(delegate.fromValue).toList();
final Dao<T> dao = delegate.createDao();
await dao.insert(convertedList); // fails here with:
// TypeError: Instance of 'JSArray<Base>': type 'JSArray<Base>' is not a subtype of type 'List<EntityA>
// or
// TypeError: Instance of 'List<Base>': type 'List<Base>' is not a subtype of type 'List<EntityA>
print("Converted: $convertedList");
return convertedList;
}
Related errors:
TypeError: Instance of 'List': type 'List' is not a subtype of type 'List
TypeError: Instance of 'JSArray': type 'JSArray' is not a subtype of type 'List