-
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
Analyzer awaiting X&F?
where F
is a proper subtype of Future
, behaves weirdly
#54628
Comments
X&F?
where F
is a proper subtype of Future
, behaves weirdX&F?
where F
is a proper subtype of Future
, behaves weirdly
I believe that We might want to change the language specification such that If we do make that change then we would probably want to add a rule saying that if |
I'll argue that's a bug in the specification of "Derives a future type". The intent of that is to figure out whether a type suggests a specific future type, which will be the one flattened by flatten. When you flatten The behavior I'd want for "derives a future type" is:
The point of "derives a future type" is to preserve A type like |
Yes, that's certainly a fair characterization. This is also the reason why I suggested that we might want to change the rules. However, we know that we must be careful in this area.
This is already working just fine. The missing part is that we're now focusing on proper subtypes of Proper (non-bottom) subtypes of Returning to the example, As I see the situation, we could just proceed and generalize 'derives a future type' in order to get a consistent and understandable semantics, and then ask for implementations. However, we don't want introduce breaking changes with respect to anything that isn't in need of improvements. |
I created dart-lang/language#3574 in order to handle a potential specification change. |
@lrhn I'm confused about what you're seeing. I can't reproduce this. What I do see is that the analyzer and the CFE are inconsistent here, but I don't see any unsoundness, and I don't see the analyzer producing void f(int x) {}
void test<X extends Object?>(X x) async {
if (x is F?) f(x); // Is error: Analyzer (type `X & ExtF`)
if (x is F?) f((await x)); // Is error: Analyzer (type `X`)
} This is inconsistent with the CFE, which seems to infer Either of these choices could, I believe, be sound in the sense that they are internally consistent: the Assuming the soundness issue is put to the side, the question then is, which is correct? I believe that the CFE is consistent with the current specification if we expand "derives a Future type" to extension types in the obvious way, right? |
I don't think this is an actual runtime unsoundness. It can't be since the issue is only aimed at the analyzer. If I remember correctly, the "give a It's hard to get a runtime unsoundness with Back to this issue: The analyzer does not appear to infer So what is left is the discrepancy between implementations of flatten( extension type EF<T>(Future<T> _) implements Future<T> {}
void test<X extends Object?>(X o) async {
if (o is EF<int>) {
var v = await o;
v.st<E<int>>(); // Both agree. (And this test would erase an intersection type to the variable)
}
if (o is EF<int>?) {
// Promotion succeeds.
var (Future<int>? _) = o;
var (X _) = o;
// Await it.
var v = await o;
var (X _) = v; // Analyzer accepts, CFE error (int? not assignable to X)
v.st<E<X>>(); // Analyzer accepts, CFE error
var (int? _) = v; // CFE accepts, analyzer error (X not assignable to int?)
v.st<E<int?>>(); // CFE accepts, analyzer error
}
}
extension <T> on T {
void st<X extends E<T>>(){}
}
typedef E<T> = T Function(T); The spec says that The CFE thinks it does, which is why its flatten gives flatten( My position is that the bug is in "derives a future type", and (Which is why I, now, think flatten( |
@lrhn where do you see this? When I test this in the beta release and on current main, I see the analyzer inferring
So it sounds like the analyzer is correctly implementing the current spec, the CFE is divergent. The question then does seem to be whether we want to change the spec. I mentioned this to @eernstg this morning, but this feels to me like a place where we would likely have stayed out of trouble if we had specified this via reference to an existing construct instead of inventing a new "derives a future type" algorithm. Am I correct that what we want "T derives a future type |
My bad, analyzer doesn't say the unsafe Yes, the analyzer is correctly implementing the spec. The spec is meaningless as written, because it doesn't actually say what it intended to say. It's not completely surprising that the CFE actually did what was intended, if they understood the intent and implemented it, and missed that the spec hadn't actually hit the mark.
No. The intent is to see if a type has a So "derives a future type" traverses the type structure just like flatten would, until it finds a type that is implements The idea with it was that for flatten( If Seemed like a great idea at the time. I don't believe in it any more. I suggest we drop the "derives a future type", let flatten( (I also wrote, not because I think I'll ever get it, a fresh design of |
@leafpetersen wrote, and @lrhn responded:
Right, that's the reason why I keep saying that if we're looking for the type It's tempting to say the following:
However, that won't quite work, because So we don't just want to use the (nice, declarative, complete) subtype relationship. The approach we've taken so far in the specification is to refer to types with which That gives rise to the following definition of "derives a future type" (which is exactly what I'm proposing in dart-lang/language#3574):
Granted, this definition is much less declarative. However, the reliance on the subtyping relationship opens a door which is too large, and then we'd still have to say something along these lines in order to avoid introducing irrelevant supertypes (like It's very easy to see that @lrhn mentioned that we might want to get rid of "derives a future type" entirely. I have been thinking about this, too, and I've been thinking about ways to simplify
We could then change it to the following (in 2035 or so):
.. and we could make it a compile-time error to have If we do that then we certainly have to introduce a rule about " However, I did not change |
Thanks, that's a very useful example for me to understand why we're specifying this separately from subtyping. |
Issue #54628 raised the issue that the static analysis of `await e` behaves in unexpected ways when the static type of `e` is a subtype of a future related type, rather than just a plain `Future<T>` or `FutureOr<T>` for some `T`. This CL adds a test that assumes the update to the language specification which is performed by the PR dart-lang/language#3574. Hence, this CL should ONLY BE LANDED if and when that PR is landed. Change-Id: Ib8acb77e24ffbceb0b4034d3b23a0f4fef8e3d1c Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/354020 Reviewed-by: Lasse Nielsen <[email protected]> Commit-Queue: Erik Ernst <[email protected]>
Example:
This shows both with an extension type and a class implementing
Future<int>
, so it's not specific to extenstion types.The promoted type of
x
isF?
, whereF
is a proper subtype ofFuture<int>
.Awaiting that should give an
int?
, but analyzer says it's anint
. (Comment out theString
line, and the code will run in DartPad and give anull
result from theint
-typed expression.)Further, if I remove the
()
from thest
invocation, making it a tear-off which should have the same parameter requirements as a call, it seems the type parameters are not checked at all.If I change the type
F
to beFuture<int>
directly, so the promoted type isFuture<int>?
, the type ofawait
becomesint?
, and the two tear-offs get checked again. (Which is why I'm guessing the issues are related.)The text was updated successfully, but these errors were encountered: