Skip to content

Downwards inference of yield* is underspecified #4672

Description

@stereotype441

How should we expect this code to be analyzed?

dynamic f() sync* {
  yield* g()..foo();
}

T g<T>() => throw 'T=$T';

main() {
  for (var x in f()) {
    print('x=$x');
  }
}

Currently, the analyzer and CFE both accepts this code without issuing any compile-time errors. The runtime behavior is to print:

Unhandled exception:
T=dynamic
#0      g (file:///home/paulberry/tmp/proj/test.dart:5:13)
#1      f (file:///home/paulberry/tmp/proj/test.dart:2:10)
#2      _SyncStarIterator.moveNext (dart:async-patch/async_patch.dart:595:13)
#3      main (file:///home/paulberry/tmp/proj/test.dart:8:17)
#4      _delayEntrypointInvocation.<anonymous closure> (dart:isolate-patch/isolate_patch.dart:313:19)
#5      _RawReceivePort._handleMessage (dart:isolate-patch/isolate_patch.dart:192:12)

This indicates that the context type being passed to the operand of yield* is either the unknown type (_) or dynamic. Reading through the implementation, I'm pretty certain it's the unknown type.

But resources/type-system/inference.md says:

  • If the enclosing function is marked sync*, then for each yield* e; statement in the block, let S be the inferred type of e, using the local type inference algorithm described below with a typing context of Iterable<K>; let E be the type such that Iterable<E> is a super-interface of S; and update T to be UP(E, T).

Which suggests that the context should be Iterable<K> for some K.

K is defined to be "the typing context for the function body as computed above from the imposed return type schema." It's not entirely clear what this refers to, but the definition of the "imposed return type schema" is as follows:

The return type of the context function type is used at several points during inference. We refer to this type as the imposed return type schema. Inference for each returned or yielded expression in the body of the function literal is done using a context type derived from the imposed return type schema S as follows:

  • If the function expression is neither async nor a generator, then the context type is S.
  • If the function expression is declared async* and S is of the form Stream<S1> for some S1, then the context type is S1.
  • If the function expression is declared sync* and S is of the form Iterable<S1> for some S1, then the context type is S1.
  • Otherwise, without null safety, the context type is FutureOr<flatten(T)> where T is the imposed return type schema; with null safety, the context type is FutureOr<futureValueTypeSchema(S)>.

It's difficult to say whether this spec text is meant to apply to the function f in the code example above. On the one hand, the spec text is clearly talking about a "function expression" (which the function f is not). On the other hand, there are no other mentions of yield* anywhere else in the doc specifying the expected behavior when the yield* appears in a function rather than a function expression.

If we assume the text is intended to apply in this case, then it a literal reading of it produces suggests that the imposed return type schema should be FutureOr<futureValueTypeSchema(_)>, which is FutureOr<_>. But this can't be what we want, because it never makes sense for the operand of yield* to be a future.

To make matters worse, the current behavior (where a return type of dynamic causes the operand of yield* to be inferred with a type context of _) is not covered by any tests. (I found this out by breaking it and seeing that no tests failed).

I'm not 100% sure what the right fix is here; I'm still researching. Filing this issue to document what I've learned so far.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions