Version 2.15.0-190.0.dev
Merge commit '7990144badcb4b32fd1a99b0964d50e9011264a8' into 'dev'
diff --git a/DEPS b/DEPS
index bbeda37f..72abd08 100644
--- a/DEPS
+++ b/DEPS
@@ -90,7 +90,7 @@
"collection_rev": "a4c941ab94044d118b2086a3f261c30377604127",
"convert_rev": "e063fdca4bebffecbb5e6aa5525995120982d9ce",
"crypto_rev": "b5024e4de2b1c474dd558bef593ddbf0bfade152",
- "csslib_rev": "6338de25a09d098a62c9a1992c175e9ceb5b994a",
+ "csslib_rev": "6f35da3d93eb56eb25925779d235858d4090ce6f",
# Note: Updates to dart_style have to be coordinated with the infrastructure
# team so that the internal formatter `tools/sdks/dart-sdk/bin/dart format`
diff --git a/pkg/_fe_analyzer_shared/lib/src/messages/codes_generated.dart b/pkg/_fe_analyzer_shared/lib/src/messages/codes_generated.dart
index e95acec..6207ad4 100644
--- a/pkg/_fe_analyzer_shared/lib/src/messages/codes_generated.dart
+++ b/pkg/_fe_analyzer_shared/lib/src/messages/codes_generated.dart
@@ -6471,9 +6471,9 @@
Message Function(
String name, String name2, String string3)>(
problemMessageTemplate:
- r"""JS interop class '#name' conflicts with natively supported class '#name2' in '#string3'.""",
+ r"""Non-static JS interop class '#name' conflicts with natively supported class '#name2' in '#string3'.""",
correctionMessageTemplate:
- r"""Try making the @JS class into an @anonymous class or use js_util on the JS object.""",
+ r"""Try replacing it with a static JS interop class using `@staticInterop` with extension methods, or use js_util to interact with the native object of type '#name2'.""",
withArguments: _withArgumentsJsInteropNativeClassInAnnotation);
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
@@ -6493,8 +6493,8 @@
if (string3.isEmpty) throw 'No string provided';
return new Message(codeJsInteropNativeClassInAnnotation,
problemMessage:
- """JS interop class '${name}' conflicts with natively supported class '${name2}' in '${string3}'.""",
- correctionMessage: """Try making the @JS class into an @anonymous class or use js_util on the JS object.""",
+ """Non-static JS interop class '${name}' conflicts with natively supported class '${name2}' in '${string3}'.""",
+ correctionMessage: """Try replacing it with a static JS interop class using `@staticInterop` with extension methods, or use js_util to interact with the native object of type '${name2}'.""",
arguments: {'name': name, 'name2': name2, 'string3': string3});
}
@@ -6521,6 +6521,66 @@
correctionMessage: r"""Try annotating the member with `external`.""");
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
+const Template<Message Function(String name)>
+ templateJsInteropStaticInteropWithInstanceMembers =
+ const Template<Message Function(String name)>(
+ problemMessageTemplate:
+ r"""JS interop class '#name' with `@staticInterop` annotation cannot declare instance members.""",
+ correctionMessageTemplate:
+ r"""Try moving the instance member to a static extension.""",
+ withArguments: _withArgumentsJsInteropStaticInteropWithInstanceMembers);
+
+// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
+const Code<Message Function(String name)>
+ codeJsInteropStaticInteropWithInstanceMembers =
+ const Code<Message Function(String name)>(
+ "JsInteropStaticInteropWithInstanceMembers",
+);
+
+// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
+Message _withArgumentsJsInteropStaticInteropWithInstanceMembers(String name) {
+ if (name.isEmpty) throw 'No name provided';
+ name = demangleMixinApplicationName(name);
+ return new Message(codeJsInteropStaticInteropWithInstanceMembers,
+ problemMessage:
+ """JS interop class '${name}' with `@staticInterop` annotation cannot declare instance members.""",
+ correctionMessage: """Try moving the instance member to a static extension.""",
+ arguments: {'name': name});
+}
+
+// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
+const Template<Message Function(String name, String name2)>
+ templateJsInteropStaticInteropWithNonStaticSupertype =
+ const Template<Message Function(String name, String name2)>(
+ problemMessageTemplate:
+ r"""JS interop class '#name' has an `@staticInterop` annotation, but has supertype '#name2', which is non-static.""",
+ correctionMessageTemplate:
+ r"""Try marking the supertype as a static interop class using `@staticInterop`.""",
+ withArguments:
+ _withArgumentsJsInteropStaticInteropWithNonStaticSupertype);
+
+// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
+const Code<Message Function(String name, String name2)>
+ codeJsInteropStaticInteropWithNonStaticSupertype =
+ const Code<Message Function(String name, String name2)>(
+ "JsInteropStaticInteropWithNonStaticSupertype",
+);
+
+// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
+Message _withArgumentsJsInteropStaticInteropWithNonStaticSupertype(
+ String name, String name2) {
+ if (name.isEmpty) throw 'No name provided';
+ name = demangleMixinApplicationName(name);
+ if (name2.isEmpty) throw 'No name provided';
+ name2 = demangleMixinApplicationName(name2);
+ return new Message(codeJsInteropStaticInteropWithNonStaticSupertype,
+ problemMessage:
+ """JS interop class '${name}' has an `@staticInterop` annotation, but has supertype '${name2}', which is non-static.""",
+ correctionMessage: """Try marking the supertype as a static interop class using `@staticInterop`.""",
+ arguments: {'name': name, 'name2': name2});
+}
+
+// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
const Template<
Message Function(String name)> templateLabelNotFound = const Template<
Message Function(String name)>(
diff --git a/pkg/_js_interop_checks/lib/js_interop_checks.dart b/pkg/_js_interop_checks/lib/js_interop_checks.dart
index 975fca7..24937db 100644
--- a/pkg/_js_interop_checks/lib/js_interop_checks.dart
+++ b/pkg/_js_interop_checks/lib/js_interop_checks.dart
@@ -19,6 +19,8 @@
messageJsInteropNonExternalConstructor,
messageJsInteropNonExternalMember,
templateJsInteropDartClassExtendsJSClass,
+ templateJsInteropStaticInteropWithInstanceMembers,
+ templateJsInteropStaticInteropWithNonStaticSupertype,
templateJsInteropJSClassExtendsDartClass,
templateJsInteropNativeClassInAnnotation;
@@ -30,6 +32,7 @@
final Map<String, Class> _nativeClasses;
bool _classHasJSAnnotation = false;
bool _classHasAnonymousAnnotation = false;
+ bool _classHasStaticInteropAnnotation = false;
bool _libraryHasJSAnnotation = false;
Map<Reference, Extension>? _libraryExtensionsIndex;
@@ -99,6 +102,7 @@
void visitClass(Class cls) {
_classHasJSAnnotation = hasJSInteropAnnotation(cls);
_classHasAnonymousAnnotation = hasAnonymousAnnotation(cls);
+ _classHasStaticInteropAnnotation = hasStaticInteropAnnotation(cls);
var superclass = cls.superclass;
if (superclass != null && superclass != _coreTypes.objectClass) {
var superHasJSAnnotation = hasJSInteropAnnotation(superclass);
@@ -116,11 +120,35 @@
cls.fileOffset,
cls.name.length,
cls.fileUri);
+ } else if (_classHasStaticInteropAnnotation) {
+ if (!hasStaticInteropAnnotation(superclass)) {
+ _diagnosticsReporter.report(
+ templateJsInteropStaticInteropWithNonStaticSupertype
+ .withArguments(cls.name, superclass.name),
+ cls.fileOffset,
+ cls.name.length,
+ cls.fileUri);
+ }
+ }
+ }
+ // Validate that superinterfaces are all annotated as static as well. Note
+ // that mixins are already disallowed and therefore are not checked here.
+ if (_classHasStaticInteropAnnotation) {
+ for (var supertype in cls.implementedTypes) {
+ if (!hasStaticInteropAnnotation(supertype.classNode)) {
+ _diagnosticsReporter.report(
+ templateJsInteropStaticInteropWithNonStaticSupertype
+ .withArguments(cls.name, supertype.classNode.name),
+ cls.fileOffset,
+ cls.name.length,
+ cls.fileUri);
+ }
}
}
// Since this is a breaking check, it is language-versioned.
if (cls.enclosingLibrary.languageVersion >= Version(2, 13) &&
_classHasJSAnnotation &&
+ !_classHasStaticInteropAnnotation &&
!_classHasAnonymousAnnotation &&
_libraryIsGlobalNamespace) {
var jsClass = getJSName(cls);
@@ -221,6 +249,30 @@
_checkNoNamedParameters(procedure.function);
}
}
+
+ if (_classHasStaticInteropAnnotation &&
+ procedure.isInstanceMember &&
+ !procedure.isFactory) {
+ _diagnosticsReporter.report(
+ templateJsInteropStaticInteropWithInstanceMembers
+ .withArguments(procedure.enclosingClass!.name),
+ procedure.fileOffset,
+ procedure.name.text.length,
+ procedure.fileUri);
+ }
+ }
+
+ @override
+ void visitField(Field field) {
+ if (_classHasStaticInteropAnnotation && field.isInstanceMember) {
+ _diagnosticsReporter.report(
+ templateJsInteropStaticInteropWithInstanceMembers
+ .withArguments(field.enclosingClass!.name),
+ field.fileOffset,
+ field.name.text.length,
+ field.fileUri);
+ }
+ super.visitField(field);
}
@override
diff --git a/pkg/_js_interop_checks/lib/src/js_interop.dart b/pkg/_js_interop_checks/lib/src/js_interop.dart
index 60a8dfd..26c0c7d 100644
--- a/pkg/_js_interop_checks/lib/src/js_interop.dart
+++ b/pkg/_js_interop_checks/lib/src/js_interop.dart
@@ -9,11 +9,16 @@
bool hasJSInteropAnnotation(Annotatable a) =>
a.annotations.any(_isPublicJSAnnotation);
-/// Returns true iff the node has an `@anonymous(...)` annotation from
-/// `package:js` or from the internal `dart:_js_annotations`.
+/// Returns true iff the node has an `@anonymous` annotation from `package:js`
+/// or from the internal `dart:_js_annotations`.
bool hasAnonymousAnnotation(Annotatable a) =>
a.annotations.any(_isAnonymousAnnotation);
+/// Returns true iff the node has an `@staticInterop` annotation from
+/// `package:js` or from the internal `dart:_js_annotations`.
+bool hasStaticInteropAnnotation(Annotatable a) =>
+ a.annotations.any(_isStaticInteropAnnotation);
+
/// If [a] has a `@JS('...')` annotation, returns the value inside the
/// parentheses.
///
@@ -52,26 +57,26 @@
final _internalJs = Uri.parse('dart:_js_annotations');
final _jsHelper = Uri.parse('dart:_js_helper');
-/// Returns true if [value] is the `JS` annotation from `package:js` or from
-/// `dart:_js_annotations`.
-bool _isPublicJSAnnotation(Expression value) {
+/// Returns true if [value] is the interop annotation whose class is
+/// [annotationClassName] from `package:js` or from `dart:_js_annotations`.
+bool _isInteropAnnotation(Expression value, String annotationClassName) {
var c = _annotationClass(value);
return c != null &&
- c.name == 'JS' &&
+ c.name == annotationClassName &&
(c.enclosingLibrary.importUri == _packageJs ||
c.enclosingLibrary.importUri == _internalJs);
}
-/// Returns true if [value] is the `anyonymous` annotation from `package:js` or
-/// from `dart:_js_annotations`.
-bool _isAnonymousAnnotation(Expression value) {
- var c = _annotationClass(value);
- return c != null &&
- c.name == '_Anonymous' &&
- (c.enclosingLibrary.importUri == _packageJs ||
- c.enclosingLibrary.importUri == _internalJs);
-}
+bool _isPublicJSAnnotation(Expression value) =>
+ _isInteropAnnotation(value, 'JS');
+bool _isAnonymousAnnotation(Expression value) =>
+ _isInteropAnnotation(value, '_Anonymous');
+
+bool _isStaticInteropAnnotation(Expression value) =>
+ _isInteropAnnotation(value, '_StaticInterop');
+
+/// Returns true if [value] is the `Native` annotation from `dart:_js_helper`.
bool _isNativeAnnotation(Expression value) {
var c = _annotationClass(value);
return c != null &&
@@ -85,6 +90,7 @@
///
/// - `@JS()` would return the "JS" class in "package:js".
/// - `@anonymous` would return the "_Anonymous" class in "package:js".
+/// - `@staticInterop` would return the "_StaticInterop" class in "package:js".
/// - `@Native` would return the "Native" class in "dart:_js_helper".
///
/// This function works regardless of whether the CFE is evaluating constants,
diff --git a/pkg/analysis_server/test/analysis/get_hover_test.dart b/pkg/analysis_server/test/analysis/get_hover_test.dart
index 927950d..576895b 100644
--- a/pkg/analysis_server/test/analysis/get_hover_test.dart
+++ b/pkg/analysis_server/test/analysis/get_hover_test.dart
@@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
import 'package:analysis_server/protocol/protocol_generated.dart';
+import 'package:analysis_server/src/protocol_server.dart';
import 'package:test/test.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';
@@ -248,6 +249,110 @@
}
}
+ Future<void> test_constructorReference_named() async {
+ addTestFile('''
+class A<T> {
+ /// doc aaa
+ /// doc bbb
+ A.named();
+}
+
+void f() {
+ A<double>.named;
+}
+''');
+ var hover = await prepareHover('named;');
+ // element
+ expect(hover.containingLibraryName, 'bin/test.dart');
+ expect(hover.containingLibraryPath, testFile);
+ expect(hover.containingClassDescription, 'A');
+ expect(hover.dartdoc, 'doc aaa\ndoc bbb');
+ expect(hover.elementDescription, 'A<double> A.named()');
+ expect(hover.elementKind, 'constructor');
+ // types
+ expect(hover.staticType, isNull);
+ expect(hover.propagatedType, isNull);
+ // no parameter
+ expect(hover.parameter, isNull);
+ }
+
+ Future<void> test_constructorReference_unnamed_declared() async {
+ addTestFile('''
+class A<T> {
+ /// doc aaa
+ /// doc bbb
+ A();
+}
+
+void f() {
+ A<double>.new;
+}
+''');
+ var hover = await prepareHover('new;');
+ // element
+ expect(hover.containingLibraryName, 'bin/test.dart');
+ expect(hover.containingLibraryPath, testFile);
+ expect(hover.containingClassDescription, 'A');
+ expect(hover.dartdoc, 'doc aaa\ndoc bbb');
+ expect(hover.elementDescription, 'A<double> A()');
+ expect(hover.elementKind, 'constructor');
+ // types
+ expect(hover.staticType, isNull);
+ expect(hover.propagatedType, isNull);
+ // no parameter
+ expect(hover.parameter, isNull);
+ }
+
+ Future<void> test_constructorReference_unnamed_declared_new() async {
+ addTestFile('''
+class A<T> {
+ /// doc aaa
+ /// doc bbb
+ A.new();
+}
+
+void f() {
+ A<double>.new;
+}
+''');
+ var hover = await prepareHover('new;');
+ // element
+ expect(hover.containingLibraryName, 'bin/test.dart');
+ expect(hover.containingLibraryPath, testFile);
+ expect(hover.containingClassDescription, 'A');
+ expect(hover.dartdoc, 'doc aaa\ndoc bbb');
+ expect(hover.elementDescription, 'A<double> A()');
+ expect(hover.elementKind, 'constructor');
+ // types
+ expect(hover.staticType, isNull);
+ expect(hover.propagatedType, isNull);
+ // no parameter
+ expect(hover.parameter, isNull);
+ }
+
+ Future<void> test_constructorReference_unnamed_synthetic() async {
+ addTestFile('''
+class A<T> {}
+
+void f() {
+ A<double>.new;
+}
+''');
+ var hover = await prepareHover('new;');
+ // element
+ expect(hover.containingLibraryName, 'bin/test.dart');
+ expect(hover.containingLibraryPath, testFile);
+ expect(hover.containingClassDescription, 'A');
+ expect(hover.dartdoc, isNull);
+ expect(hover.elementDescription, 'A<double> A()');
+ expect(hover.elementKind, 'constructor');
+ // types
+ expect(hover.staticType, isNull);
+ expect(hover.propagatedType, isNull);
+ // no parameter
+ expect(hover.parameter, isNull);
+ }
+
Future<void> test_dartdoc_block() async {
addTestFile('''
/**
@@ -402,6 +507,85 @@
expect(hover.parameter, isNull);
}
+ Future<void> test_functionReference_classMethod_instance() async {
+ addTestFile('''
+class A<T> {
+ /// doc aaa
+ /// doc bbb
+ int foo<U>(T t, U u) => 0;
+}
+
+void f(A<int> a) {
+ a.foo<double>;
+}
+''');
+ var hover = await prepareHover('foo<double>');
+ // element
+ expect(hover.containingLibraryName, 'bin/test.dart');
+ expect(hover.containingLibraryPath, testFile);
+ expect(hover.containingClassDescription, 'A');
+ expect(hover.dartdoc, '''doc aaa\ndoc bbb''');
+ expect(hover.elementDescription, 'int foo<U>(int t, U u)');
+ expect(hover.elementKind, 'method');
+ // types
+ expect(hover.staticType, isNull);
+ expect(hover.propagatedType, isNull);
+ // no parameter
+ expect(hover.parameter, isNull);
+ }
+
+ Future<void> test_functionReference_classMethod_static() async {
+ addTestFile('''
+class A<T> {
+ /// doc aaa
+ /// doc bbb
+ static int foo<U>(U u) => 0;
+}
+
+void f() {
+ A.foo<double>;
+}
+''');
+ var hover = await prepareHover('foo<double>');
+ // element
+ expect(hover.containingLibraryName, 'bin/test.dart');
+ expect(hover.containingLibraryPath, testFile);
+ expect(hover.containingClassDescription, 'A');
+ expect(hover.dartdoc, '''doc aaa\ndoc bbb''');
+ expect(hover.elementDescription, 'int foo<U>(U u)');
+ expect(hover.elementKind, 'method');
+ // types
+ expect(hover.staticType, isNull);
+ expect(hover.propagatedType, isNull);
+ // no parameter
+ expect(hover.parameter, isNull);
+ }
+
+ Future<void> test_functionReference_topLevelFunction() async {
+ addTestFile('''
+/// doc aaa
+/// doc bbb
+int foo<T>(T a) => 0;
+
+void f() {
+ foo<double>;
+}
+''');
+ var hover = await prepareHover('foo<double>');
+ // element
+ expect(hover.containingLibraryName, 'bin/test.dart');
+ expect(hover.containingLibraryPath, testFile);
+ expect(hover.containingClassDescription, isNull);
+ expect(hover.dartdoc, '''doc aaa\ndoc bbb''');
+ expect(hover.elementDescription, 'int foo<T>(T a)');
+ expect(hover.elementKind, 'function');
+ // types
+ expect(hover.staticType, isNull);
+ expect(hover.propagatedType, isNull);
+ // no parameter
+ expect(hover.parameter, isNull);
+ }
+
Future<void> test_getter_synthetic() async {
addTestFile('''
library my.library;
diff --git a/pkg/analysis_server/test/services/completion/dart/imported_reference_contributor_test.dart b/pkg/analysis_server/test/services/completion/dart/imported_reference_contributor_test.dart
index 5148c7d..05f16bd 100644
--- a/pkg/analysis_server/test/services/completion/dart/imported_reference_contributor_test.dart
+++ b/pkg/analysis_server/test/services/completion/dart/imported_reference_contributor_test.dart
@@ -4771,6 +4771,24 @@
assertNotSuggested('C2');
}
+ Future<void> test_TypeArgumentList_functionReference() async {
+ addTestSource('''
+class A {}
+
+void foo<T>() {}
+
+void f() {
+ foo<^>;
+}
+''');
+ await computeSuggestions();
+
+ expect(replacementOffset, completionOffset);
+ expect(replacementLength, 0);
+ assertSuggestClass('Object');
+ assertNotSuggested('A');
+ }
+
Future<void> test_TypeArgumentList_recursive() async {
resolveSource('/home/test/lib/a.dart', '''
class A {}
diff --git a/pkg/analysis_server/test/services/completion/dart/local_reference_contributor_test.dart b/pkg/analysis_server/test/services/completion/dart/local_reference_contributor_test.dart
index 678619f..243cdb5 100644
--- a/pkg/analysis_server/test/services/completion/dart/local_reference_contributor_test.dart
+++ b/pkg/analysis_server/test/services/completion/dart/local_reference_contributor_test.dart
@@ -6148,6 +6148,24 @@
assertSuggestClass('C2');
}
+ Future<void> test_TypeArgumentList_functionReference() async {
+ addTestSource('''
+class A {}
+
+void foo<T>() {}
+
+void f() {
+ foo<^>;
+}
+''');
+ await computeSuggestions();
+
+ expect(replacementOffset, completionOffset);
+ expect(replacementLength, 0);
+ assertSuggestClass('A');
+ assertNotSuggested('Object');
+ }
+
Future<void> test_TypeParameter_classDeclaration() async {
addTestSource('''
class A<T> {
diff --git a/pkg/analysis_server/test/src/cider/fixes_test.dart b/pkg/analysis_server/test/src/cider/fixes_test.dart
index ff775e5..63179b7 100644
--- a/pkg/analysis_server/test/src/cider/fixes_test.dart
+++ b/pkg/analysis_server/test/src/cider/fixes_test.dart
@@ -73,11 +73,10 @@
}
Future<void> test_importLibrary_withClass() async {
- var a_path = '/workspace/dart/test/lib/a.dart';
- newFile(a_path, content: r'''
+ var a = newFile('/workspace/dart/test/lib/a.dart', content: r'''
class Test {}
''');
- fileResolver.resolve(path: a_path);
+ fileResolver.resolve(path: a.path);
await _compute(r'''
void f(Test a) {}^
@@ -91,11 +90,10 @@
}
Future<void> test_importLibrary_withEnum() async {
- var a_path = '/workspace/dart/test/lib/a.dart';
- newFile(a_path, content: r'''
+ var a = newFile('/workspace/dart/test/lib/a.dart', content: r'''
enum Test {a, b, c}
''');
- fileResolver.resolve(path: a_path);
+ fileResolver.resolve(path: a.path);
await _compute(r'''
void f(Test a) {}^
@@ -109,13 +107,12 @@
}
Future<void> test_importLibrary_withExtension() async {
- var a_path = '/workspace/dart/test/lib/a.dart';
- newFile(a_path, content: r'''
+ var a = newFile('/workspace/dart/test/lib/a.dart', content: r'''
extension E on int {
void foo() {}
}
''');
- fileResolver.resolve(path: a_path);
+ fileResolver.resolve(path: a.path);
await _compute(r'''
void f() {
@@ -133,11 +130,10 @@
}
Future<void> test_importLibrary_withFunction() async {
- var a_path = '/workspace/dart/test/lib/a.dart';
- newFile(a_path, content: r'''
+ var a = newFile('/workspace/dart/test/lib/a.dart', content: r'''
void foo() {}
''');
- fileResolver.resolve(path: a_path);
+ fileResolver.resolve(path: a.path);
await _compute(r'''
void f() {
@@ -155,11 +151,10 @@
}
Future<void> test_importLibrary_withMixin() async {
- var a_path = '/workspace/dart/test/lib/a.dart';
- newFile(a_path, content: r'''
+ var a = newFile('/workspace/dart/test/lib/a.dart', content: r'''
mixin Test {}
''');
- fileResolver.resolve(path: a_path);
+ fileResolver.resolve(path: a.path);
await _compute(r'''
void f(Test a) {}^
@@ -173,11 +168,10 @@
}
Future<void> test_importLibrary_withTopLevelVariable() async {
- var a_path = '/workspace/dart/test/lib/a.dart';
- newFile(a_path, content: r'''
+ var a = newFile('/workspace/dart/test/lib/a.dart', content: r'''
var a = 0;
''');
- fileResolver.resolve(path: a_path);
+ fileResolver.resolve(path: a.path);
await _compute(r'''
void f() {
diff --git a/pkg/analyzer/lib/src/dart/constant/evaluation.dart b/pkg/analyzer/lib/src/dart/constant/evaluation.dart
index 70f5d10..a2e14e7 100644
--- a/pkg/analyzer/lib/src/dart/constant/evaluation.dart
+++ b/pkg/analyzer/lib/src/dart/constant/evaluation.dart
@@ -867,8 +867,8 @@
return null;
}
}
- // validate prefixed identifier
- return _getConstantValue(node, node.staticElement);
+ // Validate prefixed identifier.
+ return _getConstantValue(node, node.identifier);
}
@override
@@ -902,7 +902,7 @@
return prefixResult.stringLength(typeSystem);
}
}
- return _getConstantValue(node, node.propertyName.staticElement);
+ return _getConstantValue(node, node.propertyName);
}
@override
@@ -971,7 +971,7 @@
return _instantiateFunctionType(node, value);
}
- return _getConstantValue(node, node.staticElement);
+ return _getConstantValue(node, node);
}
@override
@@ -1171,9 +1171,11 @@
}
/// Return the constant value of the static constant represented by the given
- /// [element]. The [node] is the node to be used if an error needs to be
+ /// [identifier]. The [node] is the node to be used if an error needs to be
/// reported.
- DartObjectImpl? _getConstantValue(Expression node, Element? element) {
+ DartObjectImpl? _getConstantValue(
+ Expression node, SimpleIdentifier identifier) {
+ var element = identifier.staticElement;
element = element?.declaration;
var variableElement =
element is PropertyAccessorElement ? element.variable : element;
@@ -1196,7 +1198,7 @@
if (value == null) {
return value;
}
- return _instantiateFunctionType(node, value);
+ return _instantiateFunctionType(identifier, value);
}
} else if (variableElement is ConstructorElement) {
return DartObjectImpl(
@@ -1207,11 +1209,12 @@
} else if (variableElement is ExecutableElement) {
var function = element as ExecutableElement;
if (function.isStatic) {
- return DartObjectImpl(
+ var rawType = DartObjectImpl(
typeSystem,
- node.typeOrThrow,
+ function.type,
FunctionState(function),
);
+ return _instantiateFunctionType(identifier, rawType);
}
} else if (variableElement is ClassElement) {
var type = variableElement.instantiate(
@@ -1264,10 +1267,7 @@
/// type-instantiated with those [node]'s tear-off type argument types,
/// otherwise returns [value].
DartObjectImpl? _instantiateFunctionType(
- Expression node, DartObjectImpl value) {
- if (node is! SimpleIdentifier) {
- return value;
- }
+ SimpleIdentifier node, DartObjectImpl value) {
var functionElement = value.toFunctionValue();
if (functionElement is! ExecutableElement) {
return value;
diff --git a/pkg/analyzer/lib/src/dart/constant/potentially_constant.dart b/pkg/analyzer/lib/src/dart/constant/potentially_constant.dart
index 6f0eed7..aaa8ef5 100644
--- a/pkg/analyzer/lib/src/dart/constant/potentially_constant.dart
+++ b/pkg/analyzer/lib/src/dart/constant/potentially_constant.dart
@@ -94,7 +94,7 @@
}
if (node is TypedLiteral) {
- return _typeLiteral(node);
+ return _typedLiteral(node);
}
if (node is ParenthesizedExpression) {
@@ -187,6 +187,22 @@
return;
}
+ if (node is ConstructorReference) {
+ _typeArgumentList(node.constructorName.type2.typeArguments);
+ return;
+ }
+
+ if (node is FunctionReference) {
+ _typeArgumentList(node.typeArguments);
+ collect(node.function);
+ return;
+ }
+
+ if (node is TypeLiteral) {
+ _typeArgumentList(node.type.typeArguments);
+ return;
+ }
+
nodes.add(node);
}
@@ -261,6 +277,7 @@
return;
}
}
+ // TODO(srawlins): collect type arguments.
nodes.add(node);
}
@@ -292,7 +309,18 @@
nodes.add(node);
}
- void _typeLiteral(TypedLiteral node) {
+ void _typeArgumentList(TypeArgumentList? typeArgumentList) {
+ var typeArguments = typeArgumentList?.arguments;
+ if (typeArguments != null) {
+ for (var typeArgument in typeArguments) {
+ if (!isPotentiallyConstantTypeExpression(typeArgument)) {
+ nodes.add(typeArgument);
+ }
+ }
+ }
+ }
+
+ void _typedLiteral(TypedLiteral node) {
if (!node.isConst) {
nodes.add(node);
return;
@@ -302,6 +330,8 @@
var typeArguments = node.typeArguments?.arguments;
if (typeArguments != null && typeArguments.length == 1) {
var elementType = typeArguments[0];
+ // TODO(srawlins): Change to "potentially" constant type expression as
+ // per https://github.com/dart-lang/sdk/issues/47302?
if (!isConstantTypeExpression(elementType)) {
nodes.add(elementType);
}
@@ -317,6 +347,8 @@
var typeArguments = node.typeArguments?.arguments;
if (typeArguments != null && typeArguments.length == 1) {
var elementType = typeArguments[0];
+ // TODO(srawlins): Change to "potentially" constant type expression as
+ // per https://github.com/dart-lang/sdk/issues/47302?
if (!isConstantTypeExpression(elementType)) {
nodes.add(elementType);
}
@@ -350,7 +382,7 @@
_ConstantTypeChecker({required this.potentially});
- /// Return `true` if the [node] is a constant type expression.
+ /// Return `true` if the [node] is a (potentially) constant type expression.
bool check(TypeAnnotation? node) {
if (potentially) {
if (node is NamedType) {
diff --git a/pkg/analyzer/test/src/dart/constant/evaluation_test.dart b/pkg/analyzer/test/src/dart/constant/evaluation_test.dart
index 9044238..c2ad9fe 100644
--- a/pkg/analyzer/test/src/dart/constant/evaluation_test.dart
+++ b/pkg/analyzer/test/src/dart/constant/evaluation_test.dart
@@ -566,7 +566,48 @@
var result = _evaluateConstant('g');
assertType(result.type, 'void Function(int)');
assertElement(result.toFunctionValue(), findElement.topFunction('f'));
- _assertTypeArguments(result, null);
+ _assertTypeArguments(result, ['int']);
+ }
+
+ test_visitPrefixedIdentifier_genericFunction_instantiatedNonIdentifier() async {
+ await resolveTestCode('''
+void f<T>(T a) {}
+const b = false;
+const g1 = f;
+const g2 = f;
+const void Function(int) h = b ? g1 : g2;
+''');
+ var result = _evaluateConstant('h');
+ assertType(result.type, 'void Function(int)');
+ assertElement(result.toFunctionValue(), findElement.topFunction('f'));
+ _assertTypeArguments(result, ['int']);
+ }
+
+ test_visitPrefixedIdentifier_genericFunction_instantiatedPrefixed() async {
+ await resolveTestCode('''
+import '' as self;
+void f<T>(T a) {}
+const g = f;
+const void Function(int) h = self.g;
+''');
+ var result = _evaluateConstant('h');
+ assertType(result.type, 'void Function(int)');
+ assertElement(result.toFunctionValue(), findElement.topFunction('f'));
+ _assertTypeArguments(result, ['int']);
+ }
+
+ test_visitPropertyAccess_genericFunction_instantiated() async {
+ await resolveTestCode('''
+import '' as self;
+class C {
+ static void f<T>(T a) {}
+}
+const void Function(int) g = self.C.f;
+''');
+ var result = _evaluateConstant('g');
+ assertType(result.type, 'void Function(int)');
+ assertElement(result.toFunctionValue(), findElement.method('f'));
+ _assertTypeArguments(result, ['int']);
}
test_visitSimpleIdentifier_className() async {
@@ -587,6 +628,17 @@
var result = _evaluateConstant('g');
assertType(result.type, 'void Function(int)');
assertElement(result.toFunctionValue(), findElement.topFunction('f'));
+ _assertTypeArguments(result, ['int']);
+ }
+
+ test_visitSimpleIdentifier_genericFunction_nonGeneric() async {
+ await resolveTestCode('''
+void f(int a) {}
+const void Function(int) g = f;
+''');
+ var result = _evaluateConstant('g');
+ assertType(result.type, 'void Function(int)');
+ assertElement(result.toFunctionValue(), findElement.topFunction('f'));
_assertTypeArguments(result, null);
}
diff --git a/pkg/analyzer/test/src/dart/constant/potentially_constant_test.dart b/pkg/analyzer/test/src/dart/constant/potentially_constant_test.dart
index 0d881c1..fc1371d 100644
--- a/pkg/analyzer/test/src/dart/constant/potentially_constant_test.dart
+++ b/pkg/analyzer/test/src/dart/constant/potentially_constant_test.dart
@@ -458,6 +458,50 @@
]);
}
+ test_constructorReference_explicitTypeArguments() async {
+ await _assertConst('''
+class A {
+ final B Function() x;
+ const A(): x = B<int>.new;
+}
+
+class B<T> {}
+''', () => findNode.constructorReference('B<int>.new'));
+ }
+
+ test_constructorReference_noTypeArguments() async {
+ await _assertConst('''
+class A {
+ final B Function() x;
+ const A(): x = B.new;
+}
+
+class B {}
+''', () => findNode.constructorReference('B.new'));
+ }
+
+ test_functionReference_explicitTypeArguments() async {
+ await _assertConst('''
+class A {
+ final int Function(int) x;
+ const A(): x = id<int>;
+}
+
+X id<X>(X x) => x;
+''', () => findNode.functionReference('id<int>'));
+ }
+
+ test_functionReference_noTypeArguments() async {
+ await _assertConst('''
+class A {
+ final int Function(int) x;
+ const A(): x = id;
+}
+
+X id<X>(X x) => x;
+''', () => findNode.simple('id;'));
+ }
+
test_ifElement_then() async {
await _assertConst(r'''
const a = 0;
@@ -1177,6 +1221,15 @@
''', () => _xInitializer());
}
+ test_typeLiteral() async {
+ await _assertConst('''
+class A {
+ Type x;
+ const A(): x = List<int>;
+}
+''', () => findNode.typeLiteral('List<int>'));
+ }
+
_assertConst(String code, AstNode Function() getNode) async {
await resolveTestCode(code);
var node = getNode();
diff --git a/pkg/analyzer/test/src/fasta/message_coverage_test.dart b/pkg/analyzer/test/src/fasta/message_coverage_test.dart
index f21e632..726ed9d 100644
--- a/pkg/analyzer/test/src/fasta/message_coverage_test.dart
+++ b/pkg/analyzer/test/src/fasta/message_coverage_test.dart
@@ -10,8 +10,8 @@
import 'package:path/path.dart' as path;
import 'package:test/test.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';
-import 'package:yaml/yaml.dart';
+import '../../../tool/messages/error_code_info.dart';
import '../../generated/parser_test_base.dart';
main() {
@@ -35,45 +35,25 @@
return visitor.generatedNames;
}
- /// Given the path to the file 'messages.yaml', return a list of the top-level
- /// keys defined in that file that define an 'analyzerCode'.
- List<String> getMappedCodes(String messagesPath) {
- String content = io.File(messagesPath).readAsStringSync();
- YamlDocument document = loadYamlDocument(content);
- expect(document, isNotNull);
+ /// Return a list of the front end messages that define an 'analyzerCode'.
+ List<String> getMappedCodes() {
Set<String> codes = <String>{};
- YamlNode contents = document.contents;
- if (contents is YamlMap) {
- for (String name in contents.keys) {
- Object value = contents[name];
- if (value is YamlMap) {
- if (value['analyzerCode'] != null) {
- codes.add(name);
- }
- }
+ for (var entry in frontEndMessages.entries) {
+ var name = entry.key;
+ var errorCodeInfo = entry.value;
+ if (errorCodeInfo.analyzerCode.isNotEmpty) {
+ codes.add(name);
}
}
return codes.toList();
}
- /// Given the path to the file 'messages.yaml', return a list of the analyzer
- /// codes defined in that file.
- List<String> getReferencedCodes(String messagesPath) {
- String content = io.File(messagesPath).readAsStringSync();
- YamlDocument document = loadYamlDocument(content);
- expect(document, isNotNull);
+ /// Return a list of the analyzer codes defined in the front end's
+ /// `messages.yaml` file.
+ List<String> getReferencedCodes() {
Set<String> codes = <String>{};
- YamlNode contents = document.contents;
- if (contents is YamlMap) {
- for (String name in contents.keys) {
- Object value = contents[name];
- if (value is YamlMap) {
- var code = value['analyzerCode']?.toString();
- if (code != null) {
- codes.add(code);
- }
- }
- }
+ for (var errorCodeInfo in frontEndMessages.values) {
+ codes.addAll(errorCodeInfo.analyzerCode);
}
return codes.toList();
}
@@ -109,8 +89,7 @@
path.join(frontEndPath, 'lib', 'src', 'fasta', 'parser', 'parser.dart');
Set<String> generatedNames = getGeneratedNames(parserPath);
- String messagesPath = path.join(frontEndPath, 'messages.yaml');
- List<String> mappedCodes = getMappedCodes(messagesPath);
+ List<String> mappedCodes = getMappedCodes();
generatedNames.removeAll(mappedCodes);
if (generatedNames.isEmpty) {
@@ -133,9 +112,7 @@
path.join(analyzerPath, 'lib', 'src', 'fasta', 'error_converter.dart');
List<String> translatedCodes = getTranslatedCodes(astBuilderPath);
- String messagesPath =
- path.join(path.dirname(analyzerPath), 'front_end', 'messages.yaml');
- List<String> referencedCodes = getReferencedCodes(messagesPath);
+ List<String> referencedCodes = getReferencedCodes();
List<String> untranslated = <String>[];
for (String referencedCode in referencedCodes) {
diff --git a/pkg/analyzer/tool/messages/error_code_info.dart b/pkg/analyzer/tool/messages/error_code_info.dart
index 2097815..9a81b54 100644
--- a/pkg/analyzer/tool/messages/error_code_info.dart
+++ b/pkg/analyzer/tool/messages/error_code_info.dart
@@ -3,6 +3,96 @@
// BSD-style license that can be found in the LICENSE file.
import 'dart:convert';
+import 'dart:io';
+
+import 'package:analyzer_utilities/package_root.dart' as pkg_root;
+import 'package:path/path.dart';
+import 'package:yaml/yaml.dart' show loadYaml;
+
+/// Information about all the classes derived from `ErrorCode` that are code
+/// generated based on the contents of the analyzer and front end
+/// `messages.yaml` files.
+const List<ErrorClassInfo> errorClasses = [
+ ErrorClassInfo(
+ filePath: 'lib/src/analysis_options/error/option_codes.g.dart',
+ name: 'AnalysisOptionsErrorCode',
+ type: 'COMPILE_TIME_ERROR',
+ severity: 'ERROR'),
+ ErrorClassInfo(
+ filePath: 'lib/src/analysis_options/error/option_codes.g.dart',
+ name: 'AnalysisOptionsHintCode',
+ type: 'HINT',
+ severity: 'INFO'),
+ ErrorClassInfo(
+ filePath: 'lib/src/analysis_options/error/option_codes.g.dart',
+ name: 'AnalysisOptionsWarningCode',
+ type: 'STATIC_WARNING',
+ severity: 'WARNING'),
+ ErrorClassInfo(
+ filePath: 'lib/src/error/codes.g.dart',
+ name: 'CompileTimeErrorCode',
+ superclass: 'AnalyzerErrorCode',
+ type: 'COMPILE_TIME_ERROR',
+ extraImports: ['package:analyzer/src/error/analyzer_error_code.dart']),
+ ErrorClassInfo(
+ filePath: 'lib/src/error/codes.g.dart',
+ name: 'LanguageCode',
+ type: 'COMPILE_TIME_ERROR'),
+ ErrorClassInfo(
+ filePath: 'lib/src/error/codes.g.dart',
+ name: 'StaticWarningCode',
+ superclass: 'AnalyzerErrorCode',
+ type: 'STATIC_WARNING',
+ severity: 'WARNING',
+ extraImports: ['package:analyzer/src/error/analyzer_error_code.dart']),
+ ErrorClassInfo(
+ filePath: 'lib/src/dart/error/ffi_code.g.dart',
+ name: 'FfiCode',
+ superclass: 'AnalyzerErrorCode',
+ type: 'COMPILE_TIME_ERROR',
+ extraImports: ['package:analyzer/src/error/analyzer_error_code.dart']),
+ ErrorClassInfo(
+ filePath: 'lib/src/dart/error/hint_codes.g.dart',
+ name: 'HintCode',
+ superclass: 'AnalyzerErrorCode',
+ type: 'HINT',
+ extraImports: ['package:analyzer/src/error/analyzer_error_code.dart']),
+ ErrorClassInfo(
+ filePath: 'lib/src/dart/error/syntactic_errors.g.dart',
+ name: 'ParserErrorCode',
+ type: 'SYNTACTIC_ERROR',
+ severity: 'ERROR',
+ includeCfeMessages: true),
+ ErrorClassInfo(
+ filePath: 'lib/src/manifest/manifest_warning_code.g.dart',
+ name: 'ManifestWarningCode',
+ type: 'STATIC_WARNING',
+ severity: 'WARNING'),
+ ErrorClassInfo(
+ filePath: 'lib/src/pubspec/pubspec_warning_code.g.dart',
+ name: 'PubspecWarningCode',
+ type: 'STATIC_WARNING',
+ severity: 'WARNING'),
+];
+
+/// Decoded messages from the analyzer's `messages.yaml` file.
+final Map<String, Map<String, ErrorCodeInfo>> analyzerMessages =
+ _loadAnalyzerMessages();
+
+/// The path to the `analyzer` package.
+final String analyzerPkgPath =
+ normalize(join(pkg_root.packageRoot, 'analyzer'));
+
+/// A set of tables mapping between front end and analyzer error codes.
+final CfeToAnalyzerErrorCodeTables cfeToAnalyzerErrorCodeTables =
+ CfeToAnalyzerErrorCodeTables._(frontEndMessages);
+
+/// Decoded messages from the front end's `messages.yaml` file.
+final Map<String, ErrorCodeInfo> frontEndMessages = _loadFrontEndMessages();
+
+/// The path to the `front_end` package.
+final String frontEndPkgPath =
+ normalize(join(pkg_root.packageRoot, 'front_end'));
/// Decodes a YAML object (obtained from `pkg/analyzer/messages.yaml`) into a
/// two-level map of [ErrorCodeInfo], indexed first by class name and then by
@@ -32,6 +122,20 @@
return result;
}
+/// Loads analyzer messages from the analyzer's `messages.yaml` file.
+Map<String, Map<String, ErrorCodeInfo>> _loadAnalyzerMessages() {
+ Map<dynamic, dynamic> messagesYaml =
+ loadYaml(File(join(analyzerPkgPath, 'messages.yaml')).readAsStringSync());
+ return decodeAnalyzerMessagesYaml(messagesYaml);
+}
+
+/// Loads front end messages from the front end's `messages.yaml` file.
+Map<String, ErrorCodeInfo> _loadFrontEndMessages() {
+ Map<dynamic, dynamic> messagesYaml =
+ loadYaml(File(join(frontEndPkgPath, 'messages.yaml')).readAsStringSync());
+ return decodeCfeMessagesYaml(messagesYaml);
+}
+
/// Data tables mapping between CFE errors and their corresponding automatically
/// generated analyzer errors.
class CfeToAnalyzerErrorCodeTables {
@@ -59,7 +163,7 @@
/// automatically generated, and whose values are the front end error name.
final Map<ErrorCodeInfo, String> infoToFrontEndCode = {};
- CfeToAnalyzerErrorCodeTables(Map<String, ErrorCodeInfo> messages) {
+ CfeToAnalyzerErrorCodeTables._(Map<String, ErrorCodeInfo> messages) {
for (var entry in messages.entries) {
var errorCodeInfo = entry.value;
var index = errorCodeInfo.index;
@@ -110,6 +214,59 @@
}
}
+/// Information about a code generated class derived from `ErrorCode`.
+class ErrorClassInfo {
+ /// A list of additional import URIs that are needed by the code generated
+ /// for this class.
+ final List<String> extraImports;
+
+ /// The file path (relative to the root of `pkg/analyzer`) of the generated
+ /// file containing this class.
+ final String filePath;
+
+ /// True if this class should contain error messages extracted from the front
+ /// end's `messages.yaml` file.
+ ///
+ /// Note: at the moment we only support extracting front end error messages to
+ /// a single error class.
+ final bool includeCfeMessages;
+
+ /// The name of this class.
+ final String name;
+
+ /// The severity of errors in this class, or `null` if the severity should be
+ /// based on the [type] of the error.
+ final String? severity;
+
+ /// The superclass of this class.
+ final String superclass;
+
+ /// The type of errors in this class.
+ final String type;
+
+ const ErrorClassInfo(
+ {this.extraImports = const [],
+ required this.filePath,
+ this.includeCfeMessages = false,
+ required this.name,
+ this.severity,
+ this.superclass = 'ErrorCode',
+ required this.type});
+
+ /// Generates the code to compute the severity of errors of this class.
+ String get severityCode {
+ var severity = this.severity;
+ if (severity == null) {
+ return '$typeCode.severity';
+ } else {
+ return 'ErrorSeverity.$severity';
+ }
+ }
+
+ /// Generates the code to compute the type of errors of this class.
+ String get typeCode => 'ErrorType.$type';
+}
+
/// In-memory representation of error code information obtained from either a
/// `messages.yaml` file. Supports both the analyzer and front_end message file
/// formats.
diff --git a/pkg/analyzer/tool/messages/generate.dart b/pkg/analyzer/tool/messages/generate.dart
index 7f70d5c..93b703b 100644
--- a/pkg/analyzer/tool/messages/generate.dart
+++ b/pkg/analyzer/tool/messages/generate.dart
@@ -21,7 +21,6 @@
import 'package:analyzer_utilities/package_root.dart' as pkg_root;
import 'package:analyzer_utilities/tools.dart';
import 'package:path/path.dart';
-import 'package:yaml/yaml.dart' show loadYaml;
import 'error_code_info.dart';
@@ -33,94 +32,14 @@
..printSummary();
}
-/// Information about all the classes derived from `ErrorCode` that should be
-/// generated.
-const List<_ErrorClassInfo> _errorClasses = [
- _ErrorClassInfo(
- filePath: 'lib/src/analysis_options/error/option_codes.g.dart',
- name: 'AnalysisOptionsErrorCode',
- type: 'COMPILE_TIME_ERROR',
- severity: 'ERROR'),
- _ErrorClassInfo(
- filePath: 'lib/src/analysis_options/error/option_codes.g.dart',
- name: 'AnalysisOptionsHintCode',
- type: 'HINT',
- severity: 'INFO'),
- _ErrorClassInfo(
- filePath: 'lib/src/analysis_options/error/option_codes.g.dart',
- name: 'AnalysisOptionsWarningCode',
- type: 'STATIC_WARNING',
- severity: 'WARNING'),
- _ErrorClassInfo(
- filePath: 'lib/src/error/codes.g.dart',
- name: 'CompileTimeErrorCode',
- superclass: 'AnalyzerErrorCode',
- type: 'COMPILE_TIME_ERROR',
- extraImports: ['package:analyzer/src/error/analyzer_error_code.dart']),
- _ErrorClassInfo(
- filePath: 'lib/src/error/codes.g.dart',
- name: 'LanguageCode',
- type: 'COMPILE_TIME_ERROR'),
- _ErrorClassInfo(
- filePath: 'lib/src/error/codes.g.dart',
- name: 'StaticWarningCode',
- superclass: 'AnalyzerErrorCode',
- type: 'STATIC_WARNING',
- severity: 'WARNING',
- extraImports: ['package:analyzer/src/error/analyzer_error_code.dart']),
- _ErrorClassInfo(
- filePath: 'lib/src/dart/error/ffi_code.g.dart',
- name: 'FfiCode',
- superclass: 'AnalyzerErrorCode',
- type: 'COMPILE_TIME_ERROR',
- extraImports: ['package:analyzer/src/error/analyzer_error_code.dart']),
- _ErrorClassInfo(
- filePath: 'lib/src/dart/error/hint_codes.g.dart',
- name: 'HintCode',
- superclass: 'AnalyzerErrorCode',
- type: 'HINT',
- extraImports: ['package:analyzer/src/error/analyzer_error_code.dart']),
- _ErrorClassInfo(
- filePath: 'lib/src/dart/error/syntactic_errors.g.dart',
- name: 'ParserErrorCode',
- type: 'SYNTACTIC_ERROR',
- severity: 'ERROR',
- includeCfeMessages: true),
- _ErrorClassInfo(
- filePath: 'lib/src/manifest/manifest_warning_code.g.dart',
- name: 'ManifestWarningCode',
- type: 'STATIC_WARNING',
- severity: 'WARNING'),
- _ErrorClassInfo(
- filePath: 'lib/src/pubspec/pubspec_warning_code.g.dart',
- name: 'PubspecWarningCode',
- type: 'STATIC_WARNING',
- severity: 'WARNING'),
-];
-
/// A list of all targets generated by this code generator.
final List<GeneratedContent> allTargets = _analyzerGeneratedFiles();
-/// The path to the `analyzer` package.
-final String analyzerPkgPath =
- normalize(join(pkg_root.packageRoot, 'analyzer'));
-
-/// The path to the `front_end` package.
-final String frontEndPkgPath =
- normalize(join(pkg_root.packageRoot, 'front_end'));
-
-/// Decoded messages from the anlayzer's `messages.yaml` file.
-final Map<String, Map<String, ErrorCodeInfo>> _analyzerMessages =
- _loadAnalyzerMessages();
-
-/// Decoded messages from the front end's `messages.yaml` file.
-final Map<String, ErrorCodeInfo> _frontEndMessages = _loadFrontEndMessages();
-
/// Generates a list of [GeneratedContent] objects describing all the analyzer
/// files that need to be generated.
List<GeneratedContent> _analyzerGeneratedFiles() {
- var classesByFile = <String, List<_ErrorClassInfo>>{};
- for (var errorClassInfo in _errorClasses) {
+ var classesByFile = <String, List<ErrorClassInfo>>{};
+ for (var errorClassInfo in errorClasses) {
(classesByFile[errorClassInfo.filePath] ??= []).add(errorClassInfo);
}
return [
@@ -133,25 +52,9 @@
];
}
-/// Loads analyzer messages from the analyzer's `messages.yaml` file.
-Map<String, Map<String, ErrorCodeInfo>> _loadAnalyzerMessages() {
- Map<dynamic, dynamic> messagesYaml =
- loadYaml(File(join(analyzerPkgPath, 'messages.yaml')).readAsStringSync());
- return decodeAnalyzerMessagesYaml(messagesYaml);
-}
-
-/// Loads front end messages from the front end's `messages.yaml` file.
-Map<String, ErrorCodeInfo> _loadFrontEndMessages() {
- Map<dynamic, dynamic> messagesYaml =
- loadYaml(File(join(frontEndPkgPath, 'messages.yaml')).readAsStringSync());
- return decodeCfeMessagesYaml(messagesYaml);
-}
-
/// Code generator for analyzer error classes.
class _AnalyzerErrorGenerator {
- final List<_ErrorClassInfo> errorClasses;
- final CfeToAnalyzerErrorCodeTables tables =
- CfeToAnalyzerErrorCodeTables(_frontEndMessages);
+ final List<ErrorClassInfo> errorClasses;
final out = StringBuffer('''
// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
@@ -193,8 +96,9 @@
out.writeln();
out.write('class ${errorClass.name} extends ${errorClass.superclass} {');
var entries = [
- ..._analyzerMessages[errorClass.name]!.entries,
- if (errorClass.includeCfeMessages) ...tables.analyzerCodeToInfo.entries
+ ...analyzerMessages[errorClass.name]!.entries,
+ if (errorClass.includeCfeMessages)
+ ...cfeToAnalyzerErrorCodeTables.analyzerCodeToInfo.entries
];
for (var entry in entries..sort((a, b) => a.key.compareTo(b.key))) {
var errorName = entry.key;
@@ -234,66 +138,22 @@
void _generateFastaAnalyzerErrorCodeList() {
out.writeln('final fastaAnalyzerErrorCodes = <ErrorCode?>[');
- for (var entry in tables.indexToInfo) {
- var name = tables.infoToAnalyzerCode[entry];
+ for (var entry in cfeToAnalyzerErrorCodeTables.indexToInfo) {
+ var name = cfeToAnalyzerErrorCodeTables.infoToAnalyzerCode[entry];
out.writeln('${name == null ? 'null' : 'ParserErrorCode.$name'},');
}
out.writeln('];');
}
}
-class _ErrorClassInfo {
- final List<String> extraImports;
-
- final String filePath;
-
- final bool includeCfeMessages;
-
- final String name;
-
- final String? severity;
-
- final String superclass;
-
- final String type;
-
- const _ErrorClassInfo(
- {this.extraImports = const [],
- required this.filePath,
- this.includeCfeMessages = false,
- required this.name,
- this.severity,
- this.superclass = 'ErrorCode',
- required this.type});
-
- String get severityCode {
- var severity = this.severity;
- if (severity == null) {
- return '$typeCode.severity';
- } else {
- return 'ErrorSeverity.$severity';
- }
- }
-
- String get typeCode => 'ErrorType.$type';
-}
-
class _SyntacticErrorGenerator {
- final Map<String, ErrorCodeInfo> cfeMessages;
- final CfeToAnalyzerErrorCodeTables tables;
- final Map<String, Map<String, ErrorCodeInfo>> analyzerMessages;
final String errorConverterSource;
final String parserSource;
factory _SyntacticErrorGenerator() {
- String frontEndPkgPath = normalize(join(pkg_root.packageRoot, 'front_end'));
String frontEndSharedPkgPath =
normalize(join(pkg_root.packageRoot, '_fe_analyzer_shared'));
- Map<dynamic, dynamic> cfeMessagesYaml = loadYaml(
- File(join(frontEndPkgPath, 'messages.yaml')).readAsStringSync());
- Map<dynamic, dynamic> analyzerMessagesYaml = loadYaml(
- File(join(analyzerPkgPath, 'messages.yaml')).readAsStringSync());
String errorConverterSource = File(join(analyzerPkgPath,
joinAll(posix.split('lib/src/fasta/error_converter.dart'))))
.readAsStringSync();
@@ -301,22 +161,17 @@
joinAll(posix.split('lib/src/parser/parser.dart'))))
.readAsStringSync();
- return _SyntacticErrorGenerator._(
- decodeCfeMessagesYaml(cfeMessagesYaml),
- decodeAnalyzerMessagesYaml(analyzerMessagesYaml),
- errorConverterSource,
- parserSource);
+ return _SyntacticErrorGenerator._(errorConverterSource, parserSource);
}
- _SyntacticErrorGenerator._(this.cfeMessages, this.analyzerMessages,
- this.errorConverterSource, this.parserSource)
- : tables = CfeToAnalyzerErrorCodeTables(cfeMessages);
+ _SyntacticErrorGenerator._(this.errorConverterSource, this.parserSource);
void checkForManualChanges() {
// Check for ParserErrorCodes that could be removed from
// error_converter.dart now that those ParserErrorCodes are auto generated.
int converterCount = 0;
- for (var errorCode in tables.infoToAnalyzerCode.values) {
+ for (var errorCode
+ in cfeToAnalyzerErrorCodeTables.infoToAnalyzerCode.values) {
if (errorConverterSource.contains('"$errorCode"')) {
if (converterCount == 0) {
print('');
@@ -355,18 +210,19 @@
}
// Remove entries that have already been translated
- for (ErrorCodeInfo entry in tables.infoToAnalyzerCode.keys) {
+ for (ErrorCodeInfo entry
+ in cfeToAnalyzerErrorCodeTables.infoToAnalyzerCode.keys) {
messageToName.remove(messageFromEntryTemplate(entry));
}
// Print the # of autogenerated ParserErrorCodes.
- print('${tables.infoToAnalyzerCode.length} of '
+ print('${cfeToAnalyzerErrorCodeTables.infoToAnalyzerCode.length} of '
'${messageToName.length} ParserErrorCodes generated.');
// List the ParserErrorCodes that could easily be auto generated
// but have not been already.
final analyzerToFasta = <String, List<String>>{};
- cfeMessages.forEach((fastaName, entry) {
+ frontEndMessages.forEach((fastaName, entry) {
final analyzerName = messageToName[messageFromEntryTemplate(entry)];
if (analyzerName != null) {
analyzerToFasta
@@ -407,7 +263,8 @@
}
}
if (fastaErrorCode != null &&
- tables.frontEndCodeToInfo[fastaErrorCode] == null) {
+ cfeToAnalyzerErrorCodeTables.frontEndCodeToInfo[fastaErrorCode] ==
+ null) {
untranslatedFastaErrorCodes.add(fastaErrorCode);
}
}
@@ -420,7 +277,7 @@
for (String fastaErrorCode in sorted) {
String analyzerCode = '';
String problemMessage = '';
- var entry = cfeMessages[fastaErrorCode];
+ var entry = frontEndMessages[fastaErrorCode];
if (entry != null) {
// TODO(paulberry): handle multiple analyzer codes
if (entry.index == null && entry.analyzerCode.length == 1) {
diff --git a/pkg/analyzer/tool/messages/generate_test.dart b/pkg/analyzer/tool/messages/generate_test.dart
index 462df57..02a43cf 100644
--- a/pkg/analyzer/tool/messages/generate_test.dart
+++ b/pkg/analyzer/tool/messages/generate_test.dart
@@ -8,6 +8,7 @@
import 'package:analyzer_utilities/tools.dart';
import 'package:path/path.dart';
+import 'error_code_info.dart';
import 'generate.dart';
main() async {
diff --git a/pkg/front_end/messages.status b/pkg/front_end/messages.status
index 9feaf38..8f81677 100644
--- a/pkg/front_end/messages.status
+++ b/pkg/front_end/messages.status
@@ -523,6 +523,10 @@
JsInteropExternalMemberNotJSAnnotated/example: Fail # Web compiler specific
JsInteropIndexNotSupported/analyzerCode: Fail # Web compiler specific
JsInteropIndexNotSupported/example: Fail # Web compiler specific
+JsInteropStaticInteropWithInstanceMembers/analyzerCode: Fail # Web compiler specific
+JsInteropStaticInteropWithInstanceMembers/example: Fail # Web compiler specific
+JsInteropStaticInteropWithNonStaticSupertype/analyzerCode: Fail # Web compiler specific
+JsInteropStaticInteropWithNonStaticSupertype/example: Fail # Web compiler specific
JsInteropJSClassExtendsDartClass/analyzerCode: Fail # Web compiler specific
JsInteropJSClassExtendsDartClass/example: Fail # Web compiler specific
JsInteropNamedParameters/analyzerCode: Fail # Web compiler specific
diff --git a/pkg/front_end/messages.yaml b/pkg/front_end/messages.yaml
index dc77400..cae1d1b 100644
--- a/pkg/front_end/messages.yaml
+++ b/pkg/front_end/messages.yaml
@@ -5050,6 +5050,14 @@
problemMessage: "JS interop classes do not support [] and []= operator methods."
correctionMessage: "Try replacing with a normal method."
+JsInteropStaticInteropWithInstanceMembers:
+ problemMessage: "JS interop class '#name' with `@staticInterop` annotation cannot declare instance members."
+ correctionMessage: "Try moving the instance member to a static extension."
+
+JsInteropStaticInteropWithNonStaticSupertype:
+ problemMessage: "JS interop class '#name' has an `@staticInterop` annotation, but has supertype '#name2', which is non-static."
+ correctionMessage: "Try marking the supertype as a static interop class using `@staticInterop`."
+
JsInteropJSClassExtendsDartClass:
problemMessage: "JS interop class '#name' cannot extend Dart class '#name2'."
correctionMessage: "Try removing the JS interop annotation or adding it to the parent class."
@@ -5059,8 +5067,8 @@
correctionMessage: "Try replacing them with normal or optional parameters."
JsInteropNativeClassInAnnotation:
- problemMessage: "JS interop class '#name' conflicts with natively supported class '#name2' in '#string3'."
- correctionMessage: "Try making the @JS class into an @anonymous class or use js_util on the JS object."
+ problemMessage: "Non-static JS interop class '#name' conflicts with natively supported class '#name2' in '#string3'."
+ correctionMessage: "Try replacing it with a static JS interop class using `@staticInterop` with extension methods, or use js_util to interact with the native object of type '#name2'."
JsInteropNonExternalConstructor:
problemMessage: "JS interop classes do not support non-external constructors."
diff --git a/pkg/front_end/test/spell_checking_list_messages.txt b/pkg/front_end/test/spell_checking_list_messages.txt
index 5d363a0..e3c5e27 100644
--- a/pkg/front_end/test/spell_checking_list_messages.txt
+++ b/pkg/front_end/test/spell_checking_list_messages.txt
@@ -37,6 +37,7 @@
guides
h
https
+interact
interop
intervening
js_util
@@ -65,6 +66,7 @@
sdksummary
solutions
stacktrace
+staticinterop
stringokempty
struct<#name
structs
diff --git a/pkg/js/lib/js.dart b/pkg/js/lib/js.dart
index 9de2f52..1c099c3 100644
--- a/pkg/js/lib/js.dart
+++ b/pkg/js/lib/js.dart
@@ -25,6 +25,10 @@
const _Anonymous();
}
+// class _StaticInterop {
+// const _StaticInterop();
+// }
+
/// An annotation that indicates a [JS] annotated class is structural and does
/// not have a known JavaScript prototype.
///
@@ -33,3 +37,13 @@
/// desugars to creating a JavaScript object literal with name-value pairs
/// corresponding to the parameter names and values.
const _Anonymous anonymous = _Anonymous();
+
+/// [staticInterop] enables the [JS] annotated class to be treated as a "static"
+/// interop class.
+///
+/// These classes allow interop with native types, like the ones in `dart:html`.
+/// These classes should not contain any instance members, inherited or
+/// otherwise, and should instead use static extension members.
+// TODO(47324, 47325): Uncomment these annotations once erasure and subtyping
+// are ready.
+// const _StaticInterop staticInterop = _StaticInterop();
diff --git a/runtime/bin/thread_android.cc b/runtime/bin/thread_android.cc
index d386ceb..60c911a 100644
--- a/runtime/bin/thread_android.cc
+++ b/runtime/bin/thread_android.cc
@@ -86,8 +86,11 @@
uword parameter = data->parameter();
delete data;
- // Set the thread name.
- pthread_setname_np(pthread_self(), name);
+ // Set the thread name. There is 16 bytes limit on the name (including \0).
+ // pthread_setname_np ignores names that are too long rather than truncating.
+ char truncated_name[16];
+ snprintf(truncated_name, sizeof(truncated_name), "%s", name);
+ pthread_setname_np(pthread_self(), truncated_name);
// Call the supplied thread start function handing it its parameters.
function(parameter);
diff --git a/runtime/bin/thread_linux.cc b/runtime/bin/thread_linux.cc
index b671d0c..1a46a3e 100644
--- a/runtime/bin/thread_linux.cc
+++ b/runtime/bin/thread_linux.cc
@@ -86,8 +86,11 @@
uword parameter = data->parameter();
delete data;
- // Set the thread name.
- pthread_setname_np(pthread_self(), name);
+ // Set the thread name. There is 16 bytes limit on the name (including \0).
+ // pthread_setname_np ignores names that are too long rather than truncating.
+ char truncated_name[16];
+ snprintf(truncated_name, sizeof(truncated_name), "%s", name);
+ pthread_setname_np(pthread_self(), truncated_name);
// Call the supplied thread start function handing it its parameters.
function(parameter);
diff --git a/runtime/observatory/lib/elements.dart b/runtime/observatory/lib/elements.dart
index d49b31c..80f325c 100644
--- a/runtime/observatory/lib/elements.dart
+++ b/runtime/observatory/lib/elements.dart
@@ -87,7 +87,6 @@
export 'package:observatory/src/elements/strongly_reachable_instances.dart';
export 'package:observatory/src/elements/subtypetestcache_ref.dart';
export 'package:observatory/src/elements/subtypetestcache_view.dart';
-export 'package:observatory/src/elements/timeline/dashboard.dart';
export 'package:observatory/src/elements/timeline_page.dart';
export 'package:observatory/src/elements/type_arguments_ref.dart';
export 'package:observatory/src/elements/unknown_ref.dart';
diff --git a/runtime/observatory/lib/src/app/application.dart b/runtime/observatory/lib/src/app/application.dart
index e7eae4d..15defac 100644
--- a/runtime/observatory/lib/src/app/application.dart
+++ b/runtime/observatory/lib/src/app/application.dart
@@ -171,7 +171,6 @@
_pageRegistry.add(new PortsPage(this));
_pageRegistry.add(new LoggingPage(this));
_pageRegistry.add(new TimelinePage(this));
- _pageRegistry.add(new TimelineDashboardPage(this));
_pageRegistry.add(new ProcessSnapshotPage(this));
// Note that ErrorPage must be the last entry in the list as it is
// the catch all.
diff --git a/runtime/observatory/lib/src/app/page.dart b/runtime/observatory/lib/src/app/page.dart
index 9adaad7..e8c69d6 100644
--- a/runtime/observatory/lib/src/app/page.dart
+++ b/runtime/observatory/lib/src/app/page.dart
@@ -974,38 +974,6 @@
bool canVisit(Uri uri) => uri.path == 'timeline';
}
-class TimelineDashboardPage extends Page {
- TimelineDashboardPage(app) : super(app);
-
- DivElement container = new DivElement();
-
- void onInstall() {
- if (element == null) {
- element = container;
- }
- }
-
- void _visit(Uri uri) {
- assert(canVisit(uri));
- app.vm.load().then((_) {
- container.children = <Element>[
- new TimelineDashboardElement(
- app.vm, _timelineRepository, app.notifications,
- queue: app.queue)
- .element
- ];
- });
- }
-
- @override
- void onUninstall() {
- super.onUninstall();
- container.children = const [];
- }
-
- bool canVisit(Uri uri) => uri.path == 'timeline-dashboard';
-}
-
class ProcessSnapshotPage extends Page {
ProcessSnapshotPage(app) : super(app);
diff --git a/runtime/observatory/lib/src/elements/css/shared.css b/runtime/observatory/lib/src/elements/css/shared.css
index f8c0756..1fc1bfb 100644
--- a/runtime/observatory/lib/src/elements/css/shared.css
+++ b/runtime/observatory/lib/src/elements/css/shared.css
@@ -1379,6 +1379,72 @@
flex-shrink: 0;
}
+/* process-snapshot */
+
+.process-snapshot .statusMessage {
+ font-size: 150%;
+ font-weight: bold;
+}
+.process-snapshot .statusBox {
+ height: 100%;
+ padding: 1em;
+}
+.process-snapshot .explanation {
+ display: block;
+ display: -webkit-box;
+ -webkit-line-clamp: 4;
+ -webkit-box-orient: vertical;
+ max-height: 80px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+.process-snapshot .virtual-tree {
+ position: absolute;
+ height: auto;
+ top: 250px;
+ bottom: 0;
+ left: 0;
+ right: 0;
+}
+.process-snapshot .tree-item {
+ box-sizing: border-box;
+ line-height: 30px;
+ height: 30px;
+ padding-left: 5%;
+ padding-right: 5%;
+
+ display: flex;
+ flex-direction: row;
+}
+.process-snapshot .tree-item > .size,
+.process-snapshot .tree-item > .percentage {
+ text-align: right;
+ margin-left: 0.25em;
+ margin-right: 0.25em;
+ flex-basis: 5em;
+ flex-grow: 0;
+ flex-shrink: 0;
+}
+.process-snapshot .tree-item > .edge {
+ margin-left: 0.25em;
+ margin-right: 0.25em;
+ flex-basis: 0;
+ flex-grow: 1;
+ flex-shrink: 1;
+}
+.process-snapshot .tree-item > .name {
+ margin-left: 0.5em;
+ margin-right: 0.5em;
+ flex-basis: 0;
+ flex-grow: 4;
+ flex-shrink: 4;
+}
+.process-snapshot .tree-item > .link {
+ margin-left: 0.25em;
+ margin-right: 0.25em;
+ flex-grow: 0;
+ flex-shrink: 0;
+}
/* icdata-ref */
diff --git a/runtime/observatory/lib/src/elements/heap_snapshot.dart b/runtime/observatory/lib/src/elements/heap_snapshot.dart
index 0aae569..c8a5829 100644
--- a/runtime/observatory/lib/src/elements/heap_snapshot.dart
+++ b/runtime/observatory/lib/src/elements/heap_snapshot.dart
@@ -73,45 +73,6 @@
HeapSnapshotTreeMode.classesTableDiff,
];
-abstract class DiffTreeMap<T> extends TreeMap<T> {
- int getSizeA(T node);
- int getSizeB(T node);
-
- // We need to sum gains and losses separately because they both contribute
- // area to the tree map tiles, i.e., losses don't have negative area in the
- // visualization. For this reason, common is not necessarily
- // max(sizeA,sizeB)-min(sizeA,sizeB), gain is not necessarily
- // abs(sizeB-sizeA), etc.
- int getGain(T node);
- int getLoss(T node);
- int getCommon(T node);
-
- String getName(T node);
- String getType(T node);
-
- int getArea(T node) => getCommon(node) + getGain(node) + getLoss(node);
- String getLabel(T node) {
- var name = getName(node);
- var sizeA = Utils.formatSize(getSizeA(node));
- var sizeB = Utils.formatSize(getSizeB(node));
- return "$name [$sizeA → $sizeB]";
- }
-
- String getBackground(T node) {
- int l = getLoss(node);
- int c = getCommon(node);
- int g = getGain(node);
- int a = l + c + g;
- if (a == 0) {
- return "white";
- }
- // Stripes of green, white and red whose areas are poritional to loss, common and gain.
- String stop1 = (l / a * 100).toString();
- String stop2 = ((l + c) / a * 100).toString();
- return "linear-gradient(to right, #66FF99 $stop1%, white $stop1% $stop2%, #FF6680 $stop2%)";
- }
-}
-
class DominatorTreeMap extends NormalTreeMap<SnapshotObject> {
HeapSnapshotElement element;
DominatorTreeMap(this.element);
diff --git a/runtime/observatory/lib/src/elements/process_snapshot.dart b/runtime/observatory/lib/src/elements/process_snapshot.dart
index 3c64441..4aef2cf 100644
--- a/runtime/observatory/lib/src/elements/process_snapshot.dart
+++ b/runtime/observatory/lib/src/elements/process_snapshot.dart
@@ -4,6 +4,7 @@
import 'dart:async';
import 'dart:html';
+import 'dart:math' as Math;
import 'dart:convert';
import 'package:observatory/models.dart' as M;
import 'package:observatory/object_graph.dart';
@@ -25,6 +26,25 @@
import 'package:observatory/repositories.dart';
import 'package:observatory/utils.dart';
+enum ProcessSnapshotTreeMode {
+ treeMap,
+ tree,
+ treeMapDiff,
+ treeDiff,
+}
+
+// Note the order of these lists is reflected in the UI, and the first option
+// is the default.
+const _viewModes = [
+ ProcessSnapshotTreeMode.treeMap,
+ ProcessSnapshotTreeMode.tree,
+];
+
+const _diffModes = [
+ ProcessSnapshotTreeMode.treeMapDiff,
+ ProcessSnapshotTreeMode.treeDiff,
+];
+
class ProcessItemTreeMap extends NormalTreeMap<Map> {
ProcessSnapshotElement element;
ProcessItemTreeMap(this.element);
@@ -43,6 +63,148 @@
void onDetails(Map node) {}
}
+class ProcessItemDiff {
+ Map? _a;
+ Map? _b;
+ ProcessItemDiff? parent;
+ List<ProcessItemDiff>? children;
+ int retainedGain = -1;
+ int retainedLoss = -1;
+ int retainedCommon = -1;
+
+ int get retainedSizeA => _a == null ? 0 : _a!["size"];
+ int get retainedSizeB => _b == null ? 0 : _b!["size"];
+ int get retainedSizeDiff => retainedSizeB - retainedSizeA;
+
+ int get shallowSizeA {
+ if (_a == null) return 0;
+ var s = _a!["size"];
+ for (var c in _a!["children"]) {
+ s -= c["size"];
+ }
+ return s;
+ }
+
+ int get shallowSizeB {
+ if (_b == null) return 0;
+ var s = _b!["size"];
+ for (var c in _b!["children"]) {
+ s -= c["size"];
+ }
+ return s;
+ }
+
+ int get shallowSizeDiff => shallowSizeB - shallowSizeA;
+
+ String get name => _a == null ? _b!["name"] : _a!["name"];
+
+ static ProcessItemDiff from(Map a, Map b) {
+ var root = new ProcessItemDiff();
+ root._a = a;
+ root._b = b;
+
+ // We must use an explicit stack instead of the call stack because the
+ // dominator tree can be arbitrarily deep. We need to compute the full
+ // tree to compute areas, so we do this eagerly to avoid having to
+ // repeatedly test for initialization.
+ var worklist = <ProcessItemDiff>[];
+ worklist.add(root);
+ // Compute children top-down.
+ for (var i = 0; i < worklist.length; i++) {
+ worklist[i]._computeChildren(worklist);
+ }
+ // Compute area botton-up.
+ for (var i = worklist.length - 1; i >= 0; i--) {
+ worklist[i]._computeArea();
+ }
+
+ return root;
+ }
+
+ void _computeChildren(List<ProcessItemDiff> worklist) {
+ assert(children == null);
+ children = <ProcessItemDiff>[];
+
+ // Matching children by name.
+ final childrenB = <String, Map>{};
+ if (_b != null)
+ for (var childB in _b!["children"]) {
+ childrenB[childB["name"]] = childB;
+ }
+ if (_a != null)
+ for (var childA in _a!["children"]) {
+ var childDiff = new ProcessItemDiff();
+ childDiff.parent = this;
+ childDiff._a = childA;
+ var qualifiedName = childA["name"];
+ var childB = childrenB[qualifiedName];
+ if (childB != null) {
+ childrenB.remove(qualifiedName);
+ childDiff._b = childB;
+ }
+ children!.add(childDiff);
+ worklist.add(childDiff);
+ }
+ for (var childB in childrenB.values) {
+ var childDiff = new ProcessItemDiff();
+ childDiff.parent = this;
+ childDiff._b = childB;
+ children!.add(childDiff);
+ worklist.add(childDiff);
+ }
+
+ if (children!.length == 0) {
+ // Compress.
+ children = const <ProcessItemDiff>[];
+ }
+ }
+
+ void _computeArea() {
+ int g = 0;
+ int l = 0;
+ int c = 0;
+ for (var child in children!) {
+ g += child.retainedGain;
+ l += child.retainedLoss;
+ c += child.retainedCommon;
+ }
+ int d = shallowSizeDiff;
+ if (d > 0) {
+ g += d;
+ c += shallowSizeA;
+ } else {
+ l -= d;
+ c += shallowSizeB;
+ }
+ assert(retainedSizeA + g - l == retainedSizeB);
+ retainedGain = g;
+ retainedLoss = l;
+ retainedCommon = c;
+ }
+}
+
+class ProcessItemDiffTreeMap extends DiffTreeMap<ProcessItemDiff> {
+ ProcessSnapshotElement element;
+ ProcessItemDiffTreeMap(this.element);
+
+ int getSizeA(ProcessItemDiff node) => node.retainedSizeA;
+ int getSizeB(ProcessItemDiff node) => node.retainedSizeB;
+ int getGain(ProcessItemDiff node) => node.retainedGain;
+ int getLoss(ProcessItemDiff node) => node.retainedLoss;
+ int getCommon(ProcessItemDiff node) => node.retainedCommon;
+
+ String getType(ProcessItemDiff node) => node.name;
+ String getName(ProcessItemDiff node) => node.name;
+ ProcessItemDiff? getParent(ProcessItemDiff node) => node.parent;
+ Iterable<ProcessItemDiff> getChildren(ProcessItemDiff node) => node.children!;
+ void onSelect(ProcessItemDiff node) {
+ element.diffSelection = node;
+ element._r.dirty();
+ }
+
+ void onDetails(ProcessItemDiff node) {}
+}
+
class ProcessSnapshotElement extends CustomElement implements Renderable {
late RenderingScheduler<ProcessSnapshotElement> _r;
@@ -56,8 +218,10 @@
List<Map> _loadedSnapshots = <Map>[];
Map? selection;
+ ProcessItemDiff? diffSelection;
Map? _snapshotA;
Map? _snapshotB;
+ ProcessSnapshotTreeMode _mode = ProcessSnapshotTreeMode.treeMap;
factory ProcessSnapshotElement(
M.VM vm, M.EventRepository events, M.NotificationRepository notifications,
@@ -160,6 +324,8 @@
_loadedSnapshots.add(snapshot);
_snapshotA = snapshot;
_snapshotB = snapshot;
+ selection = null;
+ diffSelection = null;
_r.dirty();
}
@@ -210,27 +376,210 @@
..classes = ['memberName']
..children = _createSnapshotSelectA()
],
- // TODO(rmacnak): Diffing.
- // new DivElement()
- // ..classes = ['memberItem']
- // ..children = <Element>[
- // new DivElement()
- // ..classes = ['memberName']
- // ..text = 'Snapshot B',
- // new DivElement()
- // ..classes = ['memberName']
- // ..children = _createSnapshotSelectB()
- // ],
+ new DivElement()
+ ..classes = ['memberItem']
+ ..children = <Element>[
+ new DivElement()
+ ..classes = ['memberName']
+ ..text = 'Snapshot B',
+ new DivElement()
+ ..classes = ['memberName']
+ ..children = _createSnapshotSelectB()
+ ],
+ new DivElement()
+ ..classes = ['memberItem']
+ ..children = <Element>[
+ new DivElement()
+ ..classes = ['memberName']
+ ..text = (_snapshotA == _snapshotB) ? 'View ' : 'Compare ',
+ new DivElement()
+ ..classes = ['memberName']
+ ..children = _createModeSelect()
+ ]
]
],
];
- if (selection == null) {
- selection = _snapshotA!["root"];
+
+ switch (_mode) {
+ case ProcessSnapshotTreeMode.treeMap:
+ if (selection == null) {
+ selection = _snapshotA!["root"];
+ }
+ _createTreeMap(report, new ProcessItemTreeMap(this), selection);
+ break;
+ case ProcessSnapshotTreeMode.tree:
+ if (selection == null) {
+ selection = _snapshotA!["root"];
+ }
+ _tree = new VirtualTreeElement(
+ _createItem, _updateItem, _getChildrenItem,
+ items: [selection], queue: _r.queue);
+ _tree!.expand(selection!);
+ report.add(_tree!.element);
+ break;
+ case ProcessSnapshotTreeMode.treeMapDiff:
+ if (diffSelection == null) {
+ diffSelection =
+ ProcessItemDiff.from(_snapshotA!["root"], _snapshotB!["root"]);
+ }
+ _createTreeMap(report, new ProcessItemDiffTreeMap(this), diffSelection);
+ break;
+ case ProcessSnapshotTreeMode.treeDiff:
+ var root =
+ ProcessItemDiff.from(_snapshotA!["root"], _snapshotB!["root"]);
+ _tree = new VirtualTreeElement(
+ _createItemDiff, _updateItemDiff, _getChildrenItemDiff,
+ items: [root], queue: _r.queue);
+ _tree!.expand(root);
+ report.add(_tree!.element);
+ break;
+ default:
+ throw new Exception('Unknown ProcessSnapshotTreeMode: $_mode');
}
- _createTreeMap(report, new ProcessItemTreeMap(this), selection);
+
return report;
}
+ VirtualTreeElement? _tree;
+
+ static HtmlElement _createItem(toggle) {
+ return new DivElement()
+ ..classes = ['tree-item']
+ ..children = <Element>[
+ new SpanElement()
+ ..classes = ['percentage']
+ ..title = 'percentage of total',
+ new SpanElement()
+ ..classes = ['size']
+ ..title = 'retained size',
+ new SpanElement()..classes = ['lines'],
+ new ButtonElement()
+ ..classes = ['expander']
+ ..onClick.listen((_) => toggle(autoToggleSingleChildNodes: true)),
+ new SpanElement()..classes = ['name'],
+ ];
+ }
+
+ static HtmlElement _createItemDiff(toggle) {
+ return new DivElement()
+ ..classes = ['tree-item']
+ ..children = <Element>[
+ new SpanElement()
+ ..classes = ['percentage']
+ ..title = 'percentage of total',
+ new SpanElement()
+ ..classes = ['size']
+ ..title = 'retained size A',
+ new SpanElement()
+ ..classes = ['percentage']
+ ..title = 'percentage of total',
+ new SpanElement()
+ ..classes = ['size']
+ ..title = 'retained size B',
+ new SpanElement()
+ ..classes = ['size']
+ ..title = 'retained size change',
+ new SpanElement()..classes = ['lines'],
+ new ButtonElement()
+ ..classes = ['expander']
+ ..onClick.listen((_) => toggle(autoToggleSingleChildNodes: true)),
+ new SpanElement()..classes = ['name']
+ ];
+ }
+
+ void _updateItem(HtmlElement element, node, int depth) {
+ var size = node["size"];
+ var rootSize = _snapshotA!["root"]["size"];
+ element.children[0].text =
+ Utils.formatPercentNormalized(size * 1.0 / rootSize);
+ element.children[1].text = Utils.formatSize(size);
+ _updateLines(element.children[2].children, depth);
+ if (_getChildrenItem(node).isNotEmpty) {
+ element.children[3].text = _tree!.isExpanded(node) ? 'â–¼' : 'â–º';
+ } else {
+ element.children[3].text = '';
+ }
+ element.children[4].text = node["name"];
+ element.children[4].title = node["description"];
+ }
+
+ void _updateItemDiff(HtmlElement element, nodeDynamic, int depth) {
+ ProcessItemDiff node = nodeDynamic;
+ element.children[0].text = Utils.formatPercentNormalized(
+ node.retainedSizeA * 1.0 / _snapshotA!["root"]["size"]);
+ element.children[1].text = Utils.formatSize(node.retainedSizeA);
+ element.children[2].text = Utils.formatPercentNormalized(
+ node.retainedSizeB * 1.0 / _snapshotB!["root"]["size"]);
+ element.children[3].text = Utils.formatSize(node.retainedSizeB);
+ element.children[4].text = (node.retainedSizeDiff > 0 ? '+' : '') +
+ Utils.formatSize(node.retainedSizeDiff);
+ element.children[4].style.color =
+ node.retainedSizeDiff > 0 ? "red" : "green";
+ _updateLines(element.children[5].children, depth);
+ if (_getChildrenItemDiff(node).isNotEmpty) {
+ element.children[6].text = _tree!.isExpanded(node) ? 'â–¼' : 'â–º';
+ } else {
+ element.children[6].text = '';
+ }
+ element.children[7]..text = node.name;
+ }
+
+ static Iterable _getChildrenItem(node) {
+ return new List<Map>.from(node["children"]);
+ }
+
+ static Iterable _getChildrenItemDiff(nodeDynamic) {
+ ProcessItemDiff node = nodeDynamic;
+ final list = node.children!.toList();
+ list.sort((a, b) => b.retainedSizeDiff - a.retainedSizeDiff);
+ return list;
+ }
+
+ static _updateLines(List<Element> lines, int n) {
+ n = Math.max(0, n);
+ while (lines.length > n) {
+ lines.removeLast();
+ }
+ while (lines.length < n) {
+ lines.add(new SpanElement());
+ }
+ }
+
+ static String modeToString(ProcessSnapshotTreeMode mode) {
+ switch (mode) {
+ case ProcessSnapshotTreeMode.treeMap:
+ case ProcessSnapshotTreeMode.treeMapDiff:
+ return 'Tree Map';
+ case ProcessSnapshotTreeMode.tree:
+ case ProcessSnapshotTreeMode.treeDiff:
+ return 'Tree';
+ }
+ throw new Exception('Unknown ProcessSnapshotTreeMode: $mode');
+ }
+
+ List<Element> _createModeSelect() {
+ var s;
+ var modes = _snapshotA == _snapshotB ? _viewModes : _diffModes;
+ if (!modes.contains(_mode)) {
+ _mode = modes[0];
+ _r.dirty();
+ }
+ return [
+ s = new SelectElement()
+ ..classes = ['analysis-select']
+ ..value = modeToString(_mode)
+ ..children = modes.map((mode) {
+ return new OptionElement(
+ value: modeToString(mode), selected: _mode == mode)
+ ..text = modeToString(mode);
+ }).toList(growable: false)
+ ..onChange.listen((_) {
+ _mode = modes[s.selectedIndex];
+ _r.dirty();
+ })
+ ];
+ }
+
String snapshotToString(snapshot) {
if (snapshot == null) return "None";
return snapshot["root"]["name"] +
diff --git a/runtime/observatory/lib/src/elements/timeline/dashboard.dart b/runtime/observatory/lib/src/elements/timeline/dashboard.dart
deleted file mode 100644
index 5aca24a..0000000
--- a/runtime/observatory/lib/src/elements/timeline/dashboard.dart
+++ /dev/null
@@ -1,230 +0,0 @@
-// Copyright (c) 2017, 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.
-
-/// This page is not directly reachable from the main Observatory ui.
-/// It is mainly mented to be used from editors as an integrated tool.
-///
-/// This page mainly targeting developers and not VM experts, so concepts like
-/// timeline streams are hidden away.
-///
-/// The page exposes two views over the timeline data.
-/// Both of them are filtered based on the optional argument `mode`.
-/// See [_TimelineView] for the explanation of the two possible values.
-
-import 'dart:async';
-import 'dart:html';
-import 'dart:convert';
-import 'package:observatory/models.dart' as M;
-import 'package:observatory/src/elements/helpers/nav_bar.dart';
-import 'package:observatory/src/elements/helpers/rendering_scheduler.dart';
-import 'package:observatory/src/elements/helpers/custom_element.dart';
-import 'package:observatory/src/elements/nav/notify.dart';
-
-/// The two possible views are available.
-/// * `string`
-/// The events are just filtered by `mode` and maintain their original
-/// timestamp.
-/// * `frame`
-/// The events are organized by frame.
-/// The events are shifted in order to give a high level view of the
-/// computation involved in a frame.
-/// The frame are concatenated one after the other taking care of not
-/// overlapping the related events.
-enum _TimelineView { strict, frame }
-
-class TimelineDashboardElement extends CustomElement implements Renderable {
- late RenderingScheduler<TimelineDashboardElement> _r;
-
- Stream<RenderedEvent<TimelineDashboardElement>> get onRendered =>
- _r.onRendered;
-
- late M.VM _vm;
- late M.TimelineRepository _repository;
- late M.NotificationRepository _notifications;
- late M.TimelineFlags _flags;
- _TimelineView _view = _TimelineView.strict;
-
- M.VM get vm => _vm;
- M.NotificationRepository get notifications => _notifications;
-
- factory TimelineDashboardElement(M.VM vm, M.TimelineRepository repository,
- M.NotificationRepository notifications,
- {RenderingQueue? queue}) {
- assert(vm != null);
- assert(repository != null);
- assert(notifications != null);
- TimelineDashboardElement e = new TimelineDashboardElement.created();
- e._r = new RenderingScheduler<TimelineDashboardElement>(e, queue: queue);
- e._vm = vm;
- e._repository = repository;
- e._notifications = notifications;
- if (vm.embedder == 'Flutter') {
- e._view = _TimelineView.frame;
- }
- return e;
- }
-
- TimelineDashboardElement.created() : super.created('timeline-dashboard');
-
- @override
- attached() {
- super.attached();
- _r.enable();
- _refresh();
- }
-
- @override
- detached() {
- super.detached();
- _r.disable(notify: true);
- children = <Element>[];
- }
-
- IFrameElement? _frame;
- DivElement? _content;
-
- void render() {
- if (_frame == null) {
- _frame = new IFrameElement();
- }
- if (_content == null) {
- _content = new DivElement()..classes = ['content-centered-big'];
- }
- // ignore: unsafe_html
- _frame!.src = _makeFrameUrl();
- _content!.children = <Element>[
- new HeadingElement.h2()
- ..nodes = ([new Text("Timeline View")]
- ..addAll(_createButtons())
- ..addAll(_createTabs())),
- new ParagraphElement()
- ..text = (_view == _TimelineView.frame
- ? 'Logical view of the computation involved in each frame '
- '(timestamps may not be preserved)'
- : 'Sequence of events generated during the execution '
- '(timestamps are preserved)')
- ];
- if (children.isEmpty) {
- children = <Element>[
- navBar(<Element>[
- new NavNotifyElement(_notifications, queue: _r.queue).element
- ]),
- _content!,
- new DivElement()
- ..classes = ['iframe']
- ..children = <Element>[_frame!]
- ];
- }
- }
-
- List<Node> _createButtons() {
- if (_flags == null) {
- return [new Text('Loading')];
- }
- if (_suggestedProfile(_flags.profiles).streams.any((s) => !s.isRecorded)) {
- return [
- new ButtonElement()
- ..classes = ['header_button']
- ..text = 'Enable'
- ..title = 'The Timeline is not fully enabled, click to enable'
- ..onClick.listen((e) => _enable()),
- ];
- }
- return [
- new ButtonElement()
- ..classes = ['header_button']
- ..text = 'Load from VM'
- ..title = 'Load the timeline'
- ..onClick.listen((e) => _refresh()),
- new ButtonElement()
- ..classes = ['header_button']
- ..text = 'Reset Timeline'
- ..title = 'Reset the current timeline'
- ..onClick.listen((e) => _clear()),
- new ButtonElement()
- ..classes = ['header_button', 'left-pad']
- ..text = 'Save to File…'
- ..title = 'Save the current Timeline to file'
- ..onClick.listen((e) => _save()),
- new ButtonElement()
- ..classes = ['header_button']
- ..text = 'Load from File…'
- ..title = 'Load a saved timeline from file'
- ..onClick.listen((e) => _load()),
- ];
- }
-
- List<Element> _createTabs() {
- if (_vm.embedder != 'Flutter') {
- return const [];
- }
- return [
- new SpanElement()
- ..classes = ['tab_buttons']
- ..children = <Element>[
- new ButtonElement()
- ..text = 'Frame View'
- ..title = 'Logical view of the computation involved in each frame\n'
- 'Timestamps may not be preserved'
- ..disabled = _view == _TimelineView.frame
- ..onClick.listen((_) {
- _view = _TimelineView.frame;
- _r.dirty();
- }),
- new ButtonElement()
- ..text = 'Time View'
- ..title = 'Sequence of events generated during the execution\n'
- 'Timestamps are preserved'
- ..disabled = _view == _TimelineView.strict
- ..onClick.listen((_) {
- _view = _TimelineView.strict;
- _r.dirty();
- }),
- ]
- ];
- }
-
- String _makeFrameUrl() {
- final String mode = 'basic';
- final String view = _view == _TimelineView.frame ? 'frame' : 'strict';
- return 'timeline.html#mode=$mode&view=$view';
- }
-
- M.TimelineProfile _suggestedProfile(Iterable<M.TimelineProfile> profiles) {
- return profiles
- .where((profile) => profile.name == 'Flutter Developer')
- .single;
- }
-
- Future _enable() async {
- await _repository.setRecordedStreams(
- vm, _suggestedProfile(_flags.profiles).streams);
- _refresh();
- }
-
- Future _refresh() async {
- _flags = await _repository.getFlags(vm);
- _r.dirty();
- final traceData =
- Map<String, dynamic>.from(await _repository.getTimeline(vm));
- return _postMessage('refresh', traceData);
- }
-
- Future _clear() async {
- await _repository.clear(_vm);
- return _postMessage('clear');
- }
-
- Future _save() => _postMessage('save');
-
- Future _load() => _postMessage('load');
-
- Future _postMessage(String method,
- [Map<String, dynamic> params = const <String, dynamic>{}]) async {
- var message = {'method': method, 'params': params};
- _frame!.contentWindow!
- .postMessage(json.encode(message), window.location.href);
- return null;
- }
-}
diff --git a/runtime/observatory/lib/src/elements/tree_map.dart b/runtime/observatory/lib/src/elements/tree_map.dart
index 3a3721d..79e18d4 100644
--- a/runtime/observatory/lib/src/elements/tree_map.dart
+++ b/runtime/observatory/lib/src/elements/tree_map.dart
@@ -192,3 +192,42 @@
return "hsl($hue,60%,60%)";
}
}
+
+abstract class DiffTreeMap<T> extends TreeMap<T> {
+ int getSizeA(T node);
+ int getSizeB(T node);
+
+ // We need to sum gains and losses separately because they both contribute
+ // area to the tree map tiles, i.e., losses don't have negative area in the
+ // visualization. For this reason, common is not necessarily
+ // max(sizeA,sizeB)-min(sizeA,sizeB), gain is not necessarily
+ // abs(sizeB-sizeA), etc.
+ int getGain(T node);
+ int getLoss(T node);
+ int getCommon(T node);
+
+ String getName(T node);
+ String getType(T node);
+
+ int getArea(T node) => getCommon(node) + getGain(node) + getLoss(node);
+ String getLabel(T node) {
+ var name = getName(node);
+ var sizeA = Utils.formatSize(getSizeA(node));
+ var sizeB = Utils.formatSize(getSizeB(node));
+ return "$name [$sizeA → $sizeB]";
+ }
+
+ String getBackground(T node) {
+ int l = getLoss(node);
+ int c = getCommon(node);
+ int g = getGain(node);
+ int a = l + c + g;
+ if (a == 0) {
+ return "white";
+ }
+ // Stripes of green, white and red whose areas are poritional to loss, common and gain.
+ String stop1 = (l / a * 100).toString();
+ String stop2 = ((l + c) / a * 100).toString();
+ return "linear-gradient(to right, #66FF99 $stop1%, white $stop1% $stop2%, #FF6680 $stop2%)";
+ }
+}
diff --git a/runtime/observatory/observatory_sources.gni b/runtime/observatory/observatory_sources.gni
index 9ad1b96..88db18e 100644
--- a/runtime/observatory/observatory_sources.gni
+++ b/runtime/observatory/observatory_sources.gni
@@ -131,7 +131,6 @@
"lib/src/elements/strongly_reachable_instances.dart",
"lib/src/elements/subtypetestcache_ref.dart",
"lib/src/elements/subtypetestcache_view.dart",
- "lib/src/elements/timeline/dashboard.dart",
"lib/src/elements/timeline_page.dart",
"lib/src/elements/tree_map.dart",
"lib/src/elements/type_arguments_ref.dart",
diff --git a/runtime/vm/dart_api_impl.cc b/runtime/vm/dart_api_impl.cc
index cfc1759..6ad719e 100644
--- a/runtime/vm/dart_api_impl.cc
+++ b/runtime/vm/dart_api_impl.cc
@@ -2077,7 +2077,7 @@
Isolate* I = T->isolate();
CHECK_API_SCOPE(T);
CHECK_CALLBACK_STATE(T);
- API_TIMELINE_BEGIN_END_BASIC(T);
+ API_TIMELINE_BEGIN_END(T);
TransitionNativeToVM transition(T);
if (I->message_handler()->HandleNextMessage() != MessageHandler::kOK) {
return Api::NewHandle(T, T->StealStickyError());
@@ -2090,7 +2090,7 @@
Isolate* I = T->isolate();
CHECK_API_SCOPE(T);
CHECK_CALLBACK_STATE(T);
- API_TIMELINE_BEGIN_END_BASIC(T);
+ API_TIMELINE_BEGIN_END(T);
TransitionNativeToVM transition(T);
if (I->message_notify_callback() != NULL) {
return Api::NewError("waitForEventSync is not supported by this embedder");
diff --git a/runtime/vm/dart_api_impl.h b/runtime/vm/dart_api_impl.h
index 683be98..47f9b7a 100644
--- a/runtime/vm/dart_api_impl.h
+++ b/runtime/vm/dart_api_impl.h
@@ -118,27 +118,17 @@
#ifdef SUPPORT_TIMELINE
#define API_TIMELINE_DURATION(thread) \
TimelineBeginEndScope api_tbes(thread, Timeline::GetAPIStream(), CURRENT_FUNC)
-#define API_TIMELINE_DURATION_BASIC(thread) \
- API_TIMELINE_DURATION(thread); \
- api_tbes.SetNumArguments(1); \
- api_tbes.CopyArgument(0, "mode", "basic");
#define API_TIMELINE_BEGIN_END(thread) \
TimelineBeginEndScope api_tbes(thread, Timeline::GetAPIStream(), CURRENT_FUNC)
-#define API_TIMELINE_BEGIN_END_BASIC(thread) \
- API_TIMELINE_BEGIN_END(thread); \
- api_tbes.SetNumArguments(1); \
- api_tbes.CopyArgument(0, "mode", "basic");
#else
#define API_TIMELINE_DURATION(thread) \
do { \
} while (false)
-#define API_TIMELINE_DURATION_BASIC(thread) API_TIMELINE_DURATION(thread)
#define API_TIMELINE_BEGIN_END(thread) \
do { \
} while (false)
-#define API_TIMELINE_BEGIN_END_BASIC(thread) API_TIMELINE_BEGIN_END(thread)
#endif // !PRODUCT
class Api : AllStatic {
diff --git a/runtime/vm/heap/heap.cc b/runtime/vm/heap/heap.cc
index 903803a..980f2e4 100644
--- a/runtime/vm/heap/heap.cc
+++ b/runtime/vm/heap/heap.cc
@@ -463,7 +463,7 @@
VMTagScope tagScope(thread, reason == GCReason::kIdle
? VMTag::kGCIdleTagId
: VMTag::kGCNewSpaceTagId);
- TIMELINE_FUNCTION_GC_DURATION_BASIC(thread, "CollectNewGeneration");
+ TIMELINE_FUNCTION_GC_DURATION(thread, "CollectNewGeneration");
new_space_.Scavenge(reason);
RecordAfterGC(GCType::kScavenge);
PrintStats();
@@ -512,7 +512,7 @@
VMTagScope tagScope(thread, reason == GCReason::kIdle
? VMTag::kGCIdleTagId
: VMTag::kGCOldSpaceTagId);
- TIMELINE_FUNCTION_GC_DURATION_BASIC(thread, "CollectOldGeneration");
+ TIMELINE_FUNCTION_GC_DURATION(thread, "CollectOldGeneration");
old_space_.CollectGarbage(type == GCType::kMarkCompact, true /* finish */);
RecordAfterGC(type);
PrintStats();
@@ -610,7 +610,7 @@
}
void Heap::StartConcurrentMarking(Thread* thread) {
- TIMELINE_FUNCTION_GC_DURATION_BASIC(thread, "StartConcurrentMarking");
+ TIMELINE_FUNCTION_GC_DURATION(thread, "StartConcurrentMarking");
old_space_.CollectGarbage(/*compact=*/false, /*finalize=*/false);
}
diff --git a/runtime/vm/isolate.cc b/runtime/vm/isolate.cc
index 9d49ce6..c1e6cbe 100644
--- a/runtime/vm/isolate.cc
+++ b/runtime/vm/isolate.cc
@@ -1383,17 +1383,6 @@
}
}
} else {
-#ifndef PRODUCT
- if (!Isolate::IsSystemIsolate(I)) {
- // Mark all the user isolates as using a simplified timeline page of
- // Observatory. The internal isolates will be filtered out from
- // the Timeline due to absence of this argument. We still send them in
- // order to maintain the original behavior of the full timeline and allow
- // the developer to download complete dump files.
- tbes.SetNumArguments(2);
- tbes.CopyArgument(1, "mode", "basic");
- }
-#endif
const Object& msg_handler = Object::Handle(
zone, DartLibraryCalls::HandleMessage(message->dest_port(), msg));
if (msg_handler.IsError()) {
diff --git a/runtime/vm/os_thread_android.cc b/runtime/vm/os_thread_android.cc
index df257e3..8c09320 100644
--- a/runtime/vm/os_thread_android.cc
+++ b/runtime/vm/os_thread_android.cc
@@ -138,6 +138,7 @@
delete data;
// Set the thread name. There is 16 bytes limit on the name (including \0).
+ // pthread_setname_np ignores names that are too long rather than truncating.
char truncated_name[16];
snprintf(truncated_name, ARRAY_SIZE(truncated_name), "%s", name);
pthread_setname_np(pthread_self(), truncated_name);
diff --git a/runtime/vm/os_thread_linux.cc b/runtime/vm/os_thread_linux.cc
index 167721a..409b03b 100644
--- a/runtime/vm/os_thread_linux.cc
+++ b/runtime/vm/os_thread_linux.cc
@@ -139,6 +139,7 @@
delete data;
// Set the thread name. There is 16 bytes limit on the name (including \0).
+ // pthread_setname_np ignores names that are too long rather than truncating.
char truncated_name[16];
snprintf(truncated_name, ARRAY_SIZE(truncated_name), "%s", name);
pthread_setname_np(pthread_self(), truncated_name);
diff --git a/runtime/vm/timeline.h b/runtime/vm/timeline.h
index b6d1908..f4389ce 100644
--- a/runtime/vm/timeline.h
+++ b/runtime/vm/timeline.h
@@ -499,15 +499,10 @@
#define TIMELINE_FUNCTION_GC_DURATION(thread, name) \
TimelineBeginEndScope tbes(thread, Timeline::GetGCStream(), name);
-#define TIMELINE_FUNCTION_GC_DURATION_BASIC(thread, name) \
- TIMELINE_FUNCTION_GC_DURATION(thread, name) \
- tbes.SetNumArguments(1); \
- tbes.CopyArgument(0, "mode", "basic");
#else
#define TIMELINE_DURATION(thread, stream, name)
#define TIMELINE_FUNCTION_COMPILATION_DURATION(thread, name, function)
#define TIMELINE_FUNCTION_GC_DURATION(thread, name)
-#define TIMELINE_FUNCTION_GC_DURATION_BASIC(thread, name)
#endif // !PRODUCT
// See |TimelineBeginEndScope|.
diff --git a/sdk/lib/js/_js_annotations.dart b/sdk/lib/js/_js_annotations.dart
index c188214..b110632 100644
--- a/sdk/lib/js/_js_annotations.dart
+++ b/sdk/lib/js/_js_annotations.dart
@@ -18,4 +18,10 @@
const _Anonymous();
}
-const _Anonymous anonymous = const _Anonymous();
+// class _StaticInterop {
+// const _StaticInterop();
+// }
+
+const _Anonymous anonymous = _Anonymous();
+
+// const _StaticInterop staticInterop = _StaticInterop();
diff --git a/tests/lib/js/native_as_js_classes_static_test/default_library_namespace_test.dart b/tests/lib/js/native_as_js_classes_static_test/default_library_namespace_test.dart
index d230129..fa262ae 100644
--- a/tests/lib/js/native_as_js_classes_static_test/default_library_namespace_test.dart
+++ b/tests/lib/js/native_as_js_classes_static_test/default_library_namespace_test.dart
@@ -13,29 +13,29 @@
@JS()
class HTMLDocument {}
// ^
-// [web] JS interop class 'HTMLDocument' conflicts with natively supported class 'HtmlDocument' in 'dart:html'.
+// [web] Non-static JS interop class 'HTMLDocument' conflicts with natively supported class 'HtmlDocument' in 'dart:html'.
// Test same annotation name as a native class.
@JS('HTMLDocument')
class HtmlDocument {}
// ^
-// [web] JS interop class 'HtmlDocument' conflicts with natively supported class 'HtmlDocument' in 'dart:html'.
+// [web] Non-static JS interop class 'HtmlDocument' conflicts with natively supported class 'HtmlDocument' in 'dart:html'.
// Test annotation name with 'self' and 'window' prefixes.
@JS('self.Window')
class WindowWithSelf {}
// ^
-// [web] JS interop class 'WindowWithSelf' conflicts with natively supported class 'Window' in 'dart:html'.
+// [web] Non-static JS interop class 'WindowWithSelf' conflicts with natively supported class 'Window' in 'dart:html'.
@JS('window.Window')
class WindowWithWindow {}
// ^
-// [web] JS interop class 'WindowWithWindow' conflicts with natively supported class 'Window' in 'dart:html'.
+// [web] Non-static JS interop class 'WindowWithWindow' conflicts with natively supported class 'Window' in 'dart:html'.
@JS('self.window.self.window.self.Window')
class WindowWithMultipleSelfsAndWindows {}
// ^
-// [web] JS interop class 'WindowWithMultipleSelfsAndWindows' conflicts with natively supported class 'Window' in 'dart:html'.
+// [web] Non-static JS interop class 'WindowWithMultipleSelfsAndWindows' conflicts with natively supported class 'Window' in 'dart:html'.
// Test annotation with native class name but with a prefix that isn't 'self' or
// 'window'.
@@ -47,14 +47,14 @@
@JS()
class DOMWindow {}
// ^
-// [web] JS interop class 'DOMWindow' conflicts with natively supported class 'Window' in 'dart:html'.
+// [web] Non-static JS interop class 'DOMWindow' conflicts with natively supported class 'Window' in 'dart:html'.
// Test same annotation name as a native class with multiple annotation names
// dart:html.Window uses both "Window" and "DOMWindow".
@JS('DOMWindow')
class DomWindow {}
// ^
-// [web] JS interop class 'DomWindow' conflicts with natively supported class 'Window' in 'dart:html'.
+// [web] Non-static JS interop class 'DomWindow' conflicts with natively supported class 'Window' in 'dart:html'.
// Test different annotation name but with same class name as a @Native class.
@JS('Foo')
diff --git a/tests/lib/js/native_as_js_classes_static_test/global_library_namespace_test.dart b/tests/lib/js/native_as_js_classes_static_test/global_library_namespace_test.dart
index fb55aa6..7911057 100644
--- a/tests/lib/js/native_as_js_classes_static_test/global_library_namespace_test.dart
+++ b/tests/lib/js/native_as_js_classes_static_test/global_library_namespace_test.dart
@@ -12,27 +12,27 @@
@JS()
class HTMLDocument {}
// ^
-// [web] JS interop class 'HTMLDocument' conflicts with natively supported class 'HtmlDocument' in 'dart:html'.
+// [web] Non-static JS interop class 'HTMLDocument' conflicts with natively supported class 'HtmlDocument' in 'dart:html'.
@JS('HTMLDocument')
class HtmlDocument {}
// ^
-// [web] JS interop class 'HtmlDocument' conflicts with natively supported class 'HtmlDocument' in 'dart:html'.
+// [web] Non-static JS interop class 'HtmlDocument' conflicts with natively supported class 'HtmlDocument' in 'dart:html'.
@JS('self.Window')
class WindowWithSelf {}
// ^
-// [web] JS interop class 'WindowWithSelf' conflicts with natively supported class 'Window' in 'dart:html'.
+// [web] Non-static JS interop class 'WindowWithSelf' conflicts with natively supported class 'Window' in 'dart:html'.
@JS('window.Window')
class WindowWithWindow {}
// ^
-// [web] JS interop class 'WindowWithWindow' conflicts with natively supported class 'Window' in 'dart:html'.
+// [web] Non-static JS interop class 'WindowWithWindow' conflicts with natively supported class 'Window' in 'dart:html'.
@JS('self.window.self.window.self.Window')
class WindowWithMultipleSelfsAndWindows {}
// ^
-// [web] JS interop class 'WindowWithMultipleSelfsAndWindows' conflicts with natively supported class 'Window' in 'dart:html'.
+// [web] Non-static JS interop class 'WindowWithMultipleSelfsAndWindows' conflicts with natively supported class 'Window' in 'dart:html'.
@JS('foo.Window')
class WindowWithDifferentPrefix {}
@@ -40,12 +40,12 @@
@JS()
class DOMWindow {}
// ^
-// [web] JS interop class 'DOMWindow' conflicts with natively supported class 'Window' in 'dart:html'.
+// [web] Non-static JS interop class 'DOMWindow' conflicts with natively supported class 'Window' in 'dart:html'.
@JS('DOMWindow')
class DomWindow {}
// ^
-// [web] JS interop class 'DomWindow' conflicts with natively supported class 'Window' in 'dart:html'.
+// [web] Non-static JS interop class 'DomWindow' conflicts with natively supported class 'Window' in 'dart:html'.
@JS('Foo')
class Window {}
diff --git a/tests/lib/js/static_interop_test/member_test.dart b/tests/lib/js/static_interop_test/member_test.dart
new file mode 100644
index 0000000..f5bf11e
--- /dev/null
+++ b/tests/lib/js/static_interop_test/member_test.dart
@@ -0,0 +1,54 @@
+// Copyright (c) 2021, 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.
+
+// Test that instance members are disallowed from static interop classes.
+
+@JS()
+library member_test;
+
+import 'package:js/js.dart';
+
+@JS()
+@staticInterop
+class StaticJSClass {
+ external StaticJSClass();
+ external StaticJSClass.namedConstructor();
+ external factory StaticJSClass.externalFactory();
+ factory StaticJSClass.redirectingFactory() = StaticJSClass;
+ factory StaticJSClass.factory() => StaticJSClass();
+
+ static String staticField = 'staticField';
+ static String get staticGetSet => staticField;
+ static set staticGetSet(String val) => staticField = val;
+ static String staticMethod() => 'staticMethod';
+
+ external static String externalStaticField;
+ external static String get externalStaticGetSet;
+ external static set externalStaticGetSet(String val);
+ external static String externalStaticMethod();
+
+ external int get getter;
+ // ^
+ // [web] JS interop class 'StaticJSClass' with `@staticInterop` annotation cannot declare instance members.
+ external set setter(_);
+ // ^
+ // [web] JS interop class 'StaticJSClass' with `@staticInterop` annotation cannot declare instance members.
+ external int method();
+ // ^
+ // [web] JS interop class 'StaticJSClass' with `@staticInterop` annotation cannot declare instance members.
+ external int field;
+ // ^
+ // [web] JS interop class 'StaticJSClass' with `@staticInterop` annotation cannot declare instance members.
+}
+
+extension StaticJSClassExtension on StaticJSClass {
+ external String externalField;
+ external String get externalGetSet;
+ external set externalGetSet(String val);
+ external String externalMethod();
+
+ String get getSet => this.externalGetSet;
+ set getSet(String val) => this.externalGetSet = val;
+ String method() => 'method';
+}
diff --git a/tests/lib/js/static_interop_test/supertype_test.dart b/tests/lib/js/static_interop_test/supertype_test.dart
new file mode 100644
index 0000000..88e0085
--- /dev/null
+++ b/tests/lib/js/static_interop_test/supertype_test.dart
@@ -0,0 +1,114 @@
+// Copyright (c) 2021, 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.
+
+// Test that interop classes that inherit/implement other classes have the
+// appropriate static interop static errors.
+
+@JS()
+library supertype_test;
+
+import 'package:js/js.dart';
+
+// Static base interop class.
+@JS()
+@staticInterop
+class Static {}
+
+// Non-static base interop class.
+@JS()
+class NonStatic {
+ external int instanceMethod();
+}
+
+@JS()
+@staticInterop
+class NonStaticMarkedAsStatic {
+ external int instanceMethod();
+ // ^
+ // [web] JS interop class 'NonStaticMarkedAsStatic' with `@staticInterop` annotation cannot declare instance members.
+}
+
+// Static interop classes can inherit other static interop classes in order to
+// inherit its extension methods.
+@JS()
+@staticInterop
+class StaticExtendsStatic extends Static {}
+
+// Static interop classes are disallowed from extending non-static interop
+// classes.
+@JS()
+@staticInterop
+class StaticExtendsNonStatic extends NonStatic {}
+// ^
+// [web] JS interop class 'StaticExtendsNonStatic' has an `@staticInterop` annotation, but has supertype 'NonStatic', which is non-static.
+
+// Static interop classes can implement each other in order to inherit extension
+// methods. Note that a non-abstract static interop class can not implement a
+// non-static class by definition, as it would need to contain an
+// implementation.
+@JS()
+@staticInterop
+class StaticImplementsStatic implements Static {}
+
+// Abstract classes should behave the same way as concrete classes.
+@JS()
+@staticInterop
+abstract class StaticAbstract {}
+
+// Abstract classes with instance members should be non-static. The following
+// have abstract or concrete members, so they're considered non-static.
+@JS()
+abstract class NonStaticAbstract {
+ int abstractMethod();
+}
+
+@JS()
+@staticInterop
+abstract class NonStaticAbstractWithAbstractMembers {
+ int abstractMethod();
+ // ^
+ // [web] JS interop class 'NonStaticAbstractWithAbstractMembers' with `@staticInterop` annotation cannot declare instance members.
+}
+
+@JS()
+@staticInterop
+abstract class NonStaticAbstractWithConcreteMembers {
+ external int instanceMethod();
+ // ^
+ // [web] JS interop class 'NonStaticAbstractWithConcreteMembers' with `@staticInterop` annotation cannot declare instance members.
+}
+
+@JS()
+@staticInterop
+abstract class StaticAbstractImplementsStaticAbstract
+ implements StaticAbstract {}
+
+@JS()
+@staticInterop
+abstract class StaticAbstractExtendsStaticAbstract extends StaticAbstract {}
+
+@JS()
+@staticInterop
+abstract class StaticAbstractImplementsNonStaticAbstract
+// ^
+// [web] JS interop class 'StaticAbstractImplementsNonStaticAbstract' has an `@staticInterop` annotation, but has supertype 'NonStaticAbstract', which is non-static.
+ implements
+ NonStaticAbstract {}
+
+@JS()
+@staticInterop
+abstract class StaticAbstractImplementsMultipleNonStatic
+// ^
+// [web] JS interop class 'StaticAbstractImplementsMultipleNonStatic' has an `@staticInterop` annotation, but has supertype 'NonStatic', which is non-static.
+// [web] JS interop class 'StaticAbstractImplementsMultipleNonStatic' has an `@staticInterop` annotation, but has supertype 'NonStaticAbstract', which is non-static.
+ implements
+ NonStaticAbstract,
+ NonStatic {}
+
+@JS()
+@staticInterop
+abstract class StaticAbstractExtendsNonStaticAbstract
+// ^
+// [web] JS interop class 'StaticAbstractExtendsNonStaticAbstract' has an `@staticInterop` annotation, but has supertype 'NonStaticAbstract', which is non-static.
+ extends NonStaticAbstract {}
diff --git a/tests/lib_2/js/static_interop_test/member_test.dart b/tests/lib_2/js/static_interop_test/member_test.dart
new file mode 100644
index 0000000..f5bf11e
--- /dev/null
+++ b/tests/lib_2/js/static_interop_test/member_test.dart
@@ -0,0 +1,54 @@
+// Copyright (c) 2021, 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.
+
+// Test that instance members are disallowed from static interop classes.
+
+@JS()
+library member_test;
+
+import 'package:js/js.dart';
+
+@JS()
+@staticInterop
+class StaticJSClass {
+ external StaticJSClass();
+ external StaticJSClass.namedConstructor();
+ external factory StaticJSClass.externalFactory();
+ factory StaticJSClass.redirectingFactory() = StaticJSClass;
+ factory StaticJSClass.factory() => StaticJSClass();
+
+ static String staticField = 'staticField';
+ static String get staticGetSet => staticField;
+ static set staticGetSet(String val) => staticField = val;
+ static String staticMethod() => 'staticMethod';
+
+ external static String externalStaticField;
+ external static String get externalStaticGetSet;
+ external static set externalStaticGetSet(String val);
+ external static String externalStaticMethod();
+
+ external int get getter;
+ // ^
+ // [web] JS interop class 'StaticJSClass' with `@staticInterop` annotation cannot declare instance members.
+ external set setter(_);
+ // ^
+ // [web] JS interop class 'StaticJSClass' with `@staticInterop` annotation cannot declare instance members.
+ external int method();
+ // ^
+ // [web] JS interop class 'StaticJSClass' with `@staticInterop` annotation cannot declare instance members.
+ external int field;
+ // ^
+ // [web] JS interop class 'StaticJSClass' with `@staticInterop` annotation cannot declare instance members.
+}
+
+extension StaticJSClassExtension on StaticJSClass {
+ external String externalField;
+ external String get externalGetSet;
+ external set externalGetSet(String val);
+ external String externalMethod();
+
+ String get getSet => this.externalGetSet;
+ set getSet(String val) => this.externalGetSet = val;
+ String method() => 'method';
+}
diff --git a/tests/lib_2/js/static_interop_test/supertype_test.dart b/tests/lib_2/js/static_interop_test/supertype_test.dart
new file mode 100644
index 0000000..88e0085
--- /dev/null
+++ b/tests/lib_2/js/static_interop_test/supertype_test.dart
@@ -0,0 +1,114 @@
+// Copyright (c) 2021, 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.
+
+// Test that interop classes that inherit/implement other classes have the
+// appropriate static interop static errors.
+
+@JS()
+library supertype_test;
+
+import 'package:js/js.dart';
+
+// Static base interop class.
+@JS()
+@staticInterop
+class Static {}
+
+// Non-static base interop class.
+@JS()
+class NonStatic {
+ external int instanceMethod();
+}
+
+@JS()
+@staticInterop
+class NonStaticMarkedAsStatic {
+ external int instanceMethod();
+ // ^
+ // [web] JS interop class 'NonStaticMarkedAsStatic' with `@staticInterop` annotation cannot declare instance members.
+}
+
+// Static interop classes can inherit other static interop classes in order to
+// inherit its extension methods.
+@JS()
+@staticInterop
+class StaticExtendsStatic extends Static {}
+
+// Static interop classes are disallowed from extending non-static interop
+// classes.
+@JS()
+@staticInterop
+class StaticExtendsNonStatic extends NonStatic {}
+// ^
+// [web] JS interop class 'StaticExtendsNonStatic' has an `@staticInterop` annotation, but has supertype 'NonStatic', which is non-static.
+
+// Static interop classes can implement each other in order to inherit extension
+// methods. Note that a non-abstract static interop class can not implement a
+// non-static class by definition, as it would need to contain an
+// implementation.
+@JS()
+@staticInterop
+class StaticImplementsStatic implements Static {}
+
+// Abstract classes should behave the same way as concrete classes.
+@JS()
+@staticInterop
+abstract class StaticAbstract {}
+
+// Abstract classes with instance members should be non-static. The following
+// have abstract or concrete members, so they're considered non-static.
+@JS()
+abstract class NonStaticAbstract {
+ int abstractMethod();
+}
+
+@JS()
+@staticInterop
+abstract class NonStaticAbstractWithAbstractMembers {
+ int abstractMethod();
+ // ^
+ // [web] JS interop class 'NonStaticAbstractWithAbstractMembers' with `@staticInterop` annotation cannot declare instance members.
+}
+
+@JS()
+@staticInterop
+abstract class NonStaticAbstractWithConcreteMembers {
+ external int instanceMethod();
+ // ^
+ // [web] JS interop class 'NonStaticAbstractWithConcreteMembers' with `@staticInterop` annotation cannot declare instance members.
+}
+
+@JS()
+@staticInterop
+abstract class StaticAbstractImplementsStaticAbstract
+ implements StaticAbstract {}
+
+@JS()
+@staticInterop
+abstract class StaticAbstractExtendsStaticAbstract extends StaticAbstract {}
+
+@JS()
+@staticInterop
+abstract class StaticAbstractImplementsNonStaticAbstract
+// ^
+// [web] JS interop class 'StaticAbstractImplementsNonStaticAbstract' has an `@staticInterop` annotation, but has supertype 'NonStaticAbstract', which is non-static.
+ implements
+ NonStaticAbstract {}
+
+@JS()
+@staticInterop
+abstract class StaticAbstractImplementsMultipleNonStatic
+// ^
+// [web] JS interop class 'StaticAbstractImplementsMultipleNonStatic' has an `@staticInterop` annotation, but has supertype 'NonStatic', which is non-static.
+// [web] JS interop class 'StaticAbstractImplementsMultipleNonStatic' has an `@staticInterop` annotation, but has supertype 'NonStaticAbstract', which is non-static.
+ implements
+ NonStaticAbstract,
+ NonStatic {}
+
+@JS()
+@staticInterop
+abstract class StaticAbstractExtendsNonStaticAbstract
+// ^
+// [web] JS interop class 'StaticAbstractExtendsNonStaticAbstract' has an `@staticInterop` annotation, but has supertype 'NonStaticAbstract', which is non-static.
+ extends NonStaticAbstract {}
diff --git a/tools/VERSION b/tools/VERSION
index 1568b43..818d3bd 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
MAJOR 2
MINOR 15
PATCH 0
-PRERELEASE 189
+PRERELEASE 190
PRERELEASE_PATCH 0
\ No newline at end of file