-
Notifications
You must be signed in to change notification settings - Fork 1.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Type inference (Function type) doesn't fail compilation, TypeError thrown at runtime #48738
Comments
Also it is possible to change the code to create the mapper like this: final entityAMapper = EntityMapper<EntityA>(
(Entity a) => {'name': 'A'},
(json) => EntityA(),
); and the code works, but I think it is clearly wrong - the |
The dynamic error arises because Dart uses dynamically checked covariance: void main() {
List<num> xs = <int>[1, 2]; // Accepted, because `List<num> <: List<int>`.
xs.add(1.5); // Checked at run time, will throw because `1.5` does not have type `int`.
} In this particular example the covariance is introduced at The statically safe solution is to introduce statically checked variance, dart-lang/language#524. With statically checked variance we'd need to make several adjustments, including The precise situation where the dynamic error occurs is actually when It looks like the types are aligned, even though it is not statically safe (so we're passing an final entityJsons = (entities as dynamic).map((repository.mapper as dynamic).toJson); The first cast to dynamic ensures that the The main reason why this particular example gives rise to multiple failures during dynamic covariance checks is that there is a class with an instance member whose type is not covariant in the type parameters of the enclosing class, namely This has been recognized from the very beginning as a very error-prone situation, but I guess it wasn't considered particularly frequent back then. We've had proposals about how to deal with that kind of type for a while (e.g., dart-lang/language#297), but the real solution is to support statically checked variance. |
Thanks for the in-depth explanation and the links. I prefer not to use
Have I committed some terrible error with my code? How should it be written in a type-safe way and also work at runtime? |
There are also two questions: why the extra method fixes it, and why the compiler allows final entityAMapper = EntityMapper<EntityA>(
(Entity a) => {'name': 'A'},
(json) => EntityA(),
); Especially the snippet above seems to be wrong statically. |
My assumption was that you do not really have a choice, because If you do have the freedom to avoid using "contravariant instance variables" then you could make the following adjustments: typedef Json = Map<String, dynamic>;
abstract class Entity {}
abstract class EntityMapper<T extends Entity> {
Json toJson(T t);
T fromJson(Json json);
}
class Repository<T extends Entity> {
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 {}
class EntityAMapper extends EntityMapper<EntityA> {
toJson(a) => {'name': 'A'};
fromJson(json) => EntityA();
}
final entityAMapper = EntityAMapper();
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);
for (final entityJson in entityJsons) {
print(entityJson);
}
}
}
}
void main() {
final entityARepository = Repository<EntityA>(
() => [EntityA(), EntityA()],
entityAMapper,
);
Processor([entityARepository]).process();
} About this: final entityAMapper = EntityMapper<EntityA>(
(Entity a) => {'name': 'A'},
(json) => EntityA(),
); The reason why the static analysis permits that in the original example is that The extra method would be this one: class Repository<T extends Entity> {
...
Json toJson(T entity) => mapper.toJson(entity);
} The reason why that method can be passed to |
It sounds like this is currently working as intended, though we may make changes here in the future (declaration-site variance; dart-lang/language#524). |
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:Entity
superclass.EntityMapper
which takes functions mapping to/from JSON to instance.Entity
subclass.Repository
is parameterized with a data provider and a mapper for a specificEntity
subclass.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: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 theProcessor
is updated by now using the newRepository.toJson
method, the code works fine.The text was updated successfully, but these errors were encountered: