Skip to content

Chaining generators produces wrong order of execution in ddc #53062

@yjbanov

Description

@yjbanov

Transfering flutter/flutter#131168 to the Dart SDK:

Steps to reproduce

Open dartpad, select Flutter, paste the code below. Then run and click the "Click" button.

For some reason goo proceeds past the yield before and it is asked to do so. Even replacing await showDialog with simple await Future.delayed(...) doesn't change the result.

It works correctly in pure Dart.

Expected results

foo START
goo START
Locked!
foo obj HERE
Unlocking...
Unlocked!
foo obj THERE
Creating operation...
foo obj HERE
Signing...
Signed!
foo obj THERE
Handling operation...
Handled!
goo END
foo END

Actual results

foo START
goo START
Locked!
foo obj HERE
Creating operation...
Unlocking...
foo obj THERE
foo obj HERE
Unlocked!
Signing...
Signed!
foo obj THERE
Handling operation...
Handled!
goo END
foo END

Code sample

Wrong order in Flutter
import 'package:flutter/material.dart';

const Color darkBlue = Color.fromARGB(255, 18, 32, 47);

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData.dark().copyWith(
        scaffoldBackgroundColor: darkBlue,
      ),
      debugShowCheckedModeBanner: false,
      home: Scaffold(
        body: Center(
          child: MyWidget(),
        ),
      ),
    );
  }
}

class MyWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return TextButton(
      child: const Text('Click'),
      onPressed: () async {
        await for (var obj in foo()) {
          if (obj is LockedError) {
            print('Unlocking...');
            //await Future.delayed(const Duration(seconds: 1));
            await showDialog(
              context: context,
              builder: (_) => AlertDialog(
                title: const Text('Unlock'),
                actions: [
                  TextButton(
                    child: const Text('Ok'),
                    onPressed: () => Navigator.of(context).pop()
                  )
                ],
              ),
            );
            print('Unlocked!');
          } else if (obj is Operation) {
            print('Signing...');
            await showDialog(
              context: context,
              builder: (_) => AlertDialog(
                title: const Text('Sign operation'),
                actions: [
                  TextButton(
                    child: const Text('Ok'),
                    onPressed: () => Navigator.of(context).pop()
                  )
                ],
              ),
            );
            print('Signed!');
          }
        }
      }
    );
  }
}

Stream<Object> foo() async* {
  print('foo START');
  await for (var obj in goo()) {
    print('foo obj HERE');
    yield obj;
    print('foo obj THERE');
  }
  print('foo END');
}

Stream<Object> goo() async* {
  print('goo START');
  
  var locked = true;
  if (locked) {
    print('Locked!');
    yield LockedError();
  }
  
  print('Creating operation...');
  
  var op = Operation();
  yield op;
  
  print('Handling operation...');
  await Future.delayed(const Duration(seconds: 1));
  print('Handled!');
  
  print('goo END');
}

class LockedError {}

class Operation {}
Correct order in pure Dart
void main() async {
  await for (var obj in foo()) {
    if (obj is LockedError) {
      print('Unlocking...');
      await Future.delayed(const Duration(seconds: 1));
      print('Unlocked!');
    } else if (obj is Operation) {
      print('Signing...');
      await Future.delayed(const Duration(seconds: 1));
      print('Signed!');
    }
  }
}

Stream<Object> foo() async* {
  print('foo START');
  await for (var obj in goo()) {
    print('foo obj HERE');
    yield obj;
    print('foo obj THERE');
  }
  print('foo END');
}

Stream<Object> goo() async* {
  print('goo START');
  
  var locked = true;
  if (locked) {
    print('Locked!');
    yield LockedError();
  }
  
  print('Creating operation...');
  
  var op = Operation();
  yield op;
  
  print('Handling operation...');
  await Future.delayed(const Duration(seconds: 1));
  print('Handled!');
  
  print('goo END');
}

class LockedError {}

class Operation {}

Screenshots or Video

Screenshots / Video demonstration

[Upload media here]

Logs

Logs
[Paste your logs here]

Flutter Doctor output

Doctor output
Doctor summary (to see all details, run flutter doctor -v):
[√] Flutter (Channel stable, 3.10.2, on Microsoft Windows [Version 10.0.19045.3208], locale en-US)
[√] Windows Version (Installed version of Windows is version 10 or higher)
[√] Android toolchain - develop for Android devices (Android SDK version 30.0.1)
[√] Chrome - develop for the web
[√] Visual Studio - develop for Windows (Visual Studio Community 2019 16.11.9)  
[√] Android Studio (version 4.0)
[√] VS Code (version 1.80.1)
[√] Connected device (3 available)
[√] Network resources

• No issues found!

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2A bug or feature request we're likely to work onarea-web-jsIssues related to JavaScript support for Dart Web, including DDC, dart2js, and JS interop.dart2js-ddc-discrepancyWhen dev and production compilations have different semanticsdev-compiler-asyncweb-dev-compiler

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions