- 
                Notifications
    You must be signed in to change notification settings 
- Fork 227
Description
The analyzer and front end disagree on how to convert the context of a function expression into the context for the operands of return and yield inside the function expression, and they both disagree with the spec.
Paraphrasing from here, and ignoring legacy logic, the spec says to do this:
- Let Kbe the context of the function expression.
- If the function is sync(not a generator and not asynchronous), then the context for operands ofreturnin the function expression isK.
- If the function is async*andKisStream<S>for someS, then the context for operands ofyieldin the function expression isS.
- Otherwise, if the function is sync*andKisIterable<S>for someS, then the context for operands ofyieldin the function expression isS.
- Otherwise, the context for operands of return/yieldin the function expression isFutureOr<futureValueTypeSchema(K)>(wherefutureValueTypeSchemais defined here).
The analyzer behavior differs from the spec in the following ways:
- If Kis_ordynamic, then the context for operands ofreturnandyieldin the function expression is_, regardless of the function expression's async/generator marker.
- Matching of StreamandIterableis done by ignoring trailing?s, replacing type parameters with their bounds, and then using "as instance of" semantics (e.g. ifKisT&MyStream?, whereMyStreamextendsStream<int>, then that produces the same result thatK=Stream<int>would).
The front end behavior differs from the spec in the following ways:
- Matching of StreamandIterableis done using "union free" semantics (ignoring trailing?s and unwrappingFutureOr<S>toS), but otherwise requiring a precise match (e.g. ifKisFutureOr<Stream<int>?>?, then that produces the same result thatK=Stream<int>would, but ifKisMyStream, whereMyStreamextendsStream<int>, then that is considered not to match).
- If the function is async*orsync*andKdoesn't matchStream(or, respectively,Iterable), then the context for operands ofyieldin the function expression is_(notFutureOr<futureValueTypeSchema(K)>).
- If the function is async, then the context for operands ofreturnin the function expression iswrapFutureOr(futureValueTypeSchema(K)), wherewrapFutureOris defined as follows:- wrapFutureOr(FutureOr<S>?) = FutureOr<S>?
- wrapFutureOr(FutureOr<S>) = FutureOr<S>
- Otherwise, wrapFutureOr(S) = FutureOr(S)
 
That's a lot of behavioral differences! We should pick a behavior to standardize on, and update spec, CFE, and analyzer to all match.
My gut feeling is that the behavior we want is probably a mixture of all three. Perhaps something like this:
- Let Kbe the context of the function expression.
- If the function is async*:- If unionFree(K)isStream<S>for someS, then the context for operands ofyieldin the function expression isS. Otherwise, it's_.
- Where unionFreeis defined as follows:- unionFree(S?) = unionFree(S)
- unionFree(FutureOr<S>) = unionFree(S)
- Otherwise, unionFree(S) = S.
 
 
- If 
- If the function is sync*:- If unionFree(K)isIterable<S>for someS, then the context for operands ofyieldin the function expression isS. Otherwise, it's_.
 
- If 
- If the function is async:- Let SbefutureValueTypeSchema(K).
- If Sis_ordynamic, then the context for operands ofreturnin the function expression is_.
- Otherwise, it's FutureOr<S>.
 
- Let 
But I think that before deciding for sure, it would be worth doing some investigation to see how breaking this would be.
@dart-lang/language-team any thoughts?