Comments (1)
Check out this issue about "contravariant members".
The core issue here is that Fn<T>
is contravariant in T
(e.g., Fn<num>
is a subtype of Fn<int>
, not vice versa), and Fn<T>
is the return type of the getter done
in the declaration of A<T>
.
This means that T
occurs in a non-covariant position in the signature of a member of A<T>
, which is exactly what it takes to get the behavior that you're reporting.
Dart uses dynamic type checks to enforce soundness of the heap (that is, roughly: no variable has a value that contradicts its declared type), and these "contravariant members" are particularly prone to cause failures in such a run-time type check.
(You can vote for dart-lang/linter#4111 if you wish to help getting support for detecting when a member is contravariant in this sense, such that you can make sure you don't have any of them.)
Here is a minimal example showing such a run-time failure:
class A<X> {
void Function(X) fun;
A(this.fun);
}
void main() {
A<num> a = A<int>((int i) { print(i.isEven); });
a.fun; // Throws. We don't even have to call it!
}
However, if you insist that you want to have a contravariant member then you can still make it statically type safe. This means that we get a compile-time error at the location where the situation is created that causes the covariance problem to arise.
This approach relies on a feature which is still experimental, so you need to provide an option when running tools:
// Use option `--enable-experiment=variance`.
void main() {
A<Object?> a = B<Data>(); // Compile-time error!
a.done(Data());
}
typedef Fn<T> = void Function(T a);
abstract class A<inout T> {
Fn<T> get done;
}
class B<inout T> extends A<T> {
B();
@override
Fn<T> get done => (T a) => print(a);
}
class Data {}
The compile-time error in main
ensures that we won't have a reference to an object of type B<Data>
whose static type is A<Object?>
. The reason for this is that B<Data>
is simply not a subtype of A<Object?>
any more (because of the modifier inout
on the type variables).
You can change the declaration to A<Data> a = B<Data>();
which is accepted with no errors, and then you won't have the run-time error.
However, as you can see, you also have to give up on the ability to forget that the actual type argument is Data
, which is exactly the point: In order to make the invocation of a.done(...)
type safe, you must remember that this function needs an argument of type Data
, and if you're allowed to think that any Object?
will do then there is no way we can avoid performing the type check at run time and potentially have the run-time failure.
You mention that the following variant is more forgiving:
void main() {
A<Object?> a = B<Data>();
a.done(Data()); // No compile-time error, succeeds at run time.
a.done(false); // No compile-time error, throws at run time.
}
typedef Fn<T> = void Function(T a);
abstract class A<T> {
void done(T a);
}
class B<T> extends A<T> {
B();
@override
void done(T a) => print(a);
}
class Data {}
The reason why this variant will run successfully (until we reach a.done(false)
, at least!) is that it does not obtain an object which is mistyped at any point in time (that is, we maintain soundness at all times).
In particular, we can check dynamically that the argument passed to a.done(Data())
has the required type (it has type Data
, as required by the actual value of a
), and then we can proceed without throwing.
At a.done(false)
we perform the same dynamic type check, but it fails and the invocation throws.
In contrast, the expression a.done
in the original version of this example evaluated to obtain a function object of type void Function(Data)
, but the static type of a.done
was void Function(Object?)
. That's a soundness violation because void Function(Data)
is not a subtype of void Function(Object?)
. When we have a soundness violation we will have a run-time type error, period.
So you never take the next step and try to call that function object. So it doesn't help that you might pass an argument like Data()
that would have satisfied the function object, because we don't even try to call the function object in the situation where the function object itself is "bad".
Note that you can combine the two approaches if it is important for you to use separate function objects rather than instance methods:
void main() {
A<Object?> a = B<Data>();
a.done(Data());
}
typedef Fn<T> = void Function(T a);
abstract class A<T> {
Fn<T> get _done;
void done(T a) => _done(a);
}
class B<T> extends A<T> {
B();
@override
Fn<T> get _done => (T a) => print(a);
}
class Data {}
The invocation of _done(a)
will take place because the static type of _done
inside the body of the class is void Function(T)
, and there's nothing unsafe about evaluating _done
in this context. (In other words, no member is "contravariant" when used from inside the class itself). The dynamic type check will now take place at the invocation of the instance method done
, and the invocation of the function object relies on the fact that the type has already been checked, and it just works. The price you'll have to pay in order to get "the best of both words" is that it is more costly at run time to call two functions than it is to call just one; YMMV.
I'll close this issue because it is all working as specified.
from sdk.
Related Issues (20)
- Type inference for abstract classes with recurring generics can't be inferred HOT 10
- The front end is not happy about wrong constructor declaration
- The front end is not happy about wrong extension type constructor declaration
- pkg/analyzer/test/src/dart/resolution/augmented_invocation_test fails with RuntimeError on analyzer-asserts-win bot HOT 2
- Code completion suggestions are irrelevant / context unaware HOT 2
- [Wildcard Variables] Language Tests in `tests/language` HOT 5
- [Wildcard Variables] All tests in `tests/language` and co19 passing.
- [Wildcard Variables] Experiment Flag HOT 3
- [Wildcard Variables] CFE Implementation
- [CP] Cherry-Pick "[cfe] Ensure default values in synthesized function nodes" HOT 6
- [vm] use_code_comments_flag_test timing out on vm-aot-linux-release-simarm_x64 HOT 1
- [analysis_server] Non-const constructors aren't suggested in a const context HOT 2
- Move analyzer test utilities to analyzer_utilities package HOT 3
- [Wildcard Variables] Feature Specification HOT 6
- Problems with moving comments when automatically moving child arg to last parameter position of widget constructor
- Unreachable code during SelectRepresentations HOT 1
- "Stop on uncaught exceptions" sometimes stops on caught exceptions HOT 4
- Improve thrown `FormatException` from `int.parse` when there is an "invalid radix-n" number HOT 1
- Very poor JIT performance with const Map<Type, ..> HOT 7
- DateTime.fromMillisecondsSinceEpoch constructor returns invalid time HOT 2
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from sdk.