-
Notifications
You must be signed in to change notification settings - Fork 213
Consider pushing implicit conversions down #2129
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
Comments
I think number 3 is slightly incorrect.
That's progress! It used to be a dynamic error. With this change, a |
I presume enabling strict casts for dynamics in the analyzer settings will avoid the potential runtime errors introduced by (5), (8), and (9)? I don't like 5 in particular - getting a runtime error for trying to store a value with a type in a variable you explicitly declared with a matching type. Can something like that happen currently? Also out of curiosity, was there a reason why a callable class doesn't act as an actual implementation of the respective Function type? Like |
Yes, I believe all three. |
@Jetz72 Yes, there is a good reason a callable class cannot implement a function type directly. We tried during the Dart 2.0 type system design, and it just broke soundness too much. I don't remember the details (I'm sure some still do), but it was related to function types being contravariant in some of its subtypes, and generics being covariant, and together those could make two different types mutual subtypes and collapse the type hierarchy. Doing implicit |
One reason to stop supporting the fully general approach where a class implementing a method named class C {
void call(C c) {}
} If The other main reason was (as far as I know) that it is possible to optimize primitive function objects. For instance, if a function is a different kind of entity than a regular object then we can get access to the code of the function using a special lookup protocol (for instance, the address of the code could be stored at offset 12 in the heap entity layout). If we constrain ourselves to use exactly the same representation as we do for regular objects then (presumably) the invocation of a function object would be the same thing as an invocation of the |
In dart-lang/sdk#49106 (comment), lrhn@ points out an example of a situation where it would be undesirable to switch to using context type for implicit downcasts: class C {
int operator [](A key) => ...;
void operator []=(B key, int value) { ... }
}
test(C c, dynamic d) {
c[d] += 1;
} This is currently desugared by the CFE to: let final C #t1 = c in let final dynamic #t2 = d in #t1[#t2 as B] = #t1[#t2 as A] + 1; which shows that the downcast type is currently not determined by the context but instead by the assignability requirements implied by compound assignment desugaring. If we make the change that currently seems to be favored in dart-lang/sdk#49106 (change the context type to be the GLB of the two key types), then in the case where A and B have multiple possible maximal lower bounds, we may end up choosing a GLB which is not satisfied by the actual value, yet where the actual value does satisfy both key types. So if we don't want to break this case, we should not use the GLB for downcasts. |
The Dart language currently supports the following kinds of implicit conversions:
dynamic
to some other type via an implicit cast.call
method to a function type, by implicitly tearing off.call
The first conversion only happens on integer literals, and is based on the context type. The other two conversions are currently only performed at sites where the spec demands assignability, and is based on the type being assigned to. The language team is considering changing the behavior so that all implicit conversions are based on context type, and happen at the innermost expression where they're applicable, regardless of assignability constraints. (We would retain the restriction that int-to-double conversion only happens to literals).
Here are some examples to illustrate the user-observable consequences of this change (changes are in bold). (Assume
F
is a function type,f
is an expression of typeF
,fq
is an expression of typeF?
,C
is a callable class with afoo
method of typevoid Function()
and a call method of typeF
,c
is an expression of typeC
,cq
is an expression of typeC?
, andd
is an expression of typedynamic
. Also assume there's a declaration ofObject o = f;
in scope, with declared typeObject
and promoted typeF
):F x = b ? c : f;
will be interpreted asF x = b ? c.call : f;
, which will work. Today it's a compile time error becauseb ? c : f
has static typeObject
, which is not assignable toF
.F x = b ? c : d;
will be interpreted asF x = b ? c.call : (d as F)
, which will work, unlessb
isfalse
andd
's runtime type is notF
. Today this code is accepted, becauseb ? c : d
has static typedynamic
, but it fails at runtime ifb
istrue
, because there is no implicit tearoff ofcall
.F x = b ? "x" : d;
won't change. Today it's interpreted asF x = (b ? "x" : d) as F;
. This will change toF x = b ? ("x" as F) : (d as F);
, which is indistinguishable.o = c;
will be interpreted aso = c.call;
, retaining the promotion ono
. Today this code is accepted, but there is no implicit tearoff ofcall
, so it demoteso
toObject
, which is probably not what the user wants.o = d;
will be interpreted aso = (d as F);
, which retains the promotion ono
, but fails at runtime ifd
's runtime type is notF
. Today this code is interpreted aso = (d as Object);
, which demoteso
.F x = cq ?? f;
won't change. This code is rejected because we don't imply implicit.call
tearoffs to nullable expressions, socq ?? f
has typeObject
, which is not assignable toF
.var x = cq ?? f;
wont' change. This code is accepted and interpreted asObject x = cq ?? f;
(no downcasts or implicit tearoffs).var x = cq ?? d;
will be interpreted asC? x = cq ?? (d as C?)
, which has a more specific type, but will fail at runtime ifcq
isnull
andd
's runtime type is notC?
. Today this code is interpreted asdynamic x = cq ?? d;
, which never fails but has a less specific type.var x = fq ?? d;
will be interpreted asF? x = fq ?? (d as F?)
, which has a more specific type, but will fail at runtime iffq
isnull
andd
's runtime type is notF?
. Today this code is interpreted asdynamic x = fq ?? d;
, which never fails but has a less specific type.F x = fq ?? c;
will be interpreted asF x = fq ?? c.call;
, which will work. Today it's a compile time error becausefq ?? c
has static typeObject
, which is not assignable toF
.var x = fq ?? c
will be interpreted asF x = fq ?? c.call;
, which is more useful. Today it's accepted and interpreted asObject x = fq ?? c
, which doesn't fail, but also leavesx
in a state where it's hard for the user to do anything useful with it, because at runtime it might either be an instance ofC
or a function.F x = c..foo();
will be a compile-time error because it's interpreted asF x = c.call..foo();
, andc.call
has typeF
, which has no methodfoo
. Today it's interpreted asF x = (c..foo()).call;
, which works.F x = (c);
won't change. Today it's interpreted asF x = (c).call;
. This will change toF x = (c.call);
, which is indistinguishable.(Thanks @lrhn for working through most of these examples).
A rough summary of this might be:
x
having a more specific type, which should help reduce dynamism (and catch errors at compile time) in the code that follows.F x = (c..foo()).call;
).The text was updated successfully, but these errors were encountered: