Version 2.10.0-144.0.dev
Merge commit '9ad182a528c18753a2a0f887b0e36ef37013932e' into 'dev'
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9b56ce5..98626d0 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -54,8 +54,12 @@
#### Linter
-Updated the Linter to `0.1.118`, which includes:
+Updated the Linter to `0.1.119`, which includes:
+* Fixed `close_sinks` to handle `this`-prefixed property accesses.
+* New lint: `unnecessary_null_checks`.
+* Fixed `unawaited_futures` to handle `Future` subtypes.
+* New lint: `avoid_type_to_string`.
* New lint: `unnecessary_nullable_for_final_variable_declarations`.
* Fixed NPE in `prefer_asserts_in_initializer_lists`.
* Fixed range error in `unnecessary_string_escapes`.
diff --git a/DEPS b/DEPS
index 5271f55..43df7a6 100644
--- a/DEPS
+++ b/DEPS
@@ -44,7 +44,7 @@
# co19 is a cipd package. Use update.sh in tests/co19[_2] to update these
# hashes. It requires access to the dart-build-access group, which EngProd
# has.
- "co19_rev": "e8c8b7edab7df1a73c0215da7d458acd0f6c1ef3",
+ "co19_rev": "827f96b4cddf68cd12049ad5612c53e215b58fcf",
"co19_2_rev": "e48b3090826cf40b8037648f19d211e8eab1b4b6",
# The internal benchmarks to use. See go/dart-benchmarks-internal
@@ -113,7 +113,7 @@
"intl_tag": "0.16.1",
"jinja2_rev": "2222b31554f03e62600cd7e383376a7c187967a1",
"json_rpc_2_rev": "8f189db8f0c299187a0e8fa959dba7e9b0254be5",
- "linter_tag": "0.1.118",
+ "linter_tag": "0.1.119",
"logging_rev": "9561ba016ae607747ae69b846c0e10958ca58ed4",
"markupsafe_rev": "8f45f5cfa0009d2a70589bcda0349b8cb2b72783",
"markdown_rev": "dbeafd47759e7dd0a167602153bb9c49fb5e5fe7",
diff --git a/pkg/analysis_server/lib/src/services/completion/postfix/postfix_completion.dart b/pkg/analysis_server/lib/src/services/completion/postfix/postfix_completion.dart
index 403fbb3..b648f7f 100644
--- a/pkg/analysis_server/lib/src/services/completion/postfix/postfix_completion.dart
+++ b/pkg/analysis_server/lib/src/services/completion/postfix/postfix_completion.dart
@@ -8,10 +8,12 @@
import 'package:analyzer/dart/analysis/session.dart';
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/element/element.dart';
+import 'package:analyzer/dart/element/nullability_suffix.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:analyzer/dart/element/type_provider.dart';
import 'package:analyzer/dart/element/type_system.dart';
import 'package:analyzer/src/dart/ast/utilities.dart';
+import 'package:analyzer/src/dart/element/type.dart';
import 'package:analyzer/src/generated/java_core.dart';
import 'package:analyzer/src/generated/source.dart';
import 'package:analyzer_plugin/utilities/change_builder/change_builder_core.dart';
@@ -444,7 +446,18 @@
if (astNode is ThrowExpression) {
var expr = astNode;
var type = expr.expression.staticType;
- return type.getDisplayString(withNullability: false);
+
+ // Only print nullability for non-legacy types in non-legacy libraries.
+ var showNullability = type.nullabilitySuffix != NullabilitySuffix.star &&
+ (astNode.root as CompilationUnit)
+ .declaredElement
+ .library
+ .isNonNullableByDefault;
+
+ // Can't catch nullable types, strip `?`s now that we've checked for `*`s.
+ return (type as TypeImpl)
+ .withNullability(NullabilitySuffix.none)
+ .getDisplayString(withNullability: showNullability);
}
return 'Exception';
}
diff --git a/pkg/analysis_server/test/services/completion/postfix/postfix_completion_test.dart b/pkg/analysis_server/test/services/completion/postfix/postfix_completion_test.dart
index 44c2b39..07d36a2 100644
--- a/pkg/analysis_server/test/services/completion/postfix/postfix_completion_test.dart
+++ b/pkg/analysis_server/test/services/completion/postfix/postfix_completion_test.dart
@@ -4,6 +4,7 @@
import 'package:analysis_server/src/protocol_server.dart';
import 'package:analysis_server/src/services/completion/postfix/postfix_completion.dart';
+import 'package:analyzer/src/dart/analysis/experiments.dart';
import 'package:test/test.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';
@@ -708,6 +709,162 @@
''');
}
+ Future<void> test_tryonThrowStatement_nnbd() async {
+ createAnalysisOptionsFile(experiments: [EnableString.non_nullable]);
+ await _prepareCompletion('.tryon', '''
+f() {
+ throw 'error';.tryon
+}
+''');
+ _assertHasChange('Expand .tryon', '''
+f() {
+ try {
+ throw 'error';/*caret*/
+ } on String catch (e, s) {
+ print(s);
+ }
+}
+''');
+ }
+
+ Future<void> test_tryonThrowStatement_nnbd_into_legacy() async {
+ createAnalysisOptionsFile(experiments: [EnableString.non_nullable]);
+ newFile('/home/test/lib/a.dart', content: r'''
+String? x;
+''');
+ await _prepareCompletion('.tryon', '''
+// @dart = 2.8
+import 'a.dart';
+f() {
+ throw x;.tryon
+}
+''');
+ _assertHasChange('Expand .tryon', '''
+// @dart = 2.8
+import 'a.dart';
+f() {
+ try {
+ throw x;/*caret*/
+ } on String catch (e, s) {
+ print(s);
+ }
+}
+''');
+ }
+
+ Future<void> test_tryonThrowStatement_nnbd_into_legacy_nested() async {
+ createAnalysisOptionsFile(experiments: [EnableString.non_nullable]);
+ newFile('/home/test/lib/a.dart', content: r'''
+List<String?> x;
+''');
+ await _prepareCompletion('.tryon', '''
+// @dart = 2.8
+import 'a.dart';
+f() {
+ throw x;.tryon
+}
+''');
+ _assertHasChange('Expand .tryon', '''
+// @dart = 2.8
+import 'a.dart';
+f() {
+ try {
+ throw x;/*caret*/
+ } on List<String> catch (e, s) {
+ print(s);
+ }
+}
+''');
+ }
+
+ Future<void> test_tryonThrowStatement_nnbd_legacy() async {
+ createAnalysisOptionsFile(experiments: [EnableString.non_nullable]);
+ newFile('/home/test/lib/a.dart', content: r'''
+// @dart = 2.8
+String x;
+''');
+ await _prepareCompletion('.tryon', '''
+import 'a.dart';
+f() {
+ throw x;.tryon
+}
+''');
+ _assertHasChange('Expand .tryon', '''
+import 'a.dart';
+f() {
+ try {
+ throw x;/*caret*/
+ } on String catch (e, s) {
+ print(s);
+ }
+}
+''');
+ }
+
+ Future<void> test_tryonThrowStatement_nnbd_legacy_nested() async {
+ createAnalysisOptionsFile(experiments: [EnableString.non_nullable]);
+ newFile('/home/test/lib/a.dart', content: r'''
+// @dart = 2.8
+List<String> x;
+''');
+ await _prepareCompletion('.tryon', '''
+import 'a.dart';
+f() {
+ throw x;.tryon
+}
+''');
+ _assertHasChange('Expand .tryon', '''
+import 'a.dart';
+f() {
+ try {
+ throw x;/*caret*/
+ } on List<String> catch (e, s) {
+ print(s);
+ }
+}
+''');
+ }
+
+ Future<void> test_tryonThrowStatement_nnbd_nullable() async {
+ createAnalysisOptionsFile(experiments: [EnableString.non_nullable]);
+ await _prepareCompletion('.tryon', '''
+f() {
+ String? x;
+ throw x;.tryon
+}
+''');
+ _assertHasChange('Expand .tryon', '''
+f() {
+ String? x;
+ try {
+ throw x;/*caret*/
+ } on String catch (e, s) {
+ print(s);
+ }
+}
+''');
+ }
+
+ Future<void> test_tryonThrowStatement_nnbd_nullable_nested() async {
+ createAnalysisOptionsFile(experiments: [EnableString.non_nullable]);
+ await _prepareCompletion('.tryon', '''
+f() {
+ List<String?>? x;
+ throw x;.tryon
+}
+''');
+ _assertHasChange('Expand .tryon', '''
+f() {
+ List<String?>? x;
+ try {
+ throw x;/*caret*/
+ } on List<String?> catch (e, s) {
+ print(s);
+ }
+}
+''');
+ }
+
Future<void> test_tryonThrowString() async {
await _prepareCompletion('.tryon', '''
f() {
diff --git a/pkg/analyzer/lib/file_system/memory_file_system.dart b/pkg/analyzer/lib/file_system/memory_file_system.dart
index 091943f..a139b26 100644
--- a/pkg/analyzer/lib/file_system/memory_file_system.dart
+++ b/pkg/analyzer/lib/file_system/memory_file_system.dart
@@ -20,6 +20,7 @@
HashMap<String, _MemoryResource>();
final Map<String, Uint8List> _pathToBytes = HashMap<String, Uint8List>();
final Map<String, int> _pathToTimestamp = HashMap<String, int>();
+ final Map<String, String> _pathToLinkedPath = {};
final Map<String, List<StreamController<WatchEvent>>> _pathToWatchers =
HashMap<String, List<StreamController<WatchEvent>>>();
int nextStamp = 0;
@@ -176,6 +177,13 @@
}
}
+ /// Create a link from the [path] to the [target].
+ void newLink(String path, String target) {
+ _ensureAbsoluteAndNormalized(path);
+ _ensureAbsoluteAndNormalized(target);
+ _pathToLinkedPath[path] = target;
+ }
+
File updateFile(String path, String content, [int stamp]) {
_ensureAbsoluteAndNormalized(path);
newFolder(pathContext.dirname(path));
@@ -276,6 +284,30 @@
return newFile;
}
+ String _resolveLinks(String path) {
+ var linkTarget = _pathToLinkedPath[path];
+ if (linkTarget != null) {
+ return linkTarget;
+ }
+
+ var parentPath = _pathContext.dirname(path);
+ if (parentPath == path) {
+ return path;
+ }
+
+ var canonicalParentPath = _resolveLinks(parentPath);
+
+ var baseName = _pathContext.basename(path);
+ var result = _pathContext.join(canonicalParentPath, baseName);
+
+ linkTarget = _pathToLinkedPath[result];
+ if (linkTarget != null) {
+ return linkTarget;
+ }
+
+ return result;
+ }
+
void _setFileContent(_MemoryFile file, List<int> bytes) {
String path = file.path;
_pathToResource[path] = file;
@@ -370,7 +402,10 @@
: super(provider, path);
@override
- bool get exists => provider._pathToResource[path] is _MemoryFile;
+ bool get exists {
+ var canonicalPath = provider._resolveLinks(path);
+ return provider._pathToResource[canonicalPath] is _MemoryFile;
+ }
@override
int get lengthSync {
@@ -379,7 +414,8 @@
@override
int get modificationStamp {
- int stamp = provider._pathToTimestamp[path];
+ var canonicalPath = provider._resolveLinks(path);
+ int stamp = provider._pathToTimestamp[canonicalPath];
if (stamp == null) {
throw FileSystemException(path, 'File "$path" does not exist.');
}
@@ -412,7 +448,8 @@
@override
Uint8List readAsBytesSync() {
- Uint8List content = provider._pathToBytes[path];
+ var canonicalPath = provider._resolveLinks(path);
+ Uint8List content = provider._pathToBytes[canonicalPath];
if (content == null) {
throw FileSystemException(path, 'File "$path" does not exist.');
}
@@ -421,7 +458,8 @@
@override
String readAsStringSync() {
- Uint8List content = provider._pathToBytes[path];
+ var canonicalPath = provider._resolveLinks(path);
+ Uint8List content = provider._pathToBytes[canonicalPath];
if (content == null) {
throw FileSystemException(path, 'File "$path" does not exist.');
}
@@ -434,7 +472,10 @@
}
@override
- File resolveSymbolicLinksSync() => this;
+ File resolveSymbolicLinksSync() {
+ var canonicalPath = provider._resolveLinks(path);
+ return provider.getFile(canonicalPath);
+ }
@override
void writeAsBytesSync(List<int> bytes) {
@@ -538,7 +579,10 @@
}
@override
- Folder resolveSymbolicLinksSync() => this;
+ Folder resolveSymbolicLinksSync() {
+ var canonicalPath = provider._resolveLinks(path);
+ return provider.getFolder(canonicalPath);
+ }
@override
Uri toUri() => provider.pathContext.toUri(path + '/');
diff --git a/pkg/analyzer/lib/src/dart/element/replacement_visitor.dart b/pkg/analyzer/lib/src/dart/element/replacement_visitor.dart
index c550ccd..d636bf2 100644
--- a/pkg/analyzer/lib/src/dart/element/replacement_visitor.dart
+++ b/pkg/analyzer/lib/src/dart/element/replacement_visitor.dart
@@ -25,6 +25,7 @@
DartType createFunctionType({
@required FunctionType type,
+ @required List<DartType> newTypeArguments,
@required List<TypeParameterElement> newTypeParameters,
@required List<ParameterElement> newParameters,
@required DartType newReturnType,
@@ -41,6 +42,8 @@
parameters: newParameters ?? type.parameters,
returnType: newReturnType ?? type.returnType,
nullabilitySuffix: newNullability ?? type.nullabilitySuffix,
+ element: type.element,
+ typeArguments: newTypeArguments ?? type.typeArguments,
);
}
@@ -160,6 +163,15 @@
var newReturnType = visitType(node.returnType);
+ List<DartType> newTypeArguments;
+ for (var i = 0; i < node.typeArguments.length; i++) {
+ var substitution = node.typeArguments[i].accept(this);
+ if (substitution != null) {
+ newTypeArguments ??= node.typeArguments.toList(growable: false);
+ newTypeArguments[i] = substitution;
+ }
+ }
+
changeVariance();
List<ParameterElement> newParameters;
@@ -186,6 +198,7 @@
return createFunctionType(
type: node,
+ newTypeArguments: newTypeArguments,
newTypeParameters: newTypeParameters,
newParameters: newParameters,
newReturnType: newReturnType,
diff --git a/pkg/analyzer/lib/src/error/best_practices_verifier.dart b/pkg/analyzer/lib/src/error/best_practices_verifier.dart
index 8809534..24c9c85 100644
--- a/pkg/analyzer/lib/src/error/best_practices_verifier.dart
+++ b/pkg/analyzer/lib/src/error/best_practices_verifier.dart
@@ -1892,7 +1892,7 @@
case TargetKind.function:
return 'top-level functions';
case TargetKind.library:
- return 'librarys';
+ return 'libraries';
case TargetKind.getter:
return 'getters';
case TargetKind.method:
diff --git a/pkg/analyzer/lib/src/services/available_declarations.dart b/pkg/analyzer/lib/src/services/available_declarations.dart
index b7ea31c..b19999a 100644
--- a/pkg/analyzer/lib/src/services/available_declarations.dart
+++ b/pkg/analyzer/lib/src/services/available_declarations.dart
@@ -704,15 +704,26 @@
_File _getFileByUri(DeclarationsContext context, Uri uri) {
var file = _uriToFile[uri];
- if (file == null) {
- var path = context._resolveUri(uri);
- if (path != null) {
- file = _File(this, path, uri);
- _pathToFile[path] = file;
- _uriToFile[uri] = file;
- file.refresh(context);
- }
+ if (file != null) {
+ return file;
}
+
+ var path = context._resolveUri(uri);
+ if (path == null) {
+ return null;
+ }
+
+ path = _resolveLinks(path);
+ file = _pathToFile[path];
+ if (file != null) {
+ return file;
+ }
+
+ file = _File(this, path, uri);
+ _pathToFile[path] = file;
+ _uriToFile[uri] = file;
+
+ file.refresh(context);
return file;
}
@@ -791,6 +802,13 @@
LibraryChange._(changedLibraries, removedLibraries),
);
}
+
+ /// Return the [path] with resolved file system links.
+ String _resolveLinks(String path) {
+ var resource = _resourceProvider.getFile(path);
+ resource = resource.resolveSymbolicLinksSync();
+ return resource.path;
+ }
}
class Libraries {
diff --git a/pkg/analyzer/test/file_system/memory_file_system_test.dart b/pkg/analyzer/test/file_system/memory_file_system_test.dart
index 00d30bc..a353328 100644
--- a/pkg/analyzer/test/file_system/memory_file_system_test.dart
+++ b/pkg/analyzer/test/file_system/memory_file_system_test.dart
@@ -251,12 +251,16 @@
expect(newFile.readAsStringSync(), defaultFileContent);
}
- @failingTest
@override
test_resolveSymbolicLinksSync_links_existing() {
- // TODO(brianwilkerson) Decide how to test this given that we cannot
- // create a link in a MemoryResourceProvider.
- fail('Not tested');
+ var a = provider.convertPath('/test/lib/a.dart');
+ var b = provider.convertPath('/test/lib/b.dart');
+
+ provider.newLink(b, a);
+ provider.newFile(a, 'aaa');
+
+ var resolved = provider.getFile(b).resolveSymbolicLinksSync();
+ expect(resolved.path, a);
}
@override
@@ -286,7 +290,18 @@
}
@reflectiveTest
-class MemoryFolderTest extends BaseTest with FolderTestMixin {}
+class MemoryFolderTest extends BaseTest with FolderTestMixin {
+ test_resolveSymbolicLinksSync() {
+ var lib = provider.convertPath('/test/lib');
+ var foo = provider.convertPath('/test/lib/foo');
+
+ provider.newLink(foo, lib);
+ provider.newFolder(lib);
+
+ var resolved = provider.getFolder(foo).resolveSymbolicLinksSync();
+ expect(resolved.path, lib);
+ }
+}
@reflectiveTest
class MemoryResourceProviderTest extends BaseTest
@@ -369,6 +384,36 @@
expect(() => provider.newFolder('not/absolute'), throwsArgumentError);
}
+ test_newLink_folder() {
+ provider.newLink(
+ provider.convertPath('/test/lib/foo'),
+ provider.convertPath('/test/lib'),
+ );
+
+ provider.newFile(
+ provider.convertPath('/test/lib/a.dart'),
+ 'aaa',
+ );
+
+ {
+ var path = '/test/lib/foo/a.dart';
+ var convertedPath = provider.convertPath(path);
+ var file = provider.getFile(convertedPath);
+ expect(file.exists, true);
+ expect(file.modificationStamp, isNonNegative);
+ expect(file.readAsStringSync(), 'aaa');
+ }
+
+ {
+ var path = '/test/lib/foo/foo/a.dart';
+ var convertedPath = provider.convertPath(path);
+ var file = provider.getFile(convertedPath);
+ expect(file.exists, true);
+ expect(file.modificationStamp, isNonNegative);
+ expect(file.readAsStringSync(), 'aaa');
+ }
+ }
+
@override
test_pathContext() {
if (path.style == path.Style.windows) {
diff --git a/pkg/analyzer/test/src/dart/resolution/resolution.dart b/pkg/analyzer/test/src/dart/resolution/resolution.dart
index cb4d4f8..003a597 100644
--- a/pkg/analyzer/test/src/dart/resolution/resolution.dart
+++ b/pkg/analyzer/test/src/dart/resolution/resolution.dart
@@ -350,6 +350,17 @@
assertType(node, type);
}
+ /// We have a contract with the Angular team that FunctionType(s) from
+ /// typedefs carry the element of the typedef, and the type arguments.
+ void assertFunctionTypeTypedef(
+ FunctionType type, {
+ @required FunctionTypeAliasElement element,
+ @required List<String> typeArguments,
+ }) {
+ assertElement2(type.element, declaration: element.function);
+ assertElementTypeStrings(type.typeArguments, typeArguments);
+ }
+
void assertHasTestErrors() {
expect(result.errors, isNotEmpty);
}
diff --git a/pkg/analyzer/test/src/dart/resolution/type_name_test.dart b/pkg/analyzer/test/src/dart/resolution/type_name_test.dart
index f50eea9..502c43d 100644
--- a/pkg/analyzer/test/src/dart/resolution/type_name_test.dart
+++ b/pkg/analyzer/test/src/dart/resolution/type_name_test.dart
@@ -382,10 +382,15 @@
f(F a) {}
''');
- assertTypeName(
- findNode.typeName('F a'),
- import_a.functionTypeAlias('F'),
- 'int* Function(bool*)*',
+ var element = import_a.functionTypeAlias('F');
+
+ var typeName = findNode.typeName('F a');
+ assertTypeName(typeName, element, 'int* Function(bool*)*');
+
+ assertFunctionTypeTypedef(
+ typeName.type,
+ element: element,
+ typeArguments: [],
);
}
@@ -401,10 +406,15 @@
f(F a) {}
''');
- assertTypeName(
- findNode.typeName('F a'),
- import_a.functionTypeAlias('F'),
- 'dynamic Function(bool*)*',
+ var element = import_a.functionTypeAlias('F');
+
+ var typeName = findNode.typeName('F a');
+ assertTypeName(typeName, element, 'dynamic Function(bool*)*');
+
+ assertFunctionTypeTypedef(
+ typeName.type,
+ element: element,
+ typeArguments: ['dynamic'],
);
}
@@ -420,10 +430,15 @@
f(F a) {}
''');
- assertTypeName(
- findNode.typeName('F a'),
- import_a.functionTypeAlias('F'),
- 'num* Function(bool*)*',
+ var element = import_a.functionTypeAlias('F');
+
+ var typeName = findNode.typeName('F a');
+ assertTypeName(typeName, element, 'num* Function(bool*)*');
+
+ assertFunctionTypeTypedef(
+ typeName.type,
+ element: element,
+ typeArguments: ['num*'],
);
}
@@ -439,10 +454,15 @@
f(F<int> a) {}
''');
- assertTypeName(
- findNode.typeName('F<int> a'),
- import_a.functionTypeAlias('F'),
- 'int* Function(bool*)*',
+ var element = import_a.functionTypeAlias('F');
+
+ var typeName = findNode.typeName('F<int> a');
+ assertTypeName(typeName, element, 'int* Function(bool*)*');
+
+ assertFunctionTypeTypedef(
+ typeName.type,
+ element: element,
+ typeArguments: ['int*'],
);
}
diff --git a/pkg/analyzer/test/src/services/available_declarations_test.dart b/pkg/analyzer/test/src/services/available_declarations_test.dart
index e21ccd8..0855c5d 100644
--- a/pkg/analyzer/test/src/services/available_declarations_test.dart
+++ b/pkg/analyzer/test/src/services/available_declarations_test.dart
@@ -201,6 +201,22 @@
expect(library.uriStr, 'package:test/test.dart');
}
+ test_getLibrary_exportViaRecursiveLink() async {
+ resourceProvider.newLink('/home/test/lib/foo', '/home/test/lib');
+
+ newFile('/home/test/lib/a.dart', content: r'''
+export 'foo/a.dart';
+class A {}
+''');
+ tracker.addContext(testAnalysisContext);
+
+ await _doAllTrackerWork();
+
+ var id = uriToLibrary['package:test/a.dart'].id;
+ var library = tracker.getLibrary(id);
+ expect(library.id, id);
+ }
+
test_readByteStore() async {
newFile('/home/test/lib/a.dart', content: r'''
class A {}
diff --git a/pkg/compiler/lib/src/common_elements.dart b/pkg/compiler/lib/src/common_elements.dart
index 584fb51..7b7387c 100644
--- a/pkg/compiler/lib/src/common_elements.dart
+++ b/pkg/compiler/lib/src/common_elements.dart
@@ -441,6 +441,8 @@
FunctionEntity get defineProperty;
+ FunctionEntity get throwLateInitializationError;
+
bool isExtractTypeArguments(FunctionEntity member);
ClassEntity getInstantiationClass(int typeArgumentCount);
@@ -1778,6 +1780,10 @@
FunctionEntity get defineProperty => _findHelperFunction('defineProperty');
@override
+ FunctionEntity get throwLateInitializationError =>
+ _findHelperFunction('throwLateInitializationError');
+
+ @override
bool isExtractTypeArguments(FunctionEntity member) {
return member.name == 'extractTypeArguments' &&
member.library == internalLibrary;
diff --git a/pkg/compiler/lib/src/js_backend/backend_impact.dart b/pkg/compiler/lib/src/js_backend/backend_impact.dart
index cbb0bdd..383ed44 100644
--- a/pkg/compiler/lib/src/js_backend/backend_impact.dart
+++ b/pkg/compiler/lib/src/js_backend/backend_impact.dart
@@ -402,8 +402,10 @@
BackendImpact _lazyField;
BackendImpact get lazyField {
- return _lazyField ??=
- new BackendImpact(staticUses: [_commonElements.cyclicThrowHelper]);
+ return _lazyField ??= new BackendImpact(staticUses: [
+ _commonElements.cyclicThrowHelper,
+ _commonElements.throwLateInitializationError
+ ]);
}
BackendImpact _typeLiteral;
diff --git a/pkg/compiler/lib/src/js_emitter/startup_emitter/fragment_emitter.dart b/pkg/compiler/lib/src/js_emitter/startup_emitter/fragment_emitter.dart
index 4ec53ba..597f756 100644
--- a/pkg/compiler/lib/src/js_emitter/startup_emitter/fragment_emitter.dart
+++ b/pkg/compiler/lib/src/js_emitter/startup_emitter/fragment_emitter.dart
@@ -168,6 +168,27 @@
};
}
+// Creates a lazy final field that uses non-nullable initialization semantics.
+//
+// A lazy field has a storage entry, [name], which holds the value, and a
+// getter ([getterName]) to access the field. If the field wasn't set before
+// the first access, it is initialized with the [initializer].
+function lazyFinal(holder, name, getterName, initializer) {
+ var uninitializedSentinel = holder;
+ holder[name] = uninitializedSentinel;
+ holder[getterName] = function() {
+ if (holder[name] === uninitializedSentinel) {
+ var value = initializer();
+ if (holder[name] !== uninitializedSentinel) {
+ #throwLateInitializationError(name);
+ }
+ holder[name] = value;
+ }
+ holder[getterName] = function() { return this[name]; };
+ return holder[name];
+ };
+}
+
// Given a list, marks it as constant.
//
// The runtime ensures that const-lists cannot be modified.
@@ -369,6 +390,7 @@
makeConstList: makeConstList,
lazy: lazy,
+ lazyFinal: lazyFinal,
lazyOld: lazyOld,
updateHolder: updateHolder,
convertToFastObject: convertToFastObject,
@@ -686,6 +708,8 @@
'directAccessTestExpression': js.js(_directAccessTestExpression),
'cyclicThrow': _emitter
.staticFunctionAccess(_closedWorld.commonElements.cyclicThrowHelper),
+ 'throwLateInitializationError': _emitter.staticFunctionAccess(
+ _closedWorld.commonElements.throwLateInitializationError),
'operatorIsPrefix': js.string(_namer.fixedNames.operatorIsPrefix),
'tearOffCode': new js.Block(buildTearOffCode(
_options, _emitter, _namer, _closedWorld.commonElements)),
@@ -1755,14 +1779,17 @@
LocalAliases locals = LocalAliases();
for (StaticField field in fields) {
assert(field.holder.isStaticStateHolder);
+ String helper = field.usesNonNullableInitialization
+ ? field.isFinal
+ ? locals.find('_lazyFinal', 'hunkHelpers.lazyFinal')
+ : locals.find('_lazy', 'hunkHelpers.lazy')
+ : locals.find('_lazyOld', 'hunkHelpers.lazyOld');
js.Statement statement = js.js.statement("#(#, #, #, #);", [
- field.usesNonNullableInitialization
- ? locals.find('_lazy', 'hunkHelpers.lazy')
- : locals.find('_lazyOld', 'hunkHelpers.lazyOld'),
+ helper,
field.holder.name,
js.quoteName(field.name),
js.quoteName(field.getterName),
- field.code
+ field.code,
]);
registerEntityAst(field.element, statement,
diff --git a/pkg/compiler/lib/src/ssa/builder_kernel.dart b/pkg/compiler/lib/src/ssa/builder_kernel.dart
index 23178f9..e2738fd 100644
--- a/pkg/compiler/lib/src/ssa/builder_kernel.dart
+++ b/pkg/compiler/lib/src/ssa/builder_kernel.dart
@@ -461,6 +461,9 @@
registry.registerStaticUse(new StaticUse.staticInvoke(
closedWorld.commonElements.cyclicThrowHelper,
CallStructure.ONE_ARG));
+ registry.registerStaticUse(new StaticUse.staticInvoke(
+ closedWorld.commonElements.throwLateInitializationError,
+ CallStructure.ONE_ARG));
}
if (targetElement.isInstanceMember) {
if (fieldData.isEffectivelyFinal ||
diff --git a/pkg/compiler/test/impact/data/invokes.dart b/pkg/compiler/test/impact/data/invokes.dart
index 634025b..a7e816f 100644
--- a/pkg/compiler/test/impact/data/invokes.dart
+++ b/pkg/compiler/test/impact/data/invokes.dart
@@ -588,7 +588,13 @@
/*member: testTopLevelField:static=[topLevelField]*/
testTopLevelField() => topLevelField;
-/*member: topLevelFieldLazy:static=[throwCyclicInit(1),topLevelFunction1(1)],type=[inst:JSNull]*/
+/*member: topLevelFieldLazy:
+ static=[
+ throwCyclicInit(1),
+ throwLateInitializationError(1),
+ topLevelFunction1(1)],
+ type=[inst:JSNull]
+*/
var topLevelFieldLazy = topLevelFunction1(null);
/*member: testTopLevelFieldLazy:static=[topLevelFieldLazy]*/
@@ -599,7 +605,13 @@
/*member: testTopLevelFieldConst:type=[inst:JSNull]*/
testTopLevelFieldConst() => topLevelFieldConst;
-/*member: topLevelFieldFinal:static=[throwCyclicInit(1),topLevelFunction1(1)],type=[inst:JSNull]*/
+/*member: topLevelFieldFinal:
+ static=[
+ throwCyclicInit(1),
+ throwLateInitializationError(1),
+ topLevelFunction1(1)],
+ type=[inst:JSNull]
+*/
final topLevelFieldFinal = topLevelFunction1(null);
/*member: testTopLevelFieldFinal:static=[topLevelFieldFinal]*/
diff --git a/runtime/vm/compiler/backend/il_test_helper.cc b/runtime/vm/compiler/backend/il_test_helper.cc
index 9c5788a..f4d002b 100644
--- a/runtime/vm/compiler/backend/il_test_helper.cc
+++ b/runtime/vm/compiler/backend/il_test_helper.cc
@@ -307,6 +307,13 @@
}
}
+ if (opcode == kMoveDebugStepChecks) {
+ while (cursor != nullptr && cursor->IsDebugStepCheck()) {
+ cursor = cursor->next();
+ }
+ return cursor;
+ }
+
if (opcode == kMatchAndMoveGoto) {
if (auto goto_instr = cursor->AsGoto()) {
return goto_instr->successor();
@@ -362,6 +369,9 @@
if (opcode == kMoveGlob) {
return "kMoveGlob";
}
+ if (opcode == kMoveDebugStepChecks) {
+ return "kMoveDebugStepChecks";
+ }
switch (opcode) {
#define EMIT_CASE(Instruction, _) \
diff --git a/runtime/vm/compiler/backend/il_test_helper.h b/runtime/vm/compiler/backend/il_test_helper.h
index ec00bb7..01da0c3 100644
--- a/runtime/vm/compiler/backend/il_test_helper.h
+++ b/runtime/vm/compiler/backend/il_test_helper.h
@@ -122,6 +122,9 @@
// Moves forward until the next match code matches.
kMoveGlob,
+ // Moves over any DebugStepChecks.
+ kMoveDebugStepChecks,
+
// Invalid match opcode used as default [insert_before] argument to TryMatch
// to signal that no insertions should occur.
kInvalidMatchOpCode,
diff --git a/runtime/vm/compiler/compiler_sources.gni b/runtime/vm/compiler/compiler_sources.gni
index 3c72125..f1c672a 100644
--- a/runtime/vm/compiler/compiler_sources.gni
+++ b/runtime/vm/compiler/compiler_sources.gni
@@ -187,6 +187,7 @@
"backend/typed_data_aot_test.cc",
"backend/yield_position_test.cc",
"cha_test.cc",
+ "frontend/kernel_binary_flowgraph_test.cc",
"write_barrier_elimination_test.cc",
]
diff --git a/runtime/vm/compiler/frontend/kernel_binary_flowgraph.cc b/runtime/vm/compiler/frontend/kernel_binary_flowgraph.cc
index c2d252c..7b22ac1 100644
--- a/runtime/vm/compiler/frontend/kernel_binary_flowgraph.cc
+++ b/runtime/vm/compiler/frontend/kernel_binary_flowgraph.cc
@@ -3613,34 +3613,87 @@
LoadLocal(parsed_function()->expression_temp_var());
}
-Fragment StreamingFlowGraphBuilder::BuildStringConcatenation(TokenPosition* p) {
- TokenPosition position = ReadPosition(); // read position.
- if (p != NULL) *p = position;
+void StreamingFlowGraphBuilder::FlattenStringConcatenation(
+ PiecesCollector* collector) {
+ const auto length = ReadListLength();
+ for (intptr_t i = 0; i < length; ++i) {
+ const auto offset = reader_.offset();
+ switch (PeekTag()) {
+ case kStringLiteral: {
+ ReadTag();
+ const String& s = H.DartSymbolPlain(ReadStringReference());
+ // Skip empty strings.
+ if (!s.Equals("")) {
+ collector->Add({-1, &s});
+ }
+ break;
+ }
+ case kStringConcatenation: {
+ // Flatten by hoisting nested expressions up into the outer concat.
+ ReadTag();
+ ReadPosition();
+ FlattenStringConcatenation(collector);
+ break;
+ }
+ default: {
+ collector->Add({offset, nullptr});
+ SkipExpression();
+ }
+ }
+ }
+}
- intptr_t length = ReadListLength(); // read list length.
- // Note: there will be "length" expressions.
+Fragment StreamingFlowGraphBuilder::BuildStringConcatenation(TokenPosition* p) {
+ TokenPosition position = ReadPosition();
+ if (p != nullptr) {
+ *p = position;
+ }
+
+ // Collect and flatten all pieces of this and any nested StringConcats.
+ // The result is a single sequence of pieces, potentially flattened to
+ // a single String.
+ // The collector will hold concatenated strings and Reader offsets of
+ // non-string pieces.
+ PiecesCollector collector(Z, &H);
+ FlattenStringConcatenation(&collector);
+ collector.FlushRun();
+
+ if (collector.pieces.length() == 1) {
+ // No need to Interp. a single string, so return string as a Constant:
+ if (collector.pieces[0].literal != nullptr) {
+ return Constant(*collector.pieces[0].literal);
+ }
+ // A single non-string piece is handle by StringInterpolateSingle:
+ AlternativeReadingScope scope(&reader_, collector.pieces[0].offset);
+ Fragment instructions;
+ instructions += BuildExpression();
+ instructions += StringInterpolateSingle(position);
+ return instructions;
+ }
Fragment instructions;
- if (length == 1) {
- instructions += BuildExpression(); // read expression.
- instructions += StringInterpolateSingle(position);
- } else {
- // The type arguments for CreateArray.
- instructions += Constant(TypeArguments::ZoneHandle(Z));
- instructions += IntConstant(length);
- instructions += CreateArray();
- LocalVariable* array = MakeTemporary();
-
- for (intptr_t i = 0; i < length; ++i) {
+ instructions += Constant(TypeArguments::ZoneHandle(Z));
+ instructions += IntConstant(collector.pieces.length());
+ instructions += CreateArray();
+ LocalVariable* array = MakeTemporary();
+ for (intptr_t i = 0; i < collector.pieces.length(); ++i) {
+ // All pieces are now either a concat'd string or an expression we can
+ // read at a given offset.
+ if (collector.pieces[i].literal != nullptr) {
instructions += LoadLocal(array);
instructions += IntConstant(i);
- instructions += BuildExpression(); // read ith expression.
- instructions += StoreIndexed(kArrayCid);
+ instructions += Constant(*collector.pieces[i].literal);
+ } else {
+ AlternativeReadingScope scope(&reader_, collector.pieces[i].offset);
+ instructions += LoadLocal(array);
+ instructions += IntConstant(i);
+ instructions += BuildExpression();
}
-
- instructions += StringInterpolate(position);
+ instructions += StoreIndexed(kArrayCid);
}
+ instructions += StringInterpolate(position);
+
return instructions;
}
diff --git a/runtime/vm/compiler/frontend/kernel_binary_flowgraph.h b/runtime/vm/compiler/frontend/kernel_binary_flowgraph.h
index f3a2fee..65ea0bd 100644
--- a/runtime/vm/compiler/frontend/kernel_binary_flowgraph.h
+++ b/runtime/vm/compiler/frontend/kernel_binary_flowgraph.h
@@ -366,6 +366,53 @@
// Kernel buffer and pushes the resulting Function object.
Fragment BuildFfiNativeCallbackFunction();
+ // Piece of a StringConcatenation.
+ // Represents either a StringLiteral, or a Reader offset to the expression.
+ struct ConcatPiece {
+ intptr_t offset;
+ const String* literal;
+ };
+
+ // Collector that automatically concatenates adjacent string ConcatPieces.
+ struct PiecesCollector {
+ explicit PiecesCollector(Zone* z, TranslationHelper* translation_helper)
+ : pieces(5),
+ literal_run(z, 1),
+ translation_helper(translation_helper) {}
+
+ GrowableArray<ConcatPiece> pieces;
+ GrowableHandlePtrArray<const String> literal_run;
+ TranslationHelper* translation_helper;
+
+ void Add(const ConcatPiece& piece) {
+ if (piece.literal != nullptr) {
+ literal_run.Add(*piece.literal);
+ } else {
+ FlushRun();
+ pieces.Add(piece);
+ }
+ }
+
+ void FlushRun() {
+ switch (literal_run.length()) {
+ case 0:
+ return;
+ case 1:
+ pieces.Add({-1, &literal_run[0]});
+ break;
+ default:
+ pieces.Add({-1, &translation_helper->DartString(literal_run)});
+ }
+ literal_run.Clear();
+ }
+ };
+
+ // Flattens and collects pieces of StringConcatenations such that:
+ // ["a", "", "b"] => ["ab"]
+ // ["a", StringConcat("b", "c")] => ["abc"]
+ // ["a", "", StringConcat("b", my_var), "c"] => ["ab", my_var, "c"]
+ void FlattenStringConcatenation(PiecesCollector* collector);
+
FlowGraphBuilder* flow_graph_builder_;
ActiveClass* const active_class_;
ConstantReader constant_reader_;
diff --git a/runtime/vm/compiler/frontend/kernel_binary_flowgraph_test.cc b/runtime/vm/compiler/frontend/kernel_binary_flowgraph_test.cc
new file mode 100644
index 0000000..80ddd91
--- /dev/null
+++ b/runtime/vm/compiler/frontend/kernel_binary_flowgraph_test.cc
@@ -0,0 +1,250 @@
+// Copyright (c) 2020, 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.
+
+#include "vm/compiler/frontend/kernel_binary_flowgraph.h"
+
+#include "vm/compiler/backend/il_test_helper.h"
+#include "vm/object.h"
+#include "vm/unit_test.h"
+
+namespace dart {
+
+ISOLATE_UNIT_TEST_CASE(StreamingFlowGraphBuilder_ConstFoldStringConcats) {
+ // According to the Dart spec:
+ // "Adjacent strings are implicitly concatenated to form a single string
+ // literal."
+ const char* kScript = R"(
+ test() {
+ var s = 'aaaa'
+ 'bbbb'
+ 'cccc';
+ return s;
+ }
+ )";
+
+ const auto& root_library = Library::Handle(LoadTestScript(kScript));
+ const auto& function = Function::Handle(GetFunction(root_library, "test"));
+
+ Invoke(root_library, "test");
+
+ TestPipeline pipeline(function, CompilerPass::kJIT);
+ FlowGraph* flow_graph = pipeline.RunPasses({
+ CompilerPass::kComputeSSA,
+ });
+
+ auto entry = flow_graph->graph_entry()->normal_entry();
+ EXPECT(entry != nullptr);
+
+ ReturnInstr* ret = nullptr;
+
+ ILMatcher cursor(flow_graph, entry);
+ // clang-format off
+ RELEASE_ASSERT(cursor.TryMatch({
+ kMatchAndMoveFunctionEntry,
+ kMatchAndMoveCheckStackOverflow,
+ kMoveDebugStepChecks,
+ {kMatchReturn, &ret},
+ }));
+ // clang-format on
+
+ EXPECT(ret->value()->BindsToConstant());
+ EXPECT(ret->value()->BoundConstant().IsString());
+ const String& ret_str = String::Cast(ret->value()->BoundConstant());
+ EXPECT(ret_str.Equals("aaaabbbbcccc"));
+}
+
+ISOLATE_UNIT_TEST_CASE(StreamingFlowGraphBuilder_FlattenNestedStringInterp) {
+ // We should collapse nested StringInterpolates:
+ const char* kScript = R"(
+ test(String s) {
+ return '$s' '${'d' 'e'}';
+ }
+ main() => test('u');
+ )";
+
+ const auto& root_library = Library::Handle(LoadTestScript(kScript));
+ const auto& function = Function::Handle(GetFunction(root_library, "test"));
+
+ Invoke(root_library, "main");
+
+ TestPipeline pipeline(function, CompilerPass::kJIT);
+ FlowGraph* flow_graph = pipeline.RunPasses({
+ CompilerPass::kComputeSSA,
+ });
+
+ auto entry = flow_graph->graph_entry()->normal_entry();
+ EXPECT(entry != nullptr);
+
+ StoreIndexedInstr* store1 = nullptr;
+ StoreIndexedInstr* store2 = nullptr;
+
+ ILMatcher cursor(flow_graph, entry);
+ // clang-format off
+ RELEASE_ASSERT(cursor.TryMatch({
+ kMatchAndMoveFunctionEntry,
+ kMatchAndMoveCheckStackOverflow,
+ kMoveDebugStepChecks,
+ kMatchAndMoveCreateArray,
+ {kMatchAndMoveStoreIndexed, &store1},
+ {kMatchAndMoveStoreIndexed, &store2},
+ kMatchAndMoveStringInterpolate,
+ kMoveDebugStepChecks,
+ kMatchReturn,
+ }));
+ // clang-format on
+
+ // StoreIndexed(tmp_array, 0, s)
+ EXPECT(store1->index()->BindsToConstant());
+ EXPECT(store1->index()->BoundConstant().IsInteger());
+ EXPECT(Integer::Cast(store1->index()->BoundConstant()).AsInt64Value() == 0);
+
+ EXPECT(!store1->value()->BindsToConstant());
+
+ // StoreIndexed(tmp_array, 1, "de")
+ EXPECT(store2->index()->BindsToConstant());
+ EXPECT(store2->index()->BoundConstant().IsInteger());
+ EXPECT(Integer::Cast(store2->index()->BoundConstant()).AsInt64Value() == 1);
+
+ EXPECT(store2->value()->BindsToConstant());
+ EXPECT(store2->value()->BoundConstant().IsString());
+ EXPECT(String::Cast(store2->value()->BoundConstant()).Equals("de"));
+}
+
+ISOLATE_UNIT_TEST_CASE(StreamingFlowGraphBuilder_DropEmptyStringInterp) {
+ // We should drop empty strings from StringInterpolates:
+ const char* kScript = R"(
+ test(s) {
+ return '' 'a' '$s' '' 'b' '';
+ }
+ main() => test('u');
+ )";
+
+ const auto& root_library = Library::Handle(LoadTestScript(kScript));
+ const auto& function = Function::Handle(GetFunction(root_library, "test"));
+
+ Invoke(root_library, "main");
+
+ TestPipeline pipeline(function, CompilerPass::kJIT);
+ FlowGraph* flow_graph = pipeline.RunPasses({
+ CompilerPass::kComputeSSA,
+ });
+
+ auto entry = flow_graph->graph_entry()->normal_entry();
+ EXPECT(entry != nullptr);
+
+ StoreIndexedInstr* store1 = nullptr;
+ StoreIndexedInstr* store2 = nullptr;
+ StoreIndexedInstr* store3 = nullptr;
+
+ ILMatcher cursor(flow_graph, entry);
+ // clang-format off
+ RELEASE_ASSERT(cursor.TryMatch({
+ kMatchAndMoveFunctionEntry,
+ kMatchAndMoveCheckStackOverflow,
+ kMoveDebugStepChecks,
+ kMatchAndMoveCreateArray,
+ {kMatchAndMoveStoreIndexed, &store1},
+ {kMatchAndMoveStoreIndexed, &store2},
+ {kMatchAndMoveStoreIndexed, &store3},
+ kMatchAndMoveStringInterpolate,
+ kMoveDebugStepChecks,
+ kMatchReturn,
+ }));
+ // clang-format on
+
+ // StoreIndexed(tmp_array, 0, "ab")
+ EXPECT(store1->index()->BindsToConstant());
+ EXPECT(store1->index()->BoundConstant().IsInteger());
+ EXPECT(Integer::Cast(store1->index()->BoundConstant()).AsInt64Value() == 0);
+
+ EXPECT(store1->value()->BindsToConstant());
+ EXPECT(store1->value()->BoundConstant().IsString());
+ EXPECT(String::Cast(store1->value()->BoundConstant()).Equals("a"));
+
+ // StoreIndexed(tmp_array, 1, s)
+ EXPECT(store2->index()->BindsToConstant());
+ EXPECT(store2->index()->BoundConstant().IsInteger());
+ EXPECT(Integer::Cast(store2->index()->BoundConstant()).AsInt64Value() == 1);
+
+ EXPECT(!store2->value()->BindsToConstant());
+
+ // StoreIndexed(tmp_array, 2, "b")
+ EXPECT(store3->index()->BindsToConstant());
+ EXPECT(store3->index()->BoundConstant().IsInteger());
+ EXPECT(Integer::Cast(store3->index()->BoundConstant()).AsInt64Value() == 2);
+
+ EXPECT(store3->value()->BindsToConstant());
+ EXPECT(store3->value()->BoundConstant().IsString());
+ EXPECT(String::Cast(store3->value()->BoundConstant()).Equals("b"));
+}
+
+ISOLATE_UNIT_TEST_CASE(StreamingFlowGraphBuilder_ConcatStringLits) {
+ // We should drop empty strings from StringInterpolates:
+ const char* kScript = R"(
+ test(s) {
+ return '' 'a' '' 'b' '$s' '' 'c' '' 'd' '';
+ }
+ main() => test('u');
+ )";
+
+ const auto& root_library = Library::Handle(LoadTestScript(kScript));
+ const auto& function = Function::Handle(GetFunction(root_library, "test"));
+
+ Invoke(root_library, "main");
+
+ TestPipeline pipeline(function, CompilerPass::kJIT);
+ FlowGraph* flow_graph = pipeline.RunPasses({
+ CompilerPass::kComputeSSA,
+ });
+
+ auto entry = flow_graph->graph_entry()->normal_entry();
+ EXPECT(entry != nullptr);
+
+ StoreIndexedInstr* store1 = nullptr;
+ StoreIndexedInstr* store2 = nullptr;
+ StoreIndexedInstr* store3 = nullptr;
+
+ ILMatcher cursor(flow_graph, entry);
+ // clang-format off
+ RELEASE_ASSERT(cursor.TryMatch({
+ kMatchAndMoveFunctionEntry,
+ kMatchAndMoveCheckStackOverflow,
+ kMoveDebugStepChecks,
+ kMatchAndMoveCreateArray,
+ {kMatchAndMoveStoreIndexed, &store1},
+ {kMatchAndMoveStoreIndexed, &store2},
+ {kMatchAndMoveStoreIndexed, &store3},
+ kMatchAndMoveStringInterpolate,
+ kMoveDebugStepChecks,
+ kMatchReturn,
+ }));
+ // clang-format on
+
+ // StoreIndexed(tmp_array, 0, "ab")
+ EXPECT(store1->index()->BindsToConstant());
+ EXPECT(store1->index()->BoundConstant().IsInteger());
+ EXPECT(Integer::Cast(store1->index()->BoundConstant()).AsInt64Value() == 0);
+
+ EXPECT(store1->value()->BindsToConstant());
+ EXPECT(store1->value()->BoundConstant().IsString());
+ EXPECT(String::Cast(store1->value()->BoundConstant()).Equals("ab"));
+
+ // StoreIndexed(tmp_array, 1, s)
+ EXPECT(store2->index()->BindsToConstant());
+ EXPECT(store2->index()->BoundConstant().IsInteger());
+ EXPECT(Integer::Cast(store2->index()->BoundConstant()).AsInt64Value() == 1);
+
+ EXPECT(!store2->value()->BindsToConstant());
+
+ // StoreIndexed(tmp_array, 2, "cd")
+ EXPECT(store3->index()->BindsToConstant());
+ EXPECT(store3->index()->BoundConstant().IsInteger());
+ EXPECT(Integer::Cast(store3->index()->BoundConstant()).AsInt64Value() == 2);
+
+ EXPECT(store3->value()->BindsToConstant());
+ EXPECT(store3->value()->BoundConstant().IsString());
+ EXPECT(String::Cast(store3->value()->BoundConstant()).Equals("cd"));
+}
+
+} // namespace dart
diff --git a/runtime/vm/compiler/frontend/kernel_translation_helper.cc b/runtime/vm/compiler/frontend/kernel_translation_helper.cc
index d70966c..3c2b977 100644
--- a/runtime/vm/compiler/frontend/kernel_translation_helper.cc
+++ b/runtime/vm/compiler/frontend/kernel_translation_helper.cc
@@ -370,6 +370,11 @@
return String::ZoneHandle(Z, String::FromUTF8(utf8_array, len, space));
}
+const String& TranslationHelper::DartString(
+ const GrowableHandlePtrArray<const String>& pieces) {
+ return String::ZoneHandle(Z, Symbols::FromConcatAll(thread_, pieces));
+}
+
const String& TranslationHelper::DartSymbolPlain(const char* content) const {
return String::ZoneHandle(Z, Symbols::New(thread_, content));
}
diff --git a/runtime/vm/compiler/frontend/kernel_translation_helper.h b/runtime/vm/compiler/frontend/kernel_translation_helper.h
index f05d261..cec43d5 100644
--- a/runtime/vm/compiler/frontend/kernel_translation_helper.h
+++ b/runtime/vm/compiler/frontend/kernel_translation_helper.h
@@ -129,6 +129,8 @@
intptr_t len,
Heap::Space space);
+ const String& DartString(const GrowableHandlePtrArray<const String>& pieces);
+
const String& DartSymbolPlain(const char* content) const;
String& DartSymbolPlain(StringIndex string_index) const;
const String& DartSymbolObfuscate(const char* content) const;
diff --git a/runtime/vm/growable_array.h b/runtime/vm/growable_array.h
index 5cd2d0b..85bfedc 100644
--- a/runtime/vm/growable_array.h
+++ b/runtime/vm/growable_array.h
@@ -66,6 +66,8 @@
intptr_t length() const { return array_.length(); }
+ void Clear() { array_.Clear(); }
+
const GrowableArray<T*>& growable_array() const { return array_; }
private:
diff --git a/sdk/lib/_internal/js_runtime/lib/js_helper.dart b/sdk/lib/_internal/js_runtime/lib/js_helper.dart
index ea0239f..542f18b 100644
--- a/sdk/lib/_internal/js_runtime/lib/js_helper.dart
+++ b/sdk/lib/_internal/js_runtime/lib/js_helper.dart
@@ -48,6 +48,7 @@
import 'dart:_internal'
show
EfficientLengthIterable,
+ LateInitializationErrorImpl,
MappedIterable,
IterableElementError,
SubListIterable;
@@ -3023,3 +3024,7 @@
const kRequiredSentinel = const _Required();
bool isRequired(Object? value) => identical(kRequiredSentinel, value);
+
+/// Called by generated code to throw a LateInitializationError.
+void throwLateInitializationError(String name) =>
+ throw LateInitializationErrorImpl(name);
diff --git a/tools/VERSION b/tools/VERSION
index b4d2ba5..0010549 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
MAJOR 2
MINOR 10
PATCH 0
-PRERELEASE 143
+PRERELEASE 144
PRERELEASE_PATCH 0
\ No newline at end of file