-
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
Unsound static type in kernel AST when function type is returned #54200
Comments
This is an (unfortunate) unsoundness inherent in Dart and the reason for why we have the Would it be helpful for you if we marked these nodes directly, as well as having the |
@alexmarkov, here's another perspective on the same topic. I understand that this issue may be mostly about the Kernel code which is emitted for the given example, but I can't help commenting on the language perspective. You might want to keep an eye on sound variance support and possibly also on "non-covariant members". The point is that the declaration of I've been pushing for feedback on this situation: #59050. This is a proposal for a lint which would flag declarations like Alternatively, if you really want to use a return type like // `--enable-experiment=variance`
abstract class A<inout T> { // `in` would actually suffice in this case.
void Function(T) get foo;
}
class B implements A<int> {
void Function(int) get foo => (int x) => print(x);
}
void main() {
A<int> a = B(); // Must be `A<int>`, it's a compile-time error with `A<num>`.
final f = a.foo;
// f(3.14); // Compile-time error.
f(314); // OK.
} With this, Dart doesn't yet support sound variance, but we can emulate invariance using a type alias. So we'll make References (such as typedef Inv<X> = X Function(X);
typedef A<X> = _A<X, Inv<X>>;
abstract class _A<T, Invariance extends Inv<T>> {
void Function(T) get foo;
}
class B implements A<int> {
void Function(int) get foo => (int x) => print(x);
}
void main() {
A<int> a = B();
final f = a.foo;
// f(3.14); // Compile-time error.
f(314); // OK.
} |
@johnniwinther In my pending CL I added a workaround to test if return type of an interface target
I can probably test parent node if it is a reliable / accurate / recommended approach. Maybe something like
But something more explicit in the AST would be more robust (and would not rely on parent pointers which may be removed in future).
Yes, an additional flag on all invocation nodes which can have unsound static type would help. Alternatively, maybe CFE could just replace return type of an invocation node ( |
@eernstg Unfortunately sound variance is not there yet. The code above is a correct Dart code which should be supported by Dart implementations. Hopefully such code would trigger a compile-time error when sound variance arrives and eventually we will be able to remove |
Right, @alexmarkov, you're considering how to deal with a caller-side check (that is, a However, we could still consider going one step in the direction of sound variance without adding it to the language: In the situation where a caller-side check has been performed and didn't throw, you will basically be able to conclude that the given object has an invariant type rather than the dynamically-checked-covariant type that it has according to the surface language. For example, a successful caller-side check could be used to promote an instance of type abstract class A<T> {
void Function(T) get foo;
void Function(T, T) get bar;
}
class B implements A<int> {
void Function(int) get foo => (int x) => print(x);
void Function(int, int) get bar => (int x, int y) => print(x + y);
}
void main() {
A<num> a = B();
a.foo(3.14); // Promote `a` to `A<exactly num>`, or throw.
while (true) {
a.bar(14, 18); // No dynamic checks needed, `a` still has type `A<exactly num>`.
}
} Unfortunately, the test that the run-time type of class A<X> {
void Function(X) foo;
A(this.foo);
}
void main() {
A<num> a = A<int>((num n) {});
a.foo; // Succeeds, in spite of covariance `A<int> <: A<num>`.
} This means that we can't just rush ahead and use the check like void main() {
A<num> a = B();
// Compiler-generated local variable; only the compiler can test for `A<exactly num>`.
var aIsCovariant = a is! A<exactly num>;
a.foo(3.14); // Dynamic check performed if and only if `aIsCovariant`.
while (true) {
a.bar(14, 18); // Dynamic check performed if and only if `aIsCovariant`.
}
} PS: Doesn't the VM already keep track of invariance in some cases? Do we already have much of the machinery needed to do these things? |
I'll update the nodes that provide the unsound static type to use the sound static type (approximation) suggested in dart-lang/language#297. This should hopefully make |
Consider the following code:
Kernel:
CFE inserts
as
check to ensure soundness at run time. However, InstanceGet nodea.{foo::A::foo}{(core::num) → void}
is typed incorrectly - it has(core::num) → void
static type already, but at run time it can actually return(core::int) → void
. This typing is causing problems as TFA currently trusts static types.@johnniwinther Is it possible to correct this static type, or somehow distinguish AST nodes which
getStaticType()
cannot be trusted?The text was updated successfully, but these errors were encountered: