Skip to content

Generic type in not inferred when used outside of class that is generalized #52249

Closed
@MichaelDark

Description

@MichaelDark

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    closed-as-intendedClosed as the reported issue is expected behaviortype-questionA question about expected behavior or functionality

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions