Version 2.19.0-23.0.dev
Merge commit 'eaac498a9abc1128ffa3f5572d2a385369ae7763' into 'dev'
diff --git a/DEPS b/DEPS
index c7b269e..f45a066 100644
--- a/DEPS
+++ b/DEPS
@@ -108,7 +108,7 @@
# For more details, see https://github.com/dart-lang/sdk/issues/30164.
"dart_style_rev": "d7b73536a8079331c888b7da539b80e6825270ea", # manually rev'd
- "dartdoc_rev": "e6c8861ad3559a6dd61066a12bb81310ce131ae5",
+ "dartdoc_rev": "d3b0b724972ffcc70a47c35a31051af2c3ca012d",
"devtools_rev": "95d292626da26505b02417735f77e8922783b477",
"ffi_rev": "18b2b549d55009ff594600b04705ff6161681e07",
"file_rev": "0132eeedea2933513bf230513a766a8baeab0c4f",
@@ -132,7 +132,7 @@
"mockito_rev": "d8a2ddd2054390bd03d34bf64c940884e6f7a596",
"oauth2_rev": "199ebf15cbd5b07958438184f32e41c4447a57bf",
"package_config_rev": "cff98c90acc457a3b0750f0a7da0e351a35e5d0c",
- "path_rev": "7a0ed40280345b1c11df4c700c71e590738f4257",
+ "path_rev": "9955b27b9bb98d87591208e19eb01c51d29fd467",
"ply_rev": "604b32590ffad5cbb82e4afef1d305512d06ae93",
"pool_rev": "fa84ddd0e39f45bf3f09dcc5d6b9fbdda7820fef",
"protobuf_rev": "14c9c0b2d5542e73198a98054d93f0cb4acc846a",
@@ -143,7 +143,7 @@
"shelf_rev": "f2cca46bdeb9888f29e685626bbe40654e1ca296",
"source_map_stack_trace_rev": "72dbf21a33293b2b8434d0a9751e36f9463981ac",
"source_maps_rev": "e93565b43a7b6b367789de8ffba969c4ebeeb317",
- "source_span_rev": "24151fd80e4557a626f81f2bc0d6a2ebde172cae",
+ "source_span_rev": "ff03af16474ce91c89eb3bc28182866f4cb6dfb0",
"sse_rev": "2df072848a6090d3ed67f30c69e86ec4d6b96cd6",
"stack_trace_rev": "17f09c2c6845bb31c7c385acecce5befb8527a13",
"stream_channel_rev": "8e0d7ef1f4a3fb97fbd82e11cd539093f58511f3",
@@ -161,7 +161,7 @@
"web_components_rev": "8f57dac273412a7172c8ade6f361b407e2e4ed02",
"web_socket_channel_rev": "99dbdc5769e19b9eeaf69449a59079153c6a8b1f",
"WebCore_rev": "bcb10901266c884e7b3740abc597ab95373ab55c",
- "webdev_rev": "b697974bc5218ac443e0e08c42dbe4f70cb60d23",
+ "webdev_rev": "3d2ad34a5354cfb2324d155d903b26590d79bd19",
"webdriver_rev": "e1a9ad671ee82e05eee463f922a34585ed2d2f15",
"webkit_inspection_protocol_rev": "57522d6b29d94903b765c757079d906555d5a171",
"yaml_edit_rev": "01589b3ce447b03aed991db49f1ec6445ad5476d",
diff --git a/pkg/analysis_server/lib/src/lsp/handlers/commands/perform_refactor.dart b/pkg/analysis_server/lib/src/lsp/handlers/commands/perform_refactor.dart
index a27fa77..57dae65 100644
--- a/pkg/analysis_server/lib/src/lsp/handlers/commands/perform_refactor.dart
+++ b/pkg/analysis_server/lib/src/lsp/handlers/commands/perform_refactor.dart
@@ -33,13 +33,10 @@
final refactoring = await getRefactoring(
RefactoringKind(kind), result, offset, length, options);
return refactoring.mapResult((refactoring) async {
- // If the token we were given is not cancellable, replace it with one that
- // is for the rest of this request, as a future refactor may need to cancel
- // this request.
- // The original token should be kept and also checked for cancellation.
- final cancelableToken = cancellationToken is CancelableToken
- ? cancellationToken
- : CancelableToken();
+ // If the token we were given is not cancelable, wrap it with one that
+ // is for the rest of this request as a future refactor may need to
+ // cancel this request.
+ final cancelableToken = cancellationToken.asCancelable();
manager.begin(cancelableToken);
try {
@@ -54,15 +51,13 @@
return success(null);
}
- if (cancellationToken.isCancellationRequested ||
- cancelableToken.isCancellationRequested) {
+ if (cancelableToken.isCancellationRequested) {
return error(ErrorCodes.RequestCancelled, 'Request was cancelled');
}
final change = await refactoring.createChange();
- if (cancellationToken.isCancellationRequested ||
- cancelableToken.isCancellationRequested) {
+ if (cancelableToken.isCancellationRequested) {
return error(ErrorCodes.RequestCancelled, 'Request was cancelled');
}
diff --git a/pkg/analysis_server/lib/src/lsp/handlers/commands/validate_refactor.dart b/pkg/analysis_server/lib/src/lsp/handlers/commands/validate_refactor.dart
index 72107e8..ba9d18f 100644
--- a/pkg/analysis_server/lib/src/lsp/handlers/commands/validate_refactor.dart
+++ b/pkg/analysis_server/lib/src/lsp/handlers/commands/validate_refactor.dart
@@ -42,13 +42,10 @@
final refactoring = await getRefactoring(
RefactoringKind(kind), result, offset, length, options);
return refactoring.mapResult((refactoring) async {
- // If the token we were given is not cancellable, replace it with one that
- // is for the rest of this request, as a future refactor may need to cancel
- // this request.
- // The original token should be kept and also checked for cancellation.
- final cancelableToken = cancellationToken is CancelableToken
- ? cancellationToken
- : CancelableToken();
+ // If the token we were given is not cancelable, wrap it with one that
+ // is for the rest of this request as a future refactor may need to
+ // cancel this request.
+ final cancelableToken = cancellationToken.asCancelable();
manager.begin(cancelableToken);
try {
diff --git a/pkg/analysis_server/lib/src/lsp/handlers/handler_completion.dart b/pkg/analysis_server/lib/src/lsp/handlers/handler_completion.dart
index 40fdf76..f9cbe3b 100644
--- a/pkg/analysis_server/lib/src/lsp/handlers/handler_completion.dart
+++ b/pkg/analysis_server/lib/src/lsp/handlers/handler_completion.dart
@@ -48,6 +48,15 @@
/// debugging).
late final Duration completionBudgetDuration;
+ /// A cancellation token for the previous completion request.
+ ///
+ /// A new completion request will cancel the previous request. We do not allow
+ /// concurrent completion requests.
+ ///
+ /// `null` if there is no previous request. It the previous request has
+ /// already completed, cancelling this token will not do anything.
+ CancelableToken? previousRequestCancellationToken;
+
CompletionHandler(super.server, LspInitializationOptions options)
: suggestFromUnimportedLibraries = options.suggestFromUnimportedLibraries,
previewNotImportedCompletions = options.previewNotImportedCompletions {
@@ -73,6 +82,12 @@
return serverNotInitializedError;
}
+ // Cancel any existing in-progress completion request in case the client did
+ // not do it explicitly, because the results will not be useful and it may
+ // delay processing this one.
+ previousRequestCancellationToken?.cancel();
+ previousRequestCancellationToken = token.asCancelable();
+
final requestLatency = message.timeSinceRequest;
final triggerCharacter = params.context?.triggerCharacter;
final pos = params.position;
diff --git a/pkg/analysis_server/lib/src/services/correction/organize_imports.dart b/pkg/analysis_server/lib/src/services/correction/organize_imports.dart
index 8206e04..b31d1f9 100644
--- a/pkg/analysis_server/lib/src/services/correction/organize_imports.dart
+++ b/pkg/analysis_server/lib/src/services/correction/organize_imports.dart
@@ -59,7 +59,8 @@
bool _isUnusedImport(UriBasedDirective directive) {
for (var error in errors) {
if ((error.errorCode == HintCode.DUPLICATE_IMPORT ||
- error.errorCode == HintCode.UNUSED_IMPORT) &&
+ error.errorCode == HintCode.UNUSED_IMPORT ||
+ error.errorCode == HintCode.UNNECESSARY_IMPORT) &&
directive.uri.offset == error.offset) {
return true;
}
diff --git a/pkg/analysis_server/test/lsp/completion_dart_test.dart b/pkg/analysis_server/test/lsp/completion_dart_test.dart
index 8388626..30298a0 100644
--- a/pkg/analysis_server/test/lsp/completion_dart_test.dart
+++ b/pkg/analysis_server/test/lsp/completion_dart_test.dart
@@ -735,6 +735,27 @@
request, throwsA(isResponseError(ErrorCodes.InvalidParams)));
}
+ Future<void> test_concurrentRequestsCancellation() async {
+ // We expect a new completion request to cancel any in-flight request so
+ // send multiple without awaiting, then check only the last one completes.
+ final content = '^';
+
+ await initialize();
+ await openFile(mainFileUri, withoutMarkers(content));
+ final position = positionFromMarker(content);
+ final responseFutures = [
+ getCompletion(mainFileUri, position),
+ getCompletion(mainFileUri, position),
+ getCompletion(mainFileUri, position),
+ ];
+ expect(responseFutures[0],
+ throwsA(isResponseError(ErrorCodes.RequestCancelled)));
+ expect(responseFutures[1],
+ throwsA(isResponseError(ErrorCodes.RequestCancelled)));
+ final results = await responseFutures[2];
+ expect(results, isNotEmpty);
+ }
+
Future<void> test_filterTextNotIncludeAdditionalText() async {
// Some completions (eg. overrides) have additional text that is not part
// of the label. That text should _not_ appear in filterText as it will
diff --git a/pkg/analysis_server/test/services/correction/organize_directives_test.dart b/pkg/analysis_server/test/services/correction/organize_directives_test.dart
index 3d59aee..e61dd22 100644
--- a/pkg/analysis_server/test/services/correction/organize_directives_test.dart
+++ b/pkg/analysis_server/test/services/correction/organize_directives_test.dart
@@ -245,6 +245,31 @@
}''', removeUnused: true);
}
+ Future<void> test_remove_unnecessaryImports() async {
+ newFile(
+ convertPath('$testPackageLibPath/declarations.dart'),
+ 'class A {} class B {}',
+ );
+ newFile(
+ convertPath('$testPackageLibPath/exports.dart'),
+ 'export "a.dart" show A;',
+ );
+ await _computeUnitAndErrors(r'''
+import 'declarations.dart';
+import 'exports.dart';
+
+A? a;
+B? b;
+''');
+ // validate change
+ _assertOrganize(r'''
+import 'declarations.dart';
+
+A? a;
+B? b;
+''', removeUnused: true);
+ }
+
Future<void> test_remove_unusedImports() async {
await _computeUnitAndErrors(r'''
library lib;
diff --git a/pkg/analyzer/lib/src/dart/analysis/file_state.dart b/pkg/analyzer/lib/src/dart/analysis/file_state.dart
index a133a30e..de3a78c 100644
--- a/pkg/analyzer/lib/src/dart/analysis/file_state.dart
+++ b/pkg/analyzer/lib/src/dart/analysis/file_state.dart
@@ -115,13 +115,22 @@
/// [AugmentationImportState] that has a valid URI.
class AugmentationImportWithUri<U extends DirectiveUriWithUri>
- extends AugmentationImportState<U> {
+ extends AugmentationImportWithUriStr<U> {
AugmentationImportWithUri({
required super.unlinked,
required super.uri,
});
}
+/// [AugmentationImportState] that has a relative URI string.
+class AugmentationImportWithUriStr<U extends DirectiveUriWithString>
+ extends AugmentationImportState<U> {
+ AugmentationImportWithUriStr({
+ required super.unlinked,
+ required super.uri,
+ });
+}
+
/// The URI of the [unlinked] can be resolved.
class AugmentationKnownFileKind extends AugmentationFileKind {
/// The file that is referenced by the [unlinked].
@@ -1883,6 +1892,11 @@
unlinked: unlinked,
uri: uri,
);
+ } else if (uri is DirectiveUriWithString) {
+ return AugmentationImportWithUriStr(
+ unlinked: unlinked,
+ uri: uri,
+ );
} else {
return AugmentationImportState(
unlinked: unlinked,
diff --git a/pkg/analyzer/lib/src/dart/analysis/library_analyzer.dart b/pkg/analyzer/lib/src/dart/analysis/library_analyzer.dart
index f1c3150..9d7a8ac 100644
--- a/pkg/analyzer/lib/src/dart/analysis/library_analyzer.dart
+++ b/pkg/analyzer/lib/src/dart/analysis/library_analyzer.dart
@@ -562,49 +562,49 @@
directive.uriSource = uriState.source;
}
+ // TODO(scheglov) Similarly restructure imports/exports.
final AugmentationFileKind? importedAugmentationKind;
- if (state is AugmentationImportWithUri) {
- if (state.importedSource == null) {
- // TODO(scheglov) When do we have a valid URI here and in imports?
- final errorCode = state.uri.isValid
- ? CompileTimeErrorCode.URI_DOES_NOT_EXIST
- : CompileTimeErrorCode.INVALID_URI;
+ if (state is AugmentationImportWithFile) {
+ importedAugmentationKind = state.importedAugmentation;
+ if (!state.importedFile.exists) {
+ final errorCode = isGeneratedSource(state.importedSource)
+ ? CompileTimeErrorCode.URI_HAS_NOT_BEEN_GENERATED
+ : CompileTimeErrorCode.URI_DOES_NOT_EXIST;
errorReporter.reportErrorForNode(
errorCode,
directive.uri,
- [state.uri.relativeUriStr],
+ [state.importedFile.uriStr],
);
return;
- } else if (state is AugmentationImportWithFile) {
- importedAugmentationKind = state.importedAugmentation;
- if (!state.importedFile.exists) {
- final errorCode = isGeneratedSource(state.importedSource)
- ? CompileTimeErrorCode.URI_HAS_NOT_BEEN_GENERATED
- : CompileTimeErrorCode.URI_DOES_NOT_EXIST;
- errorReporter.reportErrorForNode(
- errorCode,
- directive.uri,
- [state.importedFile.uriStr],
- );
- return;
- } else if (importedAugmentationKind == null) {
- errorReporter.reportErrorForNode(
- CompileTimeErrorCode.IMPORT_OF_NOT_AUGMENTATION,
- directive.uri,
- [state.importedFile.uriStr],
- );
- return;
- } else if (!seenAugmentations.add(importedAugmentationKind)) {
- errorReporter.reportErrorForNode(
- CompileTimeErrorCode.DUPLICATE_AUGMENTATION_IMPORT,
- directive.uri,
- [state.importedFile.uriStr],
- );
- return;
- }
- } else {
+ } else if (importedAugmentationKind == null) {
+ errorReporter.reportErrorForNode(
+ CompileTimeErrorCode.IMPORT_OF_NOT_AUGMENTATION,
+ directive.uri,
+ [state.importedFile.uriStr],
+ );
+ return;
+ } else if (!seenAugmentations.add(importedAugmentationKind)) {
+ errorReporter.reportErrorForNode(
+ CompileTimeErrorCode.DUPLICATE_AUGMENTATION_IMPORT,
+ directive.uri,
+ [state.importedFile.uriStr],
+ );
return;
}
+ } else if (state is AugmentationImportWithUri) {
+ errorReporter.reportErrorForNode(
+ CompileTimeErrorCode.URI_DOES_NOT_EXIST,
+ directive.uri,
+ [state.uri.relativeUriStr],
+ );
+ return;
+ } else if (state is AugmentationImportWithUriStr) {
+ errorReporter.reportErrorForNode(
+ CompileTimeErrorCode.INVALID_URI,
+ directive.uri,
+ [state.uri.relativeUriStr],
+ );
+ return;
} else {
errorReporter.reportErrorForNode(
CompileTimeErrorCode.URI_WITH_INTERPOLATION,
diff --git a/pkg/analyzer/lib/src/utilities/cancellation.dart b/pkg/analyzer/lib/src/utilities/cancellation.dart
index 4dff1bd..44448b1 100644
--- a/pkg/analyzer/lib/src/utilities/cancellation.dart
+++ b/pkg/analyzer/lib/src/utilities/cancellation.dart
@@ -23,3 +23,37 @@
@override
bool get isCancellationRequested => false;
}
+
+/// A cancellable wrapper over another cancellation token.
+///
+/// This token will be considered cancelled if either it is itself cancelled,
+/// or if [child] is cancelled.
+///
+/// Cancelling this token will also cancel [child] if it is a cancelable
+/// token.
+class _WrappedCancelableToken extends CancelableToken {
+ final CancellationToken _child;
+
+ _WrappedCancelableToken(this._child);
+
+ @override
+ bool get isCancellationRequested =>
+ super.isCancellationRequested || _child.isCancellationRequested;
+
+ @override
+ void cancel() {
+ super.cancel();
+ final child = _child;
+ if (child is CancelableToken) {
+ child.cancel();
+ }
+ }
+}
+
+extension CancellationTokenExtension on CancellationToken {
+ /// Wraps this token to make it cancelable if it is not already.
+ CancelableToken asCancelable() {
+ final token = this;
+ return token is CancelableToken ? token : _WrappedCancelableToken(token);
+ }
+}
diff --git a/pkg/analyzer/test/src/dart/analysis/analyzer_state_printer.dart b/pkg/analyzer/test/src/dart/analysis/analyzer_state_printer.dart
index 7c3818aa..1c394c3 100644
--- a/pkg/analyzer/test/src/dart/analysis/analyzer_state_printer.dart
+++ b/pkg/analyzer/test/src/dart/analysis/analyzer_state_printer.dart
@@ -112,10 +112,12 @@
}
sink.writeln();
} else if (import is AugmentationImportWithUri) {
+ _writelnWithIndent('uri: ${import.uri.relativeUri}');
+ } else if (import is AugmentationImportWithUriStr) {
final uriStr = _stringOfUriStr(import.uri.relativeUriStr);
- _writelnWithIndent('uri: $uriStr');
+ _writelnWithIndent('uriStr: $uriStr');
} else {
- _writelnWithIndent('noUri');
+ _writelnWithIndent('noUriStr');
}
},
);
diff --git a/pkg/analyzer/test/src/dart/analysis/file_state_test.dart b/pkg/analyzer/test/src/dart/analysis/file_state_test.dart
index fa16196..02bf723 100644
--- a/pkg/analyzer/test/src/dart/analysis/file_state_test.dart
+++ b/pkg/analyzer/test/src/dart/analysis/file_state_test.dart
@@ -1635,9 +1635,9 @@
''');
}
- test_newFile_library_augmentations_invalidUri_cannotParse() async {
+ test_newFile_library_augmentations_noRelativeUri() async {
final a = newFile('$testPackageLibPath/a.dart', r'''
-import augment 'da:';
+import augment ':net';
''');
fileStateFor(a);
@@ -1652,7 +1652,7 @@
libraryImports
library_1 dart:core synthetic
augmentationImports
- uri: da:
+ uriStr: :net
cycle_0
dependencies: dart:core
libraries: library_0
@@ -1663,7 +1663,7 @@
''');
}
- test_newFile_library_augmentations_invalidUri_interpolation() async {
+ test_newFile_library_augmentations_noRelativeUriStr() async {
final a = newFile('$testPackageLibPath/a.dart', r'''
import augment '${'foo.dart'}';
''');
@@ -1680,7 +1680,35 @@
libraryImports
library_1 dart:core synthetic
augmentationImports
- noUri
+ noUriStr
+ cycle_0
+ dependencies: dart:core
+ libraries: library_0
+ apiSignature_0
+ unlinkedKey: k00
+libraryCycles
+elementFactory
+''');
+ }
+
+ test_newFile_library_augmentations_noSource() async {
+ final a = newFile('$testPackageLibPath/a.dart', r'''
+import augment 'foo:bar';
+''');
+
+ fileStateFor(a);
+
+ assertDriverStateString(testFile, r'''
+files
+ /home/test/lib/a.dart
+ uri: package:test/a.dart
+ current
+ id: file_0
+ kind: library_0
+ libraryImports
+ library_1 dart:core synthetic
+ augmentationImports
+ uri: foo:bar
cycle_0
dependencies: dart:core
libraries: library_0
diff --git a/pkg/analyzer/test/src/dart/resolution/augmentation_import_test.dart b/pkg/analyzer/test/src/dart/resolution/augmentation_import_test.dart
index 8cfd850..cedb204 100644
--- a/pkg/analyzer/test/src/dart/resolution/augmentation_import_test.dart
+++ b/pkg/analyzer/test/src/dart/resolution/augmentation_import_test.dart
@@ -87,38 +87,6 @@
''');
}
- test_inAugmentation_notAugmentation_invalidUri() async {
- newFile('$testPackageLibPath/a.dart', r'''
-import augment 'b.dart';
-''');
-
- final b = newFile('$testPackageLibPath/b.dart', r'''
-library augment 'a.dart';
-import augment 'da:';
-''');
-
- await resolveFile2(b.path);
- assertErrorsInResult([
- error(CompileTimeErrorCode.INVALID_URI, 41, 5),
- ]);
-
- final node = findNode.augmentationImportDirective('da:');
- assertResolvedNodeText(node, r'''
-AugmentationImportDirective
- importKeyword: import
- augmentKeyword: augment
- uri: SimpleStringLiteral
- literal: 'da:'
- semicolon: ;
- element: AugmentationImportElement
- uri: DirectiveUriWithRelativeUri
- relativeUri: da:
- uriContent: da:
- uriElement: <null>
- uriSource: <null>
-''');
- }
-
test_inAugmentation_notAugmentation_library() async {
newFile('$testPackageLibPath/a.dart', r'''
import augment 'b.dart';
@@ -153,35 +121,109 @@
''');
}
- test_inAugmentation_notAugmentation_uriDoesNotExist() async {
+ test_inAugmentation_notAugmentation_noRelativeUri() async {
newFile('$testPackageLibPath/a.dart', r'''
import augment 'b.dart';
''');
final b = newFile('$testPackageLibPath/b.dart', r'''
library augment 'a.dart';
-import augment 'c.dart';
+import augment ':net';
''');
await resolveFile2(b.path);
assertErrorsInResult([
- error(CompileTimeErrorCode.URI_DOES_NOT_EXIST, 41, 8),
+ error(CompileTimeErrorCode.INVALID_URI, 41, 6),
]);
- final node = findNode.augmentationImportDirective('c.dart');
+ final node = findNode.augmentationImportDirective(':net');
assertResolvedNodeText(node, r'''
AugmentationImportDirective
importKeyword: import
augmentKeyword: augment
uri: SimpleStringLiteral
- literal: 'c.dart'
+ literal: ':net'
semicolon: ;
element: AugmentationImportElement
- uri: DirectiveUriWithSource
- source: package:test/c.dart
- uriContent: c.dart
+ uri: DirectiveUriWithRelativeUriString
+ relativeUriString: :net
+ uriContent: :net
uriElement: <null>
- uriSource: package:test/c.dart
+ uriSource: <null>
+''');
+ }
+
+ test_inAugmentation_notAugmentation_noRelativeUriStr() async {
+ newFile('$testPackageLibPath/a.dart', r'''
+import augment 'b.dart';
+''');
+
+ final b = newFile('$testPackageLibPath/b.dart', r'''
+library augment 'a.dart';
+import augment '${'foo'}.dart';
+''');
+
+ await resolveFile2(b.path);
+ assertErrorsInResult([
+ error(CompileTimeErrorCode.URI_WITH_INTERPOLATION, 41, 15),
+ ]);
+
+ final node = findNode.augmentationImportDirective('foo');
+ assertResolvedNodeText(node, r'''
+AugmentationImportDirective
+ importKeyword: import
+ augmentKeyword: augment
+ uri: StringInterpolation
+ elements
+ InterpolationString
+ contents: '
+ InterpolationExpression
+ leftBracket: ${
+ expression: SimpleStringLiteral
+ literal: 'foo'
+ rightBracket: }
+ InterpolationString
+ contents: .dart'
+ staticType: String
+ stringValue: null
+ semicolon: ;
+ element: AugmentationImportElement
+ uri: DirectiveUri
+ uriContent: null
+ uriElement: <null>
+ uriSource: <null>
+''');
+ }
+
+ test_inAugmentation_notAugmentation_noSource() async {
+ newFile('$testPackageLibPath/a.dart', r'''
+import augment 'b.dart';
+''');
+
+ final b = newFile('$testPackageLibPath/b.dart', r'''
+library augment 'a.dart';
+import augment 'foo:bar';
+''');
+
+ await resolveFile2(b.path);
+ assertErrorsInResult([
+ error(CompileTimeErrorCode.URI_DOES_NOT_EXIST, 41, 9),
+ ]);
+
+ final node = findNode.augmentationImportDirective('foo:bar');
+ assertResolvedNodeText(node, r'''
+AugmentationImportDirective
+ importKeyword: import
+ augmentKeyword: augment
+ uri: SimpleStringLiteral
+ literal: 'foo:bar'
+ semicolon: ;
+ element: AugmentationImportElement
+ uri: DirectiveUriWithRelativeUri
+ relativeUri: foo:bar
+ uriContent: foo:bar
+ uriElement: <null>
+ uriSource: <null>
''');
}
@@ -240,6 +282,30 @@
''');
}
+ test_inLibrary_notAugmentation_fileDoesNotExist() async {
+ await assertErrorsInCode(r'''
+import augment 'a.dart';
+''', [
+ error(CompileTimeErrorCode.URI_DOES_NOT_EXIST, 15, 8),
+ ]);
+
+ final node = findNode.augmentationImportDirective('a.dart');
+ assertResolvedNodeText(node, r'''
+AugmentationImportDirective
+ importKeyword: import
+ augmentKeyword: augment
+ uri: SimpleStringLiteral
+ literal: 'a.dart'
+ semicolon: ;
+ element: AugmentationImportElement
+ uri: DirectiveUriWithSource
+ source: package:test/a.dart
+ uriContent: a.dart
+ uriElement: <null>
+ uriSource: package:test/a.dart
+''');
+ }
+
test_inLibrary_notAugmentation_library() async {
newFile('$testPackageLibPath/a.dart', '');
@@ -266,6 +332,88 @@
''');
}
+ test_inLibrary_notAugmentation_noRelativeUri() async {
+ await assertErrorsInCode(r'''
+import augment ':net';
+''', [
+ error(CompileTimeErrorCode.INVALID_URI, 15, 6),
+ ]);
+
+ final node = findNode.augmentationImportDirective(':net');
+ assertResolvedNodeText(node, r'''
+AugmentationImportDirective
+ importKeyword: import
+ augmentKeyword: augment
+ uri: SimpleStringLiteral
+ literal: ':net'
+ semicolon: ;
+ element: AugmentationImportElement
+ uri: DirectiveUriWithRelativeUriString
+ relativeUriString: :net
+ uriContent: :net
+ uriElement: <null>
+ uriSource: <null>
+''');
+ }
+
+ test_inLibrary_notAugmentation_noRelativeUriStr() async {
+ await assertErrorsInCode(r'''
+import augment '${'foo'}.dart';
+''', [
+ error(CompileTimeErrorCode.URI_WITH_INTERPOLATION, 15, 15),
+ ]);
+
+ final node = findNode.augmentationImportDirective('foo');
+ assertResolvedNodeText(node, r'''
+AugmentationImportDirective
+ importKeyword: import
+ augmentKeyword: augment
+ uri: StringInterpolation
+ elements
+ InterpolationString
+ contents: '
+ InterpolationExpression
+ leftBracket: ${
+ expression: SimpleStringLiteral
+ literal: 'foo'
+ rightBracket: }
+ InterpolationString
+ contents: .dart'
+ staticType: String
+ stringValue: null
+ semicolon: ;
+ element: AugmentationImportElement
+ uri: DirectiveUri
+ uriContent: null
+ uriElement: <null>
+ uriSource: <null>
+''');
+ }
+
+ test_inLibrary_notAugmentation_noSource() async {
+ await assertErrorsInCode(r'''
+import augment 'foo:bar';
+''', [
+ error(CompileTimeErrorCode.URI_DOES_NOT_EXIST, 15, 9),
+ ]);
+
+ final node = findNode.augmentationImportDirective('foo:bar');
+ assertResolvedNodeText(node, r'''
+AugmentationImportDirective
+ importKeyword: import
+ augmentKeyword: augment
+ uri: SimpleStringLiteral
+ literal: 'foo:bar'
+ semicolon: ;
+ element: AugmentationImportElement
+ uri: DirectiveUriWithRelativeUri
+ relativeUri: foo:bar
+ uriContent: foo:bar
+ uriElement: <null>
+ uriSource: <null>
+''');
+ }
+
test_inLibrary_notAugmentation_partOfName() async {
newFile('$testPackageLibPath/a.dart', r'''
part of my.lib;
diff --git a/pkg/analyzer/test/src/diagnostics/invalid_uri_test.dart b/pkg/analyzer/test/src/diagnostics/invalid_uri_test.dart
index 89560e1..d63fc0a 100644
--- a/pkg/analyzer/test/src/diagnostics/invalid_uri_test.dart
+++ b/pkg/analyzer/test/src/diagnostics/invalid_uri_test.dart
@@ -17,7 +17,7 @@
class InvalidUriTest extends PubPackageResolutionTest {
test_augmentationImport_invalidScheme() async {
await assertErrorsInCode('''
-import augment 'ht:';
+import augment ':da';
''', [
error(CompileTimeErrorCode.INVALID_URI, 15, 5),
]);
diff --git a/pkg/analyzer/test/src/diagnostics/uri_does_not_exist_test.dart b/pkg/analyzer/test/src/diagnostics/uri_does_not_exist_test.dart
index 81707fb..25f897b 100644
--- a/pkg/analyzer/test/src/diagnostics/uri_does_not_exist_test.dart
+++ b/pkg/analyzer/test/src/diagnostics/uri_does_not_exist_test.dart
@@ -15,14 +15,6 @@
@reflectiveTest
class UriDoesNotExistTest extends PubPackageResolutionTest {
- test_augmentationImport() async {
- await assertErrorsInCode('''
-import augment 'unknown.dart';
-''', [
- error(CompileTimeErrorCode.URI_DOES_NOT_EXIST, 15, 14),
- ]);
- }
-
test_libraryExport() async {
await assertErrorsInCode('''
export 'unknown.dart';
diff --git a/pkg/dart2wasm/lib/code_generator.dart b/pkg/dart2wasm/lib/code_generator.dart
index e10b3e2..0120221 100644
--- a/pkg/dart2wasm/lib/code_generator.dart
+++ b/pkg/dart2wasm/lib/code_generator.dart
@@ -256,7 +256,8 @@
thisLocal = paramLocals[0];
w.RefType thisType = info.nonNullableType;
if (translator.needsConversion(paramLocals[0].type, thisType) &&
- !(cls == translator.ffiPointerClass ||
+ !(cls == translator.objectInfo.cls ||
+ cls == translator.ffiPointerClass ||
translator.isFfiCompound(cls))) {
preciseThisLocal = addLocal(thisType);
b.local_get(paramLocals[0]);
diff --git a/runtime/docs/async.md b/runtime/docs/async.md
new file mode 100644
index 0000000..34de12a
--- /dev/null
+++ b/runtime/docs/async.md
@@ -0,0 +1,549 @@
+# Suspendable Functions (`async`, `async*` and `sync*`)
+
+This document describes the implementation of _suspendable_ functions (functions with `async`,
+`async*` or `sync*` modifier) in Dart VM. The execution of such functions can be suspended in
+the middle at `await`/`yield`/`yield*` and resumed afterwards.
+
+When suspending a function, its local execution state (local variables and temporaries) is saved
+and the control is returned to the caller of the suspended function.
+When resuming a function, its local execution state is restored and execution continues within
+the suspendable function from the point where it was suspended.
+
+In order to minimize code size, the implementation is built using a variety of _stubs_ - reusable
+snippets of machine code generated by the VM/AOT.
+The high-level Dart logic used to implement suspendable functions (such as managing
+Futures/Streams/Iterators) is factored into helper Dart methods in core library.
+
+The rest of the document is organized as follows: first, general mechanisms for implementation of
+suspendable functions are described.
+After that, `async`, `async*` and `sync*` implementations are outlined using the general
+mechanisms introduced before.
+
+# Building blocks common to all suspendable functions
+
+## SuspendState objects
+
+SuspendState objects are allocated on the heap and encapsulate the saved state of a suspended
+function. When suspending a function, its local frame (including local variables, spill slots
+and expression stack) is copied from the stack to a SuspendState object on the heap.
+When resuming a function, the frame is recreated and copied back from the SuspendState object
+into the stack.
+
+SuspendState objects have variable size and keep frame in the "payload" following a few fixed
+fields.
+
+In addition to a stack frame, SuspendState records a PC in the code of the suspended function
+where execution was suspended and can be resumed.
+The PC is also used by GC to find a stack map and scan through the pointers in the copied frame.
+
+SuspendState object also holds data and callbacks specific to a particular kind of suspendable
+function.
+
+SuspendState object is allocated during the first suspension and can be reused for the subsequent
+suspensions of the same function.
+
+For the declaration of SuspendState see [object.h](https://github.com/dart-lang/sdk/blob/main/runtime/vm/object.h#:~:text=class%20SuspendState),
+UntaggedSuspendState is declared in [raw_object.h](https://github.com/dart-lang/sdk/blob/main/runtime/vm/raw_object.h#:~:text=class%20UntaggedSuspendState).
+
+There is also a corresponding Dart class `_SuspendState`, declared in [async_patch.dart](https://github.com/dart-lang/sdk/blob/main/sdk/lib/_internal/vm/lib/async_patch.dart#:~:text=class%20_SuspendState).
+It contains Dart methods which are used to customize implementation for a particular kind of
+suspendable function.
+
+## Frame of a suspendable function
+
+Suspendable functions are never inlined into other functions, so their local state is not mixed
+with the state of their callers (but other functions may be inlined into them).
+
+In order to have a single contiguous region of memory to copy during suspend/resume, parameters of
+suspendable functions are always copied into the local frame in the function prologue (see uses of
+`Function::MakesCopyOfParameters()` predicate).
+
+In order to keep and reuse SuspendState object, each suspendable function has an artificial local
+variable `:suspend_state` (see uses of `ParsedFunction::suspend_state_var()`), which is always
+allocated at the fixed offset in frame. It occupies the first local variable slot
+(`SuspendState::kSuspendStateVarIndex`) in case of unoptimized code or the first spill slot
+in case of optimized code (see `FlowGraphAllocator::AllocateSpillSlotForSuspendState`).
+The fixed location helps to find this variable in various stubs and runtime.
+
+## Prologue and InitSuspendableFunction stub
+
+At the very beginning of a suspendable function `null` is stored into `:suspend_state` variable.
+This guarantees that `:suspend_state` variable can be accessed any time by GC and exception
+handling.
+
+After checking bounds of type arguments and types of arguments, suspendable functions call
+InitSuspendableFunction stub.
+
+InitSuspendableFunction stub does the following:
+
+- It calls a static generic Dart method specific to a particular kind of suspendable function.
+ The argument of the stub is passed as type arguments to that method.
+ Dart method performs initialization specific to a particular kind of suspendable function
+ (for example, it creates `_Future<T>()` for async functions).
+ It returns the instance which is used as a function-specific data.
+
+- Stub puts the function-specific data to `:suspend_state` variable, where it can be found by
+ Suspend or Return stubs later.
+
+## Suspend stub
+
+Suspend stub is called from a suspendable function when its execution should be suspended.
+
+Suspend stub does the following:
+
+- It inspects `:suspend_state` variable and checks if it contains an instance of SuspendState.
+ If it doesn't, then stub allocates a new instance with a payload sufficient to hold a frame of
+ the suspendable function. The newly allocated SuspendState is stored into `:suspend_state`
+ variable, and previous value of `:suspend_state` (coming from InitSuspendableFunction stub) is
+ saved to `SuspendState.function_data`.
+
+- In JIT mode, size of the frame may vary over time - expression stack depth varies during
+ execution of unoptimized code and frame size may change during deoptimization and OSR.
+ In AOT mode size of the stack frame stays the same.
+ So, if stub finds an existing SuspendState object in JIT mode, it also checks if its frame
+ payload has a sufficient size to hold a frame of the suspendable function. If it is not
+ large enough, suspend stub calls `AllocateSuspendState` runtime entry to allocate a larger
+ SuspendState object. The same runtime entry is called for slow path when allocating
+ SuspendState for the first time.
+
+- The return address from Suspend stub to the suspendable function is saved to `SuspendState.pc`.
+ It will be used to resume execution later.
+
+- The contents of the stack frame of the suspendable function between FP and SP is copied into
+ SuspendState.
+
+- Write barrier: if SuspendState object resides in the old generation, then
+ EnsureRememberedAndMarkingDeferred runtime entry is called.
+
+- If implementation of particular kind of suspendable function uses a customized Dart method
+ for the suspension, then that method is called.
+ Suspend stub supports passing one argument to the customization method.
+ The result of the method is returned back to the caller of the suspendable function - it's
+ the result of the suspendable function.
+ If such method is not used, then Suspend stub returns its argument (so suspendable function
+ could customize its return value).
+
+- On architectures other than x64/ia32, the frame of the suspendable function is removed and
+ stub returns directly to the caller of the suspendable function.
+ On x64/ia32, in order to maintain call/return balance and avoid performance penalty,
+ Suspend stub returns to the suspendable function which immediately returns to its caller.
+
+For more details see `StubCodeCompiler::GenerateSuspendStub` in [stub_code_compiler.cc](https://github.com/dart-lang/sdk/blob/main/runtime/vm/compiler/stub_code_compiler.cc#:~:text=StubCodeCompiler::GenerateSuspendStub).
+
+## Resume stub
+
+Resume stub is tail-called from `_SuspendState._resume` recognized method (which is called
+from Dart helpers). It is used to resume execution of the previously suspended function.
+
+Resume stub does the following:
+
+- Allocates Dart frame on the stack, using `SuspendState.frame_size` to calculate its size.
+
+- Copies frame contents from SuspendState to the stack.
+
+- In JIT mode restores pool pointer (PP).
+
+- Checks for the following cases and calls ResumeFrame runtime entry if any of this is true:
+ + If resuming with an exception.
+ + In JIT mode, if Code of the suspendable function is disabled (deoptimized).
+ + In JIT mode, if there is a resumption breakpoint set by debugger.
+
+- Otherwise, jumps to `SuspendState.pc` to resume execution of the suspended function.
+ On x64/ia32 the continuation PC is adjusted by adding `SuspendStubABI::kResumePcDistance`
+ to skip over the epilogue which immediately follows the Suspend stub call to maintain
+ call/return balance.
+
+ResumeFrame runtime entry is called as if it was called from suspended function at continuation PC.
+It handles all corner cases by throwing an exception, lazy deoptimizing or calling into
+the debugger.
+
+For more details see `StubCodeCompiler::GenerateResumeStub` in [stub_code_compiler.cc](https://github.com/dart-lang/sdk/blob/main/runtime/vm/compiler/stub_code_compiler.cc#:~:text=StubCodeCompiler::GenerateResumeStub)
+and `ResumeFrame` in [runtime_entry.cc](https://github.com/dart-lang/sdk/blob/main/runtime/vm/runtime_entry.cc#:~:text=ResumeFrame).
+
+## Return stub
+
+Suspendable functions can use Return stub if they need to do something when execution of
+a function ends (for example, complete a Future or close a Stream). In such a case,
+suspendable function jumps to the Return stub instead of returning.
+
+Return stub does the following:
+
+- Removes the frame of the suspendable function (as if function epilogue was executed).
+
+- Calls a Dart method specific to a particular kind of suspendable function.
+ The customization method takes a value of `:suspend_state` variable and a return value
+ passed from the body of the suspendable function to the stub.
+
+- The value returned from the customization method is used as the result of
+ the suspendable function.
+
+For more details see `StubCodeCompiler::GenerateReturnStub` in [stub_code_compiler.cc](https://github.com/dart-lang/sdk/blob/main/runtime/vm/compiler/stub_code_compiler.cc#:~:text=StubCodeCompiler::GenerateReturnStub).
+
+## Exception handling and AsyncExceptionHandler stub
+
+Certain kinds of suspendable functions (async and async*) may need to catch all thrown exceptions
+which are not caught within the function body, and perform certain actions (such as completing
+the Future with an error).
+
+This is implemented by setting `has_async_handler` bit on `ExceptionHandlers` object.
+When looking for an exception handler, runtime checks if this bit is set and uses
+AsyncExceptionHandler stub as a handler (see `StackFrame::FindExceptionHandler`).
+
+AsyncExceptionHandler stub does the following:
+
+- It inspects the value of `:suspend_state` variable. If it is `null` (meaning the prologue has not
+ finished yet), the exception should not be handled and it is rethrown.
+ This makes it possible for argument type checks to throw an exception synchronously
+ instead of completing a Future with an error.
+
+- Otherwise, stub removes the frame of the suspendable function (as if function epilogue was
+ executed) and calls `_SuspendState._handleException` Dart method. AsyncExceptionHandler stub
+ does not use separate Dart helper methods for async and async* functions as exception handling is
+ not performance sensitive and currently uses only one bit in `ExceptionHandlers` to select
+ a stub handler for simplicity.
+
+- The value returned from `_SuspendState._handleException` is used as the result of the
+ suspendable function.
+
+For more details see `StubCodeCompiler::GenerateAsyncExceptionHandlerStub` in [stub_code_compiler.cc](https://github.com/dart-lang/sdk/blob/main/runtime/vm/compiler/stub_code_compiler.cc#:~:text=StubCodeCompiler::GenerateAsyncExceptionHandlerStub).
+
+## IL instructions
+
+When compiling suspendable functions, the following IL instructions are used:
+
+- `Call1ArgStub` instruction is used to call one-argument stubs such as InitSuspendableFunction.
+
+- `Suspend` instruction is used to call Suspend stub. After calling Suspend stub,
+ on x64/ia32 it also generates an epilogue right after the stub, in order to
+ return to the caller after suspending without disrupting call/return balance.
+ Due to this extra epilogue following the Suspend stub call, the resumption PC is
+ not the same as the return address of the Suspend stub. So `Suspend` instruction
+ uses 2 distinct deopt ids for the Suspend stub call and resumption PC.
+
+- `Return` instruction jumps to a Return stub instead of returning for certain kinds
+ of suspendable functions (async and async*).
+
+# Combining all pieces together
+
+## Async functions
+
+See [async_patch.dart](https://github.com/dart-lang/sdk/blob/main/sdk/lib/_internal/vm/lib/async_patch.dart) for the corresponding Dart source code.
+
+Async functions use the following customized stubs:
+
+### InitAsync stub
+
+InitAsync = InitSuspendableFunction stub which calls `_SuspendState._initAsync`.
+
+`_SuspendState._initAsync` creates a `_Future<T>` instance which is used as the result of
+the async function. This `_Future<T>` instance is kept in `:suspend_state` variable until
+`_SuspendState` instance is created during the first `await`, and then kept in
+`_SuspendState._functionData`. This instance is returned from `_SuspendState._await`,
+`_SuspendState._returnAsync`, `_SuspendState._returnAsyncNotFuture` and
+`_SuspendState._handleException` methods to serve as the result of the async function.
+
+### Await stub
+
+Await = Suspend stub which calls `_SuspendState._await`. It implements the `await` expression.
+
+`_SuspendState._await` allocates 'then' and 'error' callback closures when called for
+the first time. These callback closures resume execution of the async function via Resume stub.
+It is possible to create callbacks eagerly in the InitAsync stub, but there is a significant
+fraction of async functions which don't have `await` at all, so creating callbacks lazily during
+the first `await` makes those functions more efficient.
+If an argument of `await` is a Future, then `_SuspendState._await` attaches 'then' and 'error'
+callbacks to that Future. Otherwise it schedules a micro-task to continue execution of
+the suspended function later.
+
+### ReturnAsync stub
+
+ReturnAsync stub = Return stub which calls `_SuspendState._returnAsync`.
+It is used to implement `return` statement (either explicit or implicit when reaching
+the end of function).
+
+`_SuspendState._returnAsync` completes `_Future<T>` which is used as the result of
+the async function.
+
+### ReturnAsyncNotFuture stub
+
+ReturnAsyncNotFuture stub = Return stub which calls `_SuspendState._returnAsyncNotFuture`.
+
+ReturnAsyncNotFuture is similar to ReturnAsync, but used when compiler can prove that
+return value is not a Future. It bypasses the expensive `is Future` test.
+
+### Execution flow in async functions
+
+The following diagram depicts how the control is passed in a typical async function:
+
+```
+Caller Future<T> foo() async Stubs Dart _SuspendState methods
+ |
+ *-------------------> |
+ (prologue) -------------> InitAsync
+ |
+ *----------> _initAsync
+ (creates _Future<T>)
+ | <---------
+ | <-----------------------*
+ |
+ |
+ (await) ----------------> AwaitAsync
+ |
+ *----------> _await
+ (setups resumption)
+ (returns _Future<T>)
+ | <---------
+ | <---------------------------------------------*
+
+Awaited Future is completed
+ |
+ *------------------------------------------> Resume
+ |
+ (after await) <---------------*
+ |
+ |
+ (return) ---------------> ReturnAsync/ReturnAsyncNotFuture
+ |
+ *----------> _returnAsync/_returnAsyncNotFuture
+ (completes _Future<T>)
+ (returns _Future<T>)
+ | <---------
+ | <---------------------------------------------*
+```
+
+## Async* functions
+
+See [async_patch.dart](https://github.com/dart-lang/sdk/blob/main/sdk/lib/_internal/vm/lib/async_patch.dart)
+for the corresponding Dart source code.
+
+Async* functions use the following customized stubs:
+
+### InitAsyncStar stub
+
+InitAsyncStar = InitSuspendableFunction stub which calls `_SuspendState._initAsyncStar`.
+
+`_SuspendState._initAsyncStar` creates `_AsyncStarStreamController<T>` instance which is used
+to control the Stream returned from the async* function. `_AsyncStarStreamController<T>` is kept
+in `_SuspendState._functionData` (after the first suspension at the beginning of async* function).
+
+## YieldAsyncStar stub and `yield`/`yield*`
+
+YieldAsyncStar = Suspend stub which calls `_SuspendState._yieldAsyncStar`.
+
+This stub is used to suspend async* function at the beginning (until listener is attached to
+the Stream returned from async* function), and at `yield` / `yield*` statements.
+
+When `_SuspendState._yieldAsyncStar` is called at the beginning of async* function it creates
+a callback closure to resume body of the async* function (via Resume stub), creates and
+returns `Stream`.
+
+`yield` / `yield*` statements are implemented in the following way:
+
+```
+_AsyncStarStreamController controller = :suspend_state._functionData;
+if (controller.add/addStream(<expr>)) {
+ return;
+}
+if (YieldAsyncStar()) {
+ return;
+}
+```
+
+`_AsyncStarStreamController.add`, `_AsyncStarStreamController.addStream` and YieldAsyncStar stub
+can return `true` to indicate that Stream doesn't have a listener anymore and execution of
+async* function should end.
+
+Note that YieldAsyncStar stub returns a value passed to a Resume stub when resuming async*
+function, so the 2nd hasListeners check happens right before the async* function is resumed.
+
+See `StreamingFlowGraphBuilder::BuildYieldStatement` for more details about `yield` / `yield*`.
+
+### Await stub
+
+Async* functions use the same Await stub which is used by async functions.
+
+### ReturnAsyncStar stub
+
+ReturnAsyncStar stub = Return stub which calls `_SuspendState._returnAsyncStar`.
+
+`_SuspendState._returnAsyncStar` closes the Stream.
+
+### Execution flow in async* functions
+
+The following diagram depicts how the control is passed in a typical async* function:
+
+```
+Caller Stream<T> foo() async* Stubs Dart helper methods
+ |
+ *-------------------> |
+ (prologue) -------------> InitAsyncStar
+ |
+ *----------> _SuspendState._initAsyncStar
+ (creates _AsyncStarStreamController<T>)
+ | <---------
+ | <-----------------------*
+ * ------------------> YieldAsyncStar
+ |
+ *----------> _SuspendState._yieldAsyncStar
+ (setups resumption)
+ (returns _AsyncStarStreamController.stream)
+ | <---------
+ | <---------------------------------------------*
+
+Stream is listened
+ |
+ *------------------------------------------> Resume
+ |
+ (after prologue) <--------------*
+ |
+ |
+ (yield) --------------------------------> _AsyncStarStreamController.add
+ (adds value to Stream)
+ (checks if there are listeners)
+ | <-----------------------------------
+ * ------------------> YieldAsyncStar
+ |
+ *----------> _SuspendState._yieldAsyncStar
+ | <---------
+ | <---------------------------------------------*
+
+Micro-task to run async* body
+ |
+ *----------------------------------------------------------> _AsyncStarStreamController.runBody
+ (checks if there are listeners)
+ Resume <-------
+ |
+ (after yield) <---------------*
+ |
+ |
+ (return) ---------------> ReturnAsyncStar
+ |
+ *----------> _SuspendState._returnAsyncStar
+ (closes _AsyncStarStreamController)
+ | <---------
+ | <---------------------------------------------*
+```
+
+## Sync* functions
+
+See [async_patch.dart](https://github.com/dart-lang/sdk/blob/main/sdk/lib/_internal/vm/lib/async_patch.dart)
+for the corresponding Dart source code.
+
+Sync* functions use the following customized stubs:
+
+### InitSyncStar stub
+
+InitSyncStar = InitSuspendableFunction stub which calls `_SuspendState._initSyncStar`.
+
+`_SuspendState._initSyncStar` creates a `_SyncStarIterable<T>` instance which is returned
+from sync* function.
+
+### SuspendSyncStarAtStart stub
+
+SuspendSyncStarAtStart = Suspend stub which calls `_SuspendState._suspendSyncStarAtStart`.
+
+This stub is used to suspend execution of sync* at the beginning. It is called after
+InitSyncStar in the sync* function prologue. The body of sync* function doesn't run
+until Iterator is not obtained from Iterable (`_SyncStarIterable<T>`) which is returned from
+the sync* function.
+
+### CloneSuspendState stub
+
+This stub creates a copy of SuspendState object. It is used to clone state of sync*
+function (suspended at the beginning) for each Iterator instance obtained from
+Iterable.
+
+See `StubCodeCompiler::GenerateCloneSuspendStateStub`.
+
+### SuspendSyncStarAtYield stub and `yield`/`yield*`
+
+SuspendSyncStarAtYield = Suspend stub which doesn't call helper Dart methods.
+
+SuspendSyncStarAtYield is used to implement `yield` / `yield*` statements in sync* functions.
+
+`yield` / `yield*` statements are implemented in the following way:
+
+```
+_SyncStarIterator iterator = :suspend_state._functionData;
+
+iterator._current = <expr>; // yield <expr>
+ OR
+iterator._yieldStarIterable = <expr>; // yield* <expr>
+
+SuspendSyncStarAtYield(true);
+```
+
+See `StreamingFlowGraphBuilder::BuildYieldStatement` for more details about `yield` / `yield*`.
+
+The value passed to SuspendSyncStarAtYield is returned back from the invocation of
+Resume stub. `true` indicates that iteration can continue.
+
+### Returning from sync* functions.
+
+Sync* function do not use Return stubs. Instead, return statements are rewritten to return `false`
+in order to indicate that iteration is finished.
+
+### Execution flow in sync* functions
+
+The following diagram depicts how the control is passed in a typical sync* function:
+
+```
+Caller Iterable<T> foo() sync* Stubs Dart helpers
+ |
+ *-------------------> |
+ (prologue) -------------> InitSyncStar
+ |
+ *----------> _SuspendState._initSyncStar
+ (creates _SyncStarIterable<T>)
+ | <---------
+ | <-----------------------*
+ * ------------------> SuspendSyncStarAtStart
+ |
+ *----------> _SuspendState._suspendSyncStarAtStart
+ (remembers _SuspendState at start)
+ (returns _SyncStarIterable<T>)
+ | <---------
+ | <---------------------------------------------*
+
+Iterable.iterator is called
+ |
+ *----------------------------------------------------------> _SyncStarIterable<T>.iterator
+ (creates _SyncStarIterator<T>)
+ |
+ CloneSuspendState <-------*
+ (makes a copy of _SuspendState at start)
+ |
+ *-----------> |
+ | <------------------------------------------------------- (returns _SyncStarIterator<T>)
+
+Iterator.moveNext is called
+ |
+ *----------------------------------------------------------> _SyncStarIterator<T>.moveNext
+ (iterates over the cached yield* iterator, if any)
+ (resumes sync* body to get the next element)
+ Resume <-------
+ |
+ (after prologue) <--------------*
+ |
+ |
+ (yield) ---------------> SuspendSyncStarAtYield(true)
+ |
+ *---------->
+ (the next element is cached in _SyncStarIterator<T>._current)
+ (returns true indicating that the next element is available)
+ | <----------------------------------------------------------
+
+Iterator.moveNext is called
+ |
+ *----------------------------------------------------------> _SyncStarIterator<T>.moveNext
+ (iterates over the cached yield* iterator, if any)
+ (resumes sync* body to get the next element)
+ Resume <-------
+ |
+ (after yield) <-----------------*
+ |
+ |
+ (return false) ----------------------------->
+ (returns false indicating that iteration is finished)
+ | <----------------------------------------------------------
+```
diff --git a/runtime/vm/debugger.cc b/runtime/vm/debugger.cc
index ac54b5f..818a051 100644
--- a/runtime/vm/debugger.cc
+++ b/runtime/vm/debugger.cc
@@ -949,12 +949,11 @@
return Object::null();
}
- if (function().NumOptionalParameters() > 0) {
- // If the function has optional parameters, the first positional parameter
- // can be in a number of places in the caller's frame depending on how many
- // were actually supplied at the call site, but they are copied to a fixed
- // place in the callee's frame.
-
+ if (function().MakesCopyOfParameters()) {
+ // Function parameters are copied to a fixed place in the callee's frame.
+ if (function().IsSuspendableFunction()) {
+ ++index; // Skip slot reserved for :suspend_state variable.
+ }
return GetVariableValue(LocalVarAddress(
fp(), runtime_frame_layout.FrameSlotForVariableIndex(-index)));
} else {
diff --git a/sdk/lib/_internal/wasm/lib/object_patch.dart b/sdk/lib/_internal/wasm/lib/object_patch.dart
index 8b727d2..e61e064 100644
--- a/sdk/lib/_internal/wasm/lib/object_patch.dart
+++ b/sdk/lib/_internal/wasm/lib/object_patch.dart
@@ -39,7 +39,7 @@
/// Concrete subclasses of [Object] will have overrides of [_typeArguments]
/// which return their type arguments.
- List<Type> get _typeArguments => const [];
+ List<_Type> get _typeArguments => const [];
/// We use [_runtimeType] for internal type testing, because objects can
/// override [runtimeType].
diff --git a/tools/VERSION b/tools/VERSION
index fe46c13..e499039 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
MAJOR 2
MINOR 19
PATCH 0
-PRERELEASE 22
+PRERELEASE 23
PRERELEASE_PATCH 0
\ No newline at end of file