diff --git a/pkg/dev_compiler/lib/src/kernel/compiler_new.dart b/pkg/dev_compiler/lib/src/kernel/compiler_new.dart index b4a30ab99ae..7dd8c679a2b 100644 --- a/pkg/dev_compiler/lib/src/kernel/compiler_new.dart +++ b/pkg/dev_compiler/lib/src/kernel/compiler_new.dart @@ -873,6 +873,7 @@ class LibraryCompiler extends ComputeOnceConstantVisitor final FutureOrNormalizer _futureOrNormalizer; bool _inFunctionExpression = false; + bool _inAsyncExpression = false; /// Returns whether or not [uri] can be hot reloaded. /// @@ -5072,8 +5073,14 @@ class LibraryCompiler extends ComputeOnceConstantVisitor } js_ast.Statement _emitFunctionScopedBody(FunctionNode f) { + var savedInAsyncExpression = _inAsyncExpression; + if (f.asyncMarker != AsyncMarker.Sync) { + _inAsyncExpression = true; + } var jsBody = _visitStatement(f.body!); - return _emitScopedBody(f, jsBody); + var body = _emitScopedBody(f, jsBody); + _inAsyncExpression = savedInAsyncExpression; + return body; } js_ast.Statement _emitScopedBody(FunctionNode f, js_ast.Statement body) { @@ -7732,7 +7739,7 @@ class LibraryCompiler extends ComputeOnceConstantVisitor /// include checks to preserve soundness in the presence of hot reloads at /// runtime. bool _shouldRewriteInvocationWithHotReloadChecks(Member target) => - _inFunctionExpression && + (_inFunctionExpression || _inAsyncExpression) && !_isBuildingSdk && !usesJSInterop(target) && target != _assertInteropMethod; diff --git a/tests/hot_reload/async_call_deleted_top_level_function_ddc/config.json b/tests/hot_reload/async_call_deleted_top_level_function_ddc/config.json new file mode 100644 index 00000000000..6bd1b0002af --- /dev/null +++ b/tests/hot_reload/async_call_deleted_top_level_function_ddc/config.json @@ -0,0 +1,5 @@ +{ + "exclude": [ + "vm" + ] +} \ No newline at end of file diff --git a/tests/hot_reload/async_call_deleted_top_level_function_ddc/main.0.dart b/tests/hot_reload/async_call_deleted_top_level_function_ddc/main.0.dart new file mode 100644 index 00000000000..148e476b61e --- /dev/null +++ b/tests/hot_reload/async_call_deleted_top_level_function_ddc/main.0.dart @@ -0,0 +1,43 @@ +// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'dart:async'; + +import 'package:expect/expect.dart'; +import 'package:expect/async_helper.dart'; +import 'package:reload_test/reload_test_utils.dart'; + +/// Shows the behavior when async code enqueued before a hot restart runs +/// after the restart and tries to interact with code that has been deleted. + +bool setBeforeAwait = false; +bool setAfterAwait = false; + +int deleted() { + return 10; +} + +Future helper(Future timingControl) async { + setBeforeAwait = true; + await timingControl; + setAfterAwait = true; + return deleted(); +} + +Future main() async { + var helperCompleter = Completer(); + var helperFuture = helper(helperCompleter.future); + Expect.isTrue(setBeforeAwait); + Expect.isFalse(setAfterAwait); + await hotReload(); + Expect.isFalse(setAfterAwait); + helperCompleter.complete(); + var e = await asyncExpectThrows(helperFuture); + Expect.contains( + "NoSuchMethodError: 'deleted'\n" + 'Method was deleted during a hot reload and is no longer callable.', + e.toString(), + ); + Expect.isTrue(setAfterAwait); +} diff --git a/tests/hot_reload/async_call_deleted_top_level_function_ddc/main.1.dart b/tests/hot_reload/async_call_deleted_top_level_function_ddc/main.1.dart new file mode 100644 index 00000000000..e70a6a3ca33 --- /dev/null +++ b/tests/hot_reload/async_call_deleted_top_level_function_ddc/main.1.dart @@ -0,0 +1,59 @@ +// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'dart:async'; + +import 'package:expect/expect.dart'; +import 'package:expect/async_helper.dart'; +import 'package:reload_test/reload_test_utils.dart'; + +/// Shows the behavior when async code enqueued before a hot restart runs +/// after the restart and tries to interact with code that has been deleted. + +bool setBeforeAwait = false; +bool setAfterAwait = false; + +Future helper(Future timingControl) async { + setBeforeAwait = true; + await timingControl; + setAfterAwait = true; + return 99; +} + +Future main() async { + var helperCompleter = Completer(); + var helperFuture = helper(helperCompleter.future); + Expect.isTrue(setBeforeAwait); + Expect.isFalse(setAfterAwait); + await hotReload(); + Expect.isFalse(setAfterAwait); + helperCompleter.complete(); + var e = await asyncExpectThrows(helperFuture); + Expect.contains( + "NoSuchMethodError: 'deleted'\n" + 'Method was deleted during a hot reload and is no longer callable.', + e.toString(), + ); + Expect.isTrue(setAfterAwait); +} + +/** DIFF **/ +/* + bool setBeforeAwait = false; + bool setAfterAwait = false; + +-int deleted() { +- return 10; +-} +- + Future helper(Future timingControl) async { + setBeforeAwait = true; + await timingControl; + setAfterAwait = true; +- return deleted(); ++ return 99; + } + + Future main() async { +*/ diff --git a/tests/hot_reload/async_call_deleted_top_level_function_vm/config.json b/tests/hot_reload/async_call_deleted_top_level_function_vm/config.json new file mode 100644 index 00000000000..2e7ceb47217 --- /dev/null +++ b/tests/hot_reload/async_call_deleted_top_level_function_vm/config.json @@ -0,0 +1,6 @@ +{ + "exclude": [ + "chrome", + "d8" + ] +} \ No newline at end of file diff --git a/tests/hot_reload/async_call_deleted_top_level_function_vm/main.0.dart b/tests/hot_reload/async_call_deleted_top_level_function_vm/main.0.dart new file mode 100644 index 00000000000..bd0c6190b85 --- /dev/null +++ b/tests/hot_reload/async_call_deleted_top_level_function_vm/main.0.dart @@ -0,0 +1,37 @@ +// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'dart:async'; + +import 'package:expect/expect.dart'; +import 'package:reload_test/reload_test_utils.dart'; + +/// Shows the behavior when async code enqueued before a hot restart runs +/// after the restart and tries to interact with code that has been deleted. + +bool setBeforeAwait = false; +bool setAfterAwait = false; + +int deleted() { + return 10; +} + +Future helper(Future timingControl) async { + setBeforeAwait = true; + await timingControl; + setAfterAwait = true; + return deleted(); +} + +Future main() async { + var helperCompleter = Completer(); + var helperFuture = helper(helperCompleter.future); + Expect.isTrue(setBeforeAwait); + Expect.isFalse(setAfterAwait); + await hotReload(); + Expect.isFalse(setAfterAwait); + helperCompleter.complete(); + Expect.equals(10, await helperFuture); + Expect.isTrue(setAfterAwait); +} diff --git a/tests/hot_reload/async_call_deleted_top_level_function_vm/main.1.dart b/tests/hot_reload/async_call_deleted_top_level_function_vm/main.1.dart new file mode 100644 index 00000000000..7ff314d00e4 --- /dev/null +++ b/tests/hot_reload/async_call_deleted_top_level_function_vm/main.1.dart @@ -0,0 +1,53 @@ +// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'dart:async'; + +import 'package:expect/expect.dart'; +import 'package:reload_test/reload_test_utils.dart'; + +/// Shows the behavior when async code enqueued before a hot restart runs +/// after the restart and tries to interact with code that has been deleted. + +bool setBeforeAwait = false; +bool setAfterAwait = false; + +Future helper(Future timingControl) async { + setBeforeAwait = true; + await timingControl; + setAfterAwait = true; + return 99; +} + +Future main() async { + var helperCompleter = Completer(); + var helperFuture = helper(helperCompleter.future); + Expect.isTrue(setBeforeAwait); + Expect.isFalse(setAfterAwait); + await hotReload(); + Expect.isFalse(setAfterAwait); + helperCompleter.complete(); + Expect.equals(10, await helperFuture); + Expect.isTrue(setAfterAwait); +} + +/** DIFF **/ +/* + bool setBeforeAwait = false; + bool setAfterAwait = false; + +-int deleted() { +- return 10; +-} +- + Future helper(Future timingControl) async { + setBeforeAwait = true; + await timingControl; + setAfterAwait = true; +- return deleted(); ++ return 99; + } + + Future main() async { +*/ diff --git a/tests/hot_reload/async_call_top_level_function_parameters_change_ddc/config.json b/tests/hot_reload/async_call_top_level_function_parameters_change_ddc/config.json new file mode 100644 index 00000000000..6bd1b0002af --- /dev/null +++ b/tests/hot_reload/async_call_top_level_function_parameters_change_ddc/config.json @@ -0,0 +1,5 @@ +{ + "exclude": [ + "vm" + ] +} \ No newline at end of file diff --git a/tests/hot_reload/async_call_top_level_function_parameters_change_ddc/main.0.dart b/tests/hot_reload/async_call_top_level_function_parameters_change_ddc/main.0.dart new file mode 100644 index 00000000000..060b758a273 --- /dev/null +++ b/tests/hot_reload/async_call_top_level_function_parameters_change_ddc/main.0.dart @@ -0,0 +1,42 @@ +// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'dart:async'; + +import 'package:expect/async_helper.dart'; +import 'package:expect/expect.dart'; +import 'package:reload_test/reload_test_utils.dart'; + +/// Shows the behavior when async code enqueued before a hot restart runs +/// after the restart and tries to interact with code that has been updated. + +bool setBeforeAwait = false; +bool setAfterAwait = false; + +int changed(String s) { + return s.length; +} + +Future helper(Future timingControl) async { + setBeforeAwait = true; + await timingControl; + setAfterAwait = true; + return changed('hello'); +} + +Future main() async { + var helperCompleter = Completer(); + var helperFuture = helper(helperCompleter.future); + Expect.isTrue(setBeforeAwait); + Expect.isFalse(setAfterAwait); + await hotReload(); + Expect.isFalse(setAfterAwait); + helperCompleter.complete(); + var error = await asyncExpectThrows(helperFuture); + Expect.contains( + "type 'String' is not a subtype of type 'int'", + error.toString(), + ); + Expect.isTrue(setAfterAwait); +} diff --git a/tests/hot_reload/async_call_top_level_function_parameters_change_ddc/main.1.dart b/tests/hot_reload/async_call_top_level_function_parameters_change_ddc/main.1.dart new file mode 100644 index 00000000000..7b52365c77b --- /dev/null +++ b/tests/hot_reload/async_call_top_level_function_parameters_change_ddc/main.1.dart @@ -0,0 +1,64 @@ +// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'dart:async'; + +import 'package:expect/async_helper.dart'; +import 'package:expect/expect.dart'; +import 'package:reload_test/reload_test_utils.dart'; + +/// Shows the behavior when async code enqueued before a hot restart runs +/// after the restart and tries to interact with code that has been updated. + +bool setBeforeAwait = false; +bool setAfterAwait = false; + +int changed(int i) { + return i - 10; +} + +Future helper(Future timingControl) async { + setBeforeAwait = true; + await timingControl; + setAfterAwait = true; + return changed(99); +} + +Future main() async { + var helperCompleter = Completer(); + var helperFuture = helper(helperCompleter.future); + Expect.isTrue(setBeforeAwait); + Expect.isFalse(setAfterAwait); + await hotReload(); + Expect.isFalse(setAfterAwait); + helperCompleter.complete(); + var error = await asyncExpectThrows(helperFuture); + Expect.contains( + "type 'String' is not a subtype of type 'int'", + error.toString(), + ); + Expect.isTrue(setAfterAwait); +} + +/** DIFF **/ +/* + bool setBeforeAwait = false; + bool setAfterAwait = false; + +-int changed(String s) { +- return s.length; ++int changed(int i) { ++ return i - 10; + } + + Future helper(Future timingControl) async { + setBeforeAwait = true; + await timingControl; + setAfterAwait = true; +- return changed('hello'); ++ return changed(99); + } + + Future main() async { +*/ diff --git a/tests/hot_reload/async_call_top_level_function_parameters_change_vm/config.json b/tests/hot_reload/async_call_top_level_function_parameters_change_vm/config.json new file mode 100644 index 00000000000..2e7ceb47217 --- /dev/null +++ b/tests/hot_reload/async_call_top_level_function_parameters_change_vm/config.json @@ -0,0 +1,6 @@ +{ + "exclude": [ + "chrome", + "d8" + ] +} \ No newline at end of file diff --git a/tests/hot_reload/async_call_top_level_function_parameters_change_vm/main.0.dart b/tests/hot_reload/async_call_top_level_function_parameters_change_vm/main.0.dart new file mode 100644 index 00000000000..6c12ae4029a --- /dev/null +++ b/tests/hot_reload/async_call_top_level_function_parameters_change_vm/main.0.dart @@ -0,0 +1,42 @@ +// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'dart:async'; + +import 'package:expect/async_helper.dart'; +import 'package:expect/expect.dart'; +import 'package:reload_test/reload_test_utils.dart'; + +/// Shows the behavior when async code enqueued before a hot restart runs +/// after the restart and tries to interact with code that has been updated. + +bool setBeforeAwait = false; +bool setAfterAwait = false; + +int changed(String s) { + return s.length; +} + +Future helper(Future timingControl) async { + setBeforeAwait = true; + await timingControl; + setAfterAwait = true; + return changed('hello'); +} + +Future main() async { + var helperCompleter = Completer(); + var helperFuture = helper(helperCompleter.future); + Expect.isTrue(setBeforeAwait); + Expect.isFalse(setAfterAwait); + await hotReload(); + Expect.isFalse(setAfterAwait); + helperCompleter.complete(); + var error = await asyncExpectThrows(helperFuture); + Expect.contains( + "Class 'String' has no instance method '-'", + error.toString(), + ); + Expect.isTrue(setAfterAwait); +} diff --git a/tests/hot_reload/async_call_top_level_function_parameters_change_vm/main.1.dart b/tests/hot_reload/async_call_top_level_function_parameters_change_vm/main.1.dart new file mode 100644 index 00000000000..0f50ea3afa0 --- /dev/null +++ b/tests/hot_reload/async_call_top_level_function_parameters_change_vm/main.1.dart @@ -0,0 +1,64 @@ +// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'dart:async'; + +import 'package:expect/async_helper.dart'; +import 'package:expect/expect.dart'; +import 'package:reload_test/reload_test_utils.dart'; + +/// Shows the behavior when async code enqueued before a hot restart runs +/// after the restart and tries to interact with code that has been updated. + +bool setBeforeAwait = false; +bool setAfterAwait = false; + +int changed(int i) { + return i - 10; +} + +Future helper(Future timingControl) async { + setBeforeAwait = true; + await timingControl; + setAfterAwait = true; + return changed(99); +} + +Future main() async { + var helperCompleter = Completer(); + var helperFuture = helper(helperCompleter.future); + Expect.isTrue(setBeforeAwait); + Expect.isFalse(setAfterAwait); + await hotReload(); + Expect.isFalse(setAfterAwait); + helperCompleter.complete(); + var error = await asyncExpectThrows(helperFuture); + Expect.contains( + "Class 'String' has no instance method '-'", + error.toString(), + ); + Expect.isTrue(setAfterAwait); +} + +/** DIFF **/ +/* + bool setBeforeAwait = false; + bool setAfterAwait = false; + +-int changed(String s) { +- return s.length; ++int changed(int i) { ++ return i - 10; + } + + Future helper(Future timingControl) async { + setBeforeAwait = true; + await timingControl; + setAfterAwait = true; +- return changed('hello'); ++ return changed(99); + } + + Future main() async { +*/ diff --git a/tests/hot_reload/existing_field_changes_type/main.0.dart b/tests/hot_reload/existing_field_changes_type/main.0.dart index 4ec158b895f..74dbfe78a70 100644 --- a/tests/hot_reload/existing_field_changes_type/main.0.dart +++ b/tests/hot_reload/existing_field_changes_type/main.0.dart @@ -13,11 +13,13 @@ class Foo { int x = 42; } +helper() => Foo().x; + Future main() async { - Expect.type(Foo().x); - Expect.equals(42, Foo().x); + Expect.type(helper()); + Expect.equals(42, helper()); await hotReload(); - Expect.type(Foo().x); - Expect.equals('42', Foo().x); + Expect.type(helper()); + Expect.equals('42', helper()); } diff --git a/tests/hot_reload/existing_field_changes_type/main.1.dart b/tests/hot_reload/existing_field_changes_type/main.1.dart index a2fc85859a2..3b2774eb3c4 100644 --- a/tests/hot_reload/existing_field_changes_type/main.1.dart +++ b/tests/hot_reload/existing_field_changes_type/main.1.dart @@ -13,13 +13,15 @@ class Foo { String x = '42'; } +helper() => Foo().x; + Future main() async { - Expect.type(Foo().x); - Expect.equals(42, Foo().x); + Expect.type(helper()); + Expect.equals(42, helper()); await hotReload(); - Expect.type(Foo().x); - Expect.equals('42', Foo().x); + Expect.type(helper()); + Expect.equals('42', helper()); } /** DIFF **/ @@ -31,5 +33,5 @@ Future main() async { + String x = '42'; } - Future main() async { + helper() => Foo().x; */