Skip to content
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

A value of type 'T & Future<int>' can't be returned from the function 'i' because it has a return type of 'Future<int>'. #47053

Closed
Cyp opened this issue Aug 27, 2021 · 10 comments
Labels
area-meta Cross-cutting, high-level issues (for tracking many other implementation issues, ...). implementation Track the implementation of a specific feature (use on area-meta issue, not issues for each tool) type-bug Incorrect behavior (everything from a crash to more subtle misbehavior)

Comments

@Cyp
Copy link

Cyp commented Aug 27, 2021

[Edit by eernstg: This comment contains a brief summary of the situation, and about what we need to change. Subtasks: #47056, #47057]

Originally opened here: #58492

To Reproduce

  • No error.
Future<int> h<T>(T x, Future<int> y) async => x is Future<int> ? x : y;

  • Spurious error, despite being logically identical to h!
    Since a value of type T & Future<int> does have the required type int | Future<int>, the error message is inconsistent.
    Doing return x as Future<int>; compiles, but (correctly) warns about an unneccesary cast.
Future<int> i<T>(T x, Future<int> y) async {
  if (x is Future<int>) {
    return x; // error: "A value of type 'T & Future<int>' can't be returned from the function 'i' because it has a return type of 'Future<int>'."
    return x as Future<int>; // warning: "Unnecessary cast."
  }
  return y;
}

  • No error, despite being exactly identical to i except for async.
Future<int> j<T>(T x, Future<int> y) {
  if (x is Future<int>) {
    return x;
  }
  return y;
}

Expected behavior
No warnings or errors except for the valid warning: "Unnecessary cast.".

Additional context
Using dart 2.13.4.

@eernstg
Copy link
Member

eernstg commented Aug 27, 2021

The relevant specification section is this section in the null-safety specification, and the reason why the given situation is an error is that flatten(T & Future<int>) isn't int, it is just T & Future<int>.

So it is working as specified.

But we might want to consider changing flatten() such that it does handle that case as well.

@eernstg eernstg transferred this issue from dart-lang/sdk Aug 27, 2021
@eernstg
Copy link
Member

eernstg commented Aug 27, 2021

@leafpetersen, WDYT?

@Cyp
Copy link
Author

Cyp commented Aug 27, 2021

If flatten “removes” Future, then flatten(T & Future<int>)T & int would make sense to me (don't know much about the specification though). I think it should work both for a function that returns Future<T> or one that returns Future<int>.

@eernstg
Copy link
Member

eernstg commented Aug 27, 2021

flatten(T & Future<int>) can't be T & int, because there is no reason to believe that we can await the future and obtain an instance of a type that is a subtype of T. But it could be int.

@Cyp
Copy link
Author

Cyp commented Aug 27, 2021

I mean, here's a test case which currently works, and should maybe continue to work:

Future<T> k<T>(T x, Future<T> y) async {
  if (x is Future<int>) {
    return x;
  }
  return y;
}

Although thinking about it a bit more, if T is Future<int>, that would mean returning a Future<Future<int>>, which I guess is a bit weird, so I'm not really sure that the case matters.

@eernstg
Copy link
Member

eernstg commented Aug 27, 2021

That one works because there is no promotion (because Future<int> is not a subtype of T): return x; is type correct in a function with an async body and return type Future<T> because it has type T and flatten(T) is again T, which is a subtype of the future value type T; further down, return y; is type correct because y has type Future<T>, and flatten(Future<T>) is T, which is again a subtype of T.

@lrhn
Copy link
Member

lrhn commented Aug 27, 2021

If we just removed the implicit await in async return statements (#870) then this would be much easier. 😁

As I read the definition of flatten, flatten(T & Future<int>) should be int.

Since T & Future<int> <: Future (aka. Future<Object?>), that is the item to apply. We just need to find a most specific type S such that X & Future<int> <: Future<S>. I think int should satisfy that.

Let R be any other type such that X & Future<int> <: Future<R>.
The rule for intersection types is that T1 & T2 <: T3 iff T1 <: T3 or T2 <: T3.
Here the type variable has no bound, so no Future<R> is a supertype of it, so if Future<R> is a supertype of the intersection, it's a supertype of Future<int>. Ergo, Future<int> is the most specific type of future which is a supertype of the intersection type, and therefore the result of flatten is int.

Now, it might be that our algorithms won't find that solution because we don't look for it the right way, or forgot to account for type variable intersection types. (Or maybe I'm missing something).

@eernstg
Copy link
Member

eernstg commented Aug 27, 2021

You're right! -- flatten does have a subsumption step such that we go from T & Future<int> to Future<int> and then to int. So this is an issue on the tools after all.

@eernstg eernstg transferred this issue from dart-lang/language Aug 31, 2021
@eernstg eernstg added area-meta Cross-cutting, high-level issues (for tracking many other implementation issues, ...). type-bug Incorrect behavior (everything from a crash to more subtle misbehavior) labels Aug 31, 2021
@eernstg
Copy link
Member

eernstg commented Aug 31, 2021

I transferred this issue to the SDK repository because it shows that there is a need to adjust the behavior of the CFE and the analyzer. Here is a summary of the situation:

The following example program shows that return x; where x has static type T & Future<int>, is rejected by current implementations (both dart and dartanalyzer from 8f9113d):

Future<int> f<T>(T x, Future<int> y) async {
  if (x is Future<int>) return x;
  return y;
}

void main() => f(0, Future<int>.value(1));

with the following error messages:

> dartanalyzer n037.dart
ERROR|COMPILE_TIME_ERROR|RETURN_OF_INVALID_TYPE|/usr/local/google/home/eernst/lang/dart/scratch/202108/n037.dart|2|32|1|A value of type 'T & Future<int>' can't be returned from the function 'f' because it has a return type of 'Future<int>'.
> dart n037.dart
n037.dart:2:32: Error: A value of type 'T' can't be returned from an async function with return type 'Future<int>'.
 - 'Future' is from 'dart:async'.
  if (x is Future<int>) return x;
                               ^

The function flatten basically eliminates one layer of Future from a type, but in this case we need flatten(T & Future<int>), and this should yield int.

The future value type of the enclosing function is int as well, and int <: int, so return x; is not an error, cf. this section.

It looks like flatten is not computed correctly in this case.

@eernstg eernstg added implementation Track the implementation of a specific feature (use on area-meta issue, not issues for each tool) and removed implementation Track the implementation of a specific feature (use on area-meta issue, not issues for each tool) labels Aug 31, 2021
@eernstg eernstg added the implementation Track the implementation of a specific feature (use on area-meta issue, not issues for each tool) label Oct 4, 2021
@eernstg
Copy link
Member

eernstg commented Oct 4, 2021

Closing: At this point the example is working and the subtasks (#47056, #47057) have been completed.

@eernstg eernstg closed this as completed Oct 4, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-meta Cross-cutting, high-level issues (for tracking many other implementation issues, ...). implementation Track the implementation of a specific feature (use on area-meta issue, not issues for each tool) type-bug Incorrect behavior (everything from a crash to more subtle misbehavior)
Projects
None yet
Development

No branches or pull requests

3 participants