Skip to content

Type inference (Function type) doesn't fail compilation, TypeError thrown at runtime #48738

Closed
@wujek-srujek

Description

@wujek-srujek

Dart version: Dart SDK version: 2.16.2 (stable) (Tue Mar 22 13:15:13 2022 +0100) on "macos_x64"
Error reproducible with the below sample on DartPad.

The type inferer doesn't report a Function typing error and fails at runtime instead. Consider the sample below:

typedef Json = Map<String, dynamic>;

abstract class Entity {}

class EntityMapper<T extends Entity> {
  // Generated with json_serializable.
  final Json Function(T) toJson;
  final T Function(Json) fromJson;

  EntityMapper(this.toJson, this.fromJson);
}

class Repository<T extends Entity> {
  // Provides real data from Firestore.
  final Iterable<T> Function() _dataProvider;
  final EntityMapper<T> mapper;

  Repository(this._dataProvider, this.mapper);

  Iterable<T> findAll() => _dataProvider();
  
//   Json toJson(T entity) => mapper.toJson(entity);
}

class EntityA extends Entity {}

final entityAMapper = EntityMapper<EntityA>(
  (a) => {'name': 'A'},
  (json) => EntityA(),
);

class Processor {
  final Iterable<Repository<Entity>> _repositories;

  Processor(this._repositories);

  void process() {
    for (final repository in _repositories) {
      final entities = repository.findAll();
      final entityJsons = entities.map(repository.mapper.toJson); // <<<
//       final entityJsons = entities.map(repository.toJson);
      for (final entityJson in entityJsons) {
        print(entityJson);
      }
    }
  }
}

void main() {
  final entityARepository = Repository<EntityA>(
    () => [EntityA(), EntityA()],
    entityAMapper,
  );

  Processor([entityARepository]).process();
}
  • There is an Entity superclass.
  • There is a generic EntityMapper which takes functions mapping to/from JSON to instance.
  • There is a mapper instance for each Entity subclass.
  • A generic Repository is parameterized with a data provider and a mapper for a specific Entity subclass.
  • A Processor gets a list of repositories, iterates over them, gets all entities and attempts to convert them to JSON.

The line marked with <<< causes the following failure:

Uncaught Error: TypeError: Closure 'entityAMapper_closure': type '(EntityA) => Map<String, dynamic>' is not a subtype of type '(Entity) => Map<String, dynamic>'

This happens only at runtime and the type inferer / analyzer aren't able to report this issue before that.

When the commented out method Repository.toJson is enabled, which basically just delegates a method call to the mapper, and the Processor is updated by now using the new Repository.toJson method, the code works fine.

  1. Should type inference be able to find the issue with the original code?
  2. Why doesn't the code work in the first place?
  3. Why does simple delegation fix the error?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions