-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Give users more help in solving contravariance issues #33697
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
yeah this problem is really bad and seems to be getting worse. For whatever reason, callbacks like this are becoming more common. We originally did not see this style used much across the code base, and thus expected it to be rarely hit in practice. @leafpetersen has some ideas for fixing this in the type system, so this code is simply accepted (and succeeds with a runtime cast). IMO that's a good solution in the short term (longer term I want to see invariant generics and/or sound co/contravariant generics.) |
Hmm, in general, cases where a type parameter class Foo<T> {
void Function(T) callback;
} is a nasty foot-gun. If we want to avoid the strange runtime error, options appear to be: In Matan's example, (3) would mean a static error here: Response<Context> response = manager.getResponse(); |
Thanks both @jmesserly and @vsmenon for following up! I'd love to see how much work it would be to do |
(1) agree this is too breaking. (2) seems essentially the same as 1? Like we're still disallowing contravariant T, but instead of an error, we're silently making it Object for you ... that seems like it would be really confusing. I have a field (3) seems very breaking as well. We'd need to enforce invariance on those types for runtime casts. It will break because Angular constructs components with I haven't had a chance to chat with Leaf yet, but I think we were thinking something more along the lines of resurrecting a fuzzy arrow type approach: class C<T> {
final Function(T) callback;
C(this.callback);
}
main() {
C<Object> c = new C<int>((int x) => x.abs());
var f = c.callback; // previously fails, now works
// f has type `(?) -> dynamic`
// `?` means we don't know the param type, need to check it before assuming.
f(42); // works
f('hello'); // fails runtime param type check
Function(Object) g = f; // fails runtime cast
Function(int) h = f; // works
c.callback = f; // works
} This would fix most of the examples I think, with no code changes. We could provide a way for users to write these types as well, depending on whether this is a short term hack or not. Long term yes sound variance please. Need to make Angular understand generics :) |
We're working on the generics issue. FWIW, the work-around has been to loosten the expectations to dynamic anyway, so I'm not sure the invariant type will be strictly speaking breaking (or not any more breaking than the current behavior). For example, we advise users to rewrite: class ParentComponent {
String renderName(Animal a) => a.name
}
// <T> is always _dynamic_ in AngularDart today.
class ChildComponent<T> {
@Input()
String Function(T) itemRenderer;
} ... to .. class ParentComponent {
String renderName(Object a) => (a as Animal).name;
} |
Note also an option (4), which is the one that I've recommended, which says that members whose type contains a type variable from the enclosing class in a contravariant or invariant position should be given a static type which is sound (that is, a common supertype of all the possible actual types):
The (*) is there because we might instead want to use a superinterface where For option (3), I'd recommend that we just support invariance as a separate feature. With that, developers would be able to get a safe and informative type for a "contravariant member" when they are ready to give up on covariance (which is perfectly fine for all the members whose type is actually covariant). |
with (4) you wouldn't be able to call the function, though? Unless you pass |
Yes, because we should check that actual arguments satisfy the actual requirements. Relying on a check which guarantees that relationship statically would prevent us from ever putting anything else than I do realize that we get that for free as long as we choose to check the type of an actual argument whose type is covariant (from class or via the Another case where it would be very useful to check against the actual parameter type rather than a statically known |
Bump. @davidmorgan hit this again in
A runtime error occurs when we access |
@jmesserly - as an initial step, perhaps we can improve the runtime error on |
In this particular case I think @davidmorgan hit it in the VM, so DDC would not be enough. |
Good point. Are injecting the cast in kernel or the backends? If the former, we should probably mark it specially. |
The front ends inject the cast (CFE/Analyzer). You can probably tell it's one of these casts already, because it will be an implicit cast to/from the same function type. Marking them would be cleaner though. I don't recall if Kernel does that or not. |
Considering the options mentioned by @vsmenon here, we do have an applicable device today:
The lint
This is essentially dart-lang/language#296. We don't have that, though.
|
See #33119 (comment) for background.
DDC throws with:
Unfortunately this doesn't really explain anything, and because most of Dart is based on covariance, I (incorrectly) thought "WTF, its a
Response<SubContext>
, that's even BETTER thanResponse<Context>
". Of course, that is not the case...In #33119 (comment) specifically, we are statically asking for a
Callback<Response<Context>>
, but at runtime gettingCallback<Response<SubContext>>
, which of course is contravariantly invalid.The text was updated successfully, but these errors were encountered: