Version 3.11.0-34.0.dev Merge bf3e8e923ae30426c20e7cc0c20ba6cfea9d3bfe into dev
diff --git a/pkg/analysis_server/lib/src/computer/computer_highlights.dart b/pkg/analysis_server/lib/src/computer/computer_highlights.dart index 5e2f93d..bd7bdcb 100644 --- a/pkg/analysis_server/lib/src/computer/computer_highlights.dart +++ b/pkg/analysis_server/lib/src/computer/computer_highlights.dart
@@ -19,7 +19,6 @@ show SemanticTokenInfo; import 'package:analysis_server/src/lsp/semantic_tokens/mapping.dart' show highlightRegionTokenModifiers, highlightRegionTokenTypes; -import 'package:analyzer/dart/ast/ast.dart'; import 'package:analyzer/dart/ast/token.dart'; import 'package:analyzer/dart/ast/visitor.dart'; import 'package:analyzer/dart/element/element.dart'; @@ -30,6 +29,7 @@ import 'package:analyzer/src/dart/element/extensions.dart'; import 'package:analyzer/src/utilities/extensions/ast.dart'; import 'package:analyzer_plugin/protocol/protocol_common.dart' hide Element; +import 'package:collection/collection.dart'; /// A computer for [HighlightRegion]s and LSP [SemanticTokenInfo] in a Dart [CompilationUnit]. class DartUnitHighlightsComputer { @@ -674,16 +674,42 @@ /// Returns whether [nameToken] is a reference to the `call` method on /// a function. bool _isCallMethod(AstNode parent, Token nameToken) { + late bool enclosingInstanceFunction = + switch (parent.enclosingInstanceElement) { + ExtensionElement(:var extendedType) => extendedType.isFunction, + _ => false, + }; + Expression? expression() => switch (parent) { + ExpressionFunctionBody(:var expression) || + ExpressionStatement(:var expression) => expression, + ReturnStatement(:var expression) => expression, + ArgumentList(:var arguments) => arguments.firstWhereOrNull((argument) { + return argument is SimpleIdentifier && argument.token == nameToken; + }), + AssignmentExpression(:var rightHandSide) => rightHandSide, + VariableDeclaration(:var initializer) => initializer, + _ => null, + }; return // Invocation - parent is MethodInvocation && + (parent is MethodInvocation && parent.methodName.token == nameToken && parent.methodName.name == MethodElement.CALL_METHOD_NAME && - parent.realTarget?.staticType is FunctionType || + ((parent.realTarget?.staticType).isFunction || + enclosingInstanceFunction)) || // Tearoff (parent is PrefixedIdentifier && parent.identifier.token == nameToken && parent.identifier.name == MethodElement.CALL_METHOD_NAME && - parent.prefix.staticType is FunctionType); + parent.prefix.staticType.isFunction) || + // Property access + (parent is PropertyAccess && + parent.propertyName.token == nameToken && + parent.propertyName.name == MethodElement.CALL_METHOD_NAME && + parent.realTarget.staticType.isFunction) || + // Special cases for extension methods + (expression() is SimpleIdentifier && + nameToken.lexeme == MethodElement.CALL_METHOD_NAME && + enclosingInstanceFunction); } void _reset() { @@ -2084,3 +2110,8 @@ (false, false, false) => Quote.Double, }; } + +extension on DartType? { + bool get isFunction => + this is FunctionType || (this?.isDartCoreFunction ?? false); +}
diff --git a/pkg/analysis_server/lib/src/services/correction/dart/create_class.dart b/pkg/analysis_server/lib/src/services/correction/dart/create_class.dart index 71ea8bc..d0e59a0 100644 --- a/pkg/analysis_server/lib/src/services/correction/dart/create_class.dart +++ b/pkg/analysis_server/lib/src/services/correction/dart/create_class.dart
@@ -7,6 +7,7 @@ import 'package:analysis_server_plugin/edit/dart/correction_producer.dart'; import 'package:analyzer/dart/ast/ast.dart'; import 'package:analyzer/dart/element/element.dart'; +import 'package:analyzer/dart/element/type.dart'; import 'package:analyzer/file_system/file_system.dart'; import 'package:analyzer_plugin/utilities/change_builder/change_builder_core.dart'; import 'package:analyzer_plugin/utilities/fixes/fixes.dart'; @@ -178,6 +179,23 @@ @override Future<void> compute(ChangeBuilder builder) async { + Expression? expression; + if (_targetNode is Expression) { + expression = _targetNode; + } else if (_targetNode.parent case Expression parent) { + expression = parent; + } + if (expression != null) { + var fieldType = inferUndefinedExpressionType(expression); + if (fieldType is InvalidType) { + return; + } + if (fieldType != null && + (!typeSystem.isAssignableTo(fieldType, typeProvider.typeType) || + !typeSystem.isSubtypeOf(fieldType, typeProvider.objectType))) { + return; + } + } // prepare environment LibraryFragment targetUnit; var offset = -1;
diff --git a/pkg/analysis_server/lib/src/services/correction/dart/create_method_or_function.dart b/pkg/analysis_server/lib/src/services/correction/dart/create_method_or_function.dart index d9b2cfe..957e69c 100644 --- a/pkg/analysis_server/lib/src/services/correction/dart/create_method_or_function.dart +++ b/pkg/analysis_server/lib/src/services/correction/dart/create_method_or_function.dart
@@ -146,7 +146,6 @@ }) async { // build method source await builder.addDartFileEdit(targetFile, (builder) { - var eol = builder.eol; builder.addInsertion(insertOffset, (builder) { if (leadingEol) { builder.writeln(); @@ -175,7 +174,7 @@ builder.write(' async'); } // close method - builder.write(' {$eol$prefix}'); + builder.write(' {}'); if (trailingEol) { builder.writeln(); }
diff --git a/pkg/analysis_server/test/lsp/semantic_tokens_test.dart b/pkg/analysis_server/test/lsp/semantic_tokens_test.dart index 6aee1a5..943b097 100644 --- a/pkg/analysis_server/test/lsp/semantic_tokens_test.dart +++ b/pkg/analysis_server/test/lsp/semantic_tokens_test.dart
@@ -1213,6 +1213,148 @@ await _initializeAndVerifyTokensInRange(content, expected); } + Future<void> test_function_callMethod_invocation_extension() async { + var content = r''' +extension on void Function() { + m() => [!call()!]; +} +'''; + + var expected = <_Token>[ + _Token('call', SemanticTokenTypes.method, [ + CustomSemanticTokenModifiers.instance, + ]), + ]; + + await _initializeAndVerifyTokensInRange(content, expected); + } + + Future<void> test_function_callMethod_propertyAccess() async { + var content = r''' +extension on void Function()? { + m() => [!this?.call!]; +} +'''; + + var expected = <_Token>[ + _Token('this', SemanticTokenTypes.keyword), + _Token('call', SemanticTokenTypes.method, [ + CustomSemanticTokenModifiers.instance, + ]), + ]; + + await _initializeAndVerifyTokensInRange(content, expected); + } + + Future<void> test_function_callMethod_simpleIdentifier() async { + var content = r''' +extension on void Function() { + m() { + [!call!]; + } +} +'''; + + var expected = <_Token>[ + _Token('call', SemanticTokenTypes.method, [ + CustomSemanticTokenModifiers.instance, + ]), + ]; + + await _initializeAndVerifyTokensInRange(content, expected); + } + + Future<void> test_function_callMethod_simpleIdentifier_argument() async { + var content = r''' +extension on void Function() { + m(void Function() f) { + m([!call!]); + } +} +'''; + + var expected = <_Token>[ + _Token('call', SemanticTokenTypes.method, [ + CustomSemanticTokenModifiers.instance, + ]), + ]; + + await _initializeAndVerifyTokensInRange(content, expected); + } + + Future<void> test_function_callMethod_simpleIdentifier_assignment() async { + var content = r''' +extension on void Function() { + m() { + var a; + a = [!call!]; + } +} +'''; + + var expected = <_Token>[ + _Token('call', SemanticTokenTypes.method, [ + CustomSemanticTokenModifiers.instance, + ]), + ]; + + await _initializeAndVerifyTokensInRange(content, expected); + } + + Future<void> + test_function_callMethod_simpleIdentifier_expressionFunctionBody() async { + var content = r''' +extension on void Function() { + m() => [!call!]; +} +'''; + + var expected = <_Token>[ + _Token('call', SemanticTokenTypes.method, [ + CustomSemanticTokenModifiers.instance, + ]), + ]; + + await _initializeAndVerifyTokensInRange(content, expected); + } + + Future<void> test_function_callMethod_simpleIdentifier_return() async { + var content = r''' +extension on void Function() { + m() { + return [!call!]; + } +} +'''; + + var expected = <_Token>[ + _Token('call', SemanticTokenTypes.method, [ + CustomSemanticTokenModifiers.instance, + ]), + ]; + + await _initializeAndVerifyTokensInRange(content, expected); + } + + Future<void> + test_function_callMethod_simpleIdentifier_variableDeclaration() async { + var content = r''' +extension on void Function() { + m() { + var _ = [!call!]; + } +} +'''; + + var expected = <_Token>[ + _Token('call', SemanticTokenTypes.method, [ + CustomSemanticTokenModifiers.instance, + ]), + ]; + + await _initializeAndVerifyTokensInRange(content, expected); + } + Future<void> test_function_callMethod_tearOff() async { var content = r''' f(void Function(int) x) { @@ -1230,6 +1372,39 @@ await _initializeAndVerifyTokensInRange(content, expected); } + Future<void> test_functionType_callMethod_invocation_extension() async { + var content = r''' +extension on Function { + m() => [!call()!]; +} +'''; + + var expected = <_Token>[ + _Token('call', SemanticTokenTypes.method, [ + CustomSemanticTokenModifiers.instance, + ]), + ]; + + await _initializeAndVerifyTokensInRange(content, expected); + } + + Future<void> test_functionType_callMethod_tearOff() async { + var content = r''' +f(Function x) { + [!x.call!]; +} +'''; + + var expected = [ + _Token('x', SemanticTokenTypes.parameter), + _Token('call', SemanticTokenTypes.method, [ + CustomSemanticTokenModifiers.instance, + ]), + ]; + + await _initializeAndVerifyTokensInRange(content, expected); + } + /// Verify that sending a semantic token request immediately after an overlay /// update (with no delay) does not result in corrupt semantic tokens because /// the previous file content was used.
diff --git a/pkg/analysis_server/test/src/services/correction/fix/create_class_test.dart b/pkg/analysis_server/test/src/services/correction/fix/create_class_test.dart index 31a87a6..3b09117 100644 --- a/pkg/analysis_server/test/src/services/correction/fix/create_class_test.dart +++ b/pkg/analysis_server/test/src/services/correction/fix/create_class_test.dart
@@ -25,6 +25,18 @@ @override FixKind get kind => DartFixKind.createClassLowercase; + Future<void> test_ifNull_notType() async { + await resolveTestCode(''' +class A { + int Function()? _f; + int Function() get f { + return _f ?? _defaultF; + } +} +'''); + await assertNoFix(); + } + Future<void> test_instanceMethod_noFix() async { await resolveTestCode(''' class C {}
diff --git a/pkg/analysis_server/test/src/services/correction/fix/create_extension_member_test.dart b/pkg/analysis_server/test/src/services/correction/fix/create_extension_member_test.dart index ee7c875..5753bf2 100644 --- a/pkg/analysis_server/test/src/services/correction/fix/create_extension_member_test.dart +++ b/pkg/analysis_server/test/src/services/correction/fix/create_extension_member_test.dart
@@ -815,6 +815,27 @@ '''); } + Future<void> test_ifNull() async { + await resolveTestCode(''' +extension E on Object { + int Function()? get _f => null; + int Function() get f { + return _f ?? _defaultF; + } +} +'''); + await assertHasFix(''' +extension E on Object { + int Function()? get _f => null; + int Function() get f { + return _f ?? _defaultF; + } + + int _defaultF() {} +} +'''); + } + Future<void> test_main_part() async { var partPath = join(testPackageLibPath, 'part.dart'); newFile(partPath, '''
diff --git a/pkg/analysis_server/test/src/services/correction/fix/create_field_test.dart b/pkg/analysis_server/test/src/services/correction/fix/create_field_test.dart index e01ef6b..e25b29f 100644 --- a/pkg/analysis_server/test/src/services/correction/fix/create_field_test.dart +++ b/pkg/analysis_server/test/src/services/correction/fix/create_field_test.dart
@@ -555,6 +555,27 @@ '''); } + Future<void> test_ifNull() async { + await resolveTestCode(''' +class A { + int Function()? _f; + int Function() get f { + return _f ?? _defaultF; + } +} +'''); + await assertHasFix(''' +class A { + int Function()? _f; + + int Function() _defaultF; + int Function() get f { + return _f ?? _defaultF; + } +} +'''); + } + Future<void> test_importType() async { newFile('$testPackageLibPath/a.dart', r''' class A {}
diff --git a/pkg/analysis_server/test/src/services/correction/fix/create_function_test.dart b/pkg/analysis_server/test/src/services/correction/fix/create_function_test.dart index 27ac2f0..c614ffd 100644 --- a/pkg/analysis_server/test/src/services/correction/fix/create_function_test.dart +++ b/pkg/analysis_server/test/src/services/correction/fix/create_function_test.dart
@@ -28,8 +28,7 @@ await assertHasFix(''' bool Function() f = g; -bool g() { -} +bool g() {} '''); } @@ -60,8 +59,7 @@ a..ma().useFunction(test); } -int test(double a, String b) { -} +int test(double a, String b) {} '''); } @@ -78,8 +76,7 @@ } useFunction({Function? g}) {} -test() { -} +test() {} '''); } @@ -96,8 +93,7 @@ } useFunction(int g(a, b)) {} -int test(a, b) { -} +int test(a, b) {} '''); } @@ -114,8 +110,7 @@ } useFunction(int g(double a, String b)) {} -int test(double a, String b) { -} +int test(double a, String b) {} '''); } @@ -132,8 +127,7 @@ } useFunction({int g(double a, String b)?}) {} -int test(double a, String b) { -} +int test(double a, String b) {} '''); } @@ -152,8 +146,7 @@ void f2(int Function() f) {} -int f3() { -} +int f3() {} '''); } @@ -181,8 +174,7 @@ useFunction(test); } -int test(A a) { -} +int test(A a) {} '''); } @@ -201,8 +193,7 @@ void f2(int Function(int) f) {} -int f3(int p1) { -} +int f3(int p1) {} '''); } @@ -221,8 +212,7 @@ void f2(int Function(int) f) {} -int f3(int p1) { -} +int f3(int p1) {} '''); } @@ -241,8 +231,7 @@ void f2((int, int Function(int)) f) {} -int f3(int p1) { -} +int f3(int p1) {} '''); } @@ -261,9 +250,25 @@ void f2(({int Function(int) f}) f) {} -int f3(int p1) { +int f3(int p1) {} +'''); + } + + Future<void> test_ifNull() async { + await resolveTestCode(''' +int Function()? _f; +int Function() get f { + return _f ?? _defaultF; } '''); + await assertHasFix(''' +int Function()? _f; +int Function() get f { + return _f ?? _defaultF; +} + +int _defaultF() {} +'''); } Future<void> test_record_tearoff() async { @@ -273,8 +278,7 @@ await assertHasFix(''' (bool Function(),) f = (g,); -bool g() { -} +bool g() {} '''); } @@ -297,8 +301,7 @@ f(test); } -A<int> test() { -} +A<int> test() {} '''); } }
diff --git a/pkg/analysis_server/test/src/services/correction/fix/create_getter_test.dart b/pkg/analysis_server/test/src/services/correction/fix/create_getter_test.dart index fc4fc1c..49c4ac9 100644 --- a/pkg/analysis_server/test/src/services/correction/fix/create_getter_test.dart +++ b/pkg/analysis_server/test/src/services/correction/fix/create_getter_test.dart
@@ -302,6 +302,94 @@ '''); } + Future<void> test_ifNull_LHS() async { + await resolveTestCode(''' +class A { + late int Function() _f; + + void f() { + _defaultF ?? _f; + } +} +'''); + await assertHasFix(''' +class A { + late int Function() _f; + + int Function()? get _defaultF => null; + + void f() { + _defaultF ?? _f; + } +} +'''); + } + + Future<void> test_ifNull_LHS_return() async { + await resolveTestCode(''' +class A { + int Function()? _f; + int Function() get f { + return _defaultF ?? _f; + } +} +'''); + await assertHasFix(''' +class A { + int Function()? _f; + int Function() get f { + return _defaultF ?? _f; + } + + int Function()? get _defaultF => null; +} +'''); + } + + Future<void> test_ifNull_RHS() async { + await resolveTestCode(''' +class A { + int Function()? _f; + + void f() { + _f ?? _defaultF; + } +} +'''); + await assertHasFix(''' +class A { + int Function()? _f; + + int Function()? get _defaultF => null; + + void f() { + _f ?? _defaultF; + } +} +'''); + } + + Future<void> test_ifNull_RHS_return() async { + await resolveTestCode(''' +class A { + int Function()? _f; + int Function() get f { + return _f ?? _defaultF; + } +} +'''); + await assertHasFix(''' +class A { + int Function()? _f; + int Function() get f { + return _f ?? _defaultF; + } + + int Function() get _defaultF => null; +} +'''); + } + Future<void> test_inExtensionGetter_class() async { await resolveTestCode(''' class A {
diff --git a/pkg/analysis_server/test/src/services/correction/fix/create_method_test.dart b/pkg/analysis_server/test/src/services/correction/fix/create_method_test.dart index e690591..ed2c455 100644 --- a/pkg/analysis_server/test/src/services/correction/fix/create_method_test.dart +++ b/pkg/analysis_server/test/src/services/correction/fix/create_method_test.dart
@@ -252,8 +252,7 @@ useFunction(test); } - static int test(double a, String b) { - } + static int test(double a, String b) {} } useFunction(int g(double a, String b)) {} @@ -277,8 +276,7 @@ } mixin M { - int test(double a, String b) { - } + int test(double a, String b) {} } useFunction(int g(double a, String b)) {} @@ -304,8 +302,7 @@ part of 'test.dart'; mixin M { - void myUndefinedMethod() { - } + void myUndefinedMethod() {} } ''', target: partPath); } @@ -329,8 +326,7 @@ part 'test.dart'; mixin M { - void myUndefinedMethod() { - } + void myUndefinedMethod() {} } ''', target: mainPath); } @@ -358,8 +354,7 @@ part of 'main.dart'; mixin M { - void myUndefinedMethod() { - } + void myUndefinedMethod() {} } ''', target: part1Path); } @@ -387,8 +382,7 @@ enum E { e1, e2; - int bar() { - } + int bar() {} } void g(int Function() f) {} @@ -416,8 +410,7 @@ enum E { e1, e2; - static int bar() { - } + static int bar() {} } void g(int Function() f) {} @@ -441,8 +434,7 @@ '''); await assertHasFix(''' extension type E(int i) { - int bar() { - } + int bar() {} } void g(int Function() f) {} @@ -466,8 +458,7 @@ '''); await assertHasFix(''' extension type E(int i) { - static int bar() { - } + static int bar() {} } void g(int Function() f) {} @@ -496,8 +487,7 @@ void m2(int Function(int) f) {} - int m3(int p1) { - } + int m3(int p1) {} } '''); } @@ -517,8 +507,7 @@ useFunction(test); } - static int test(double a, String b) { - } + static int test(double a, String b) {} } useFunction(int g(double a, String b)) {} '''); @@ -537,8 +526,7 @@ var f; A() : f = useFunction(test); - static int test(double a, String b) { - } + static int test(double a, String b) {} } useFunction(int g(double a, String b)) {} '''); @@ -562,8 +550,7 @@ void m2(int Function() f) {} - int m3() { - } + int m3() {} } '''); } @@ -587,8 +574,7 @@ void m2(int Function(int) f) {} - int m3(int p1) { - } + int m3(int p1) {} } '''); } @@ -612,8 +598,7 @@ void m2(int Function(int) f) {} - int m3(int p1) { - } + int m3(int p1) {} } '''); } @@ -636,8 +621,7 @@ void m2((int Function(int),) f) {} - int m3(int p1) { - } + int m3(int p1) {} } '''); } @@ -661,8 +645,7 @@ void m2(({int Function(int) f}) f) {} - int m3(int p1) { - } + int m3(int p1) {} } '''); } @@ -681,8 +664,7 @@ useFunction(a.test); } class A { - int test(double a, String b) { - } + int test(double a, String b) {} } useFunction(int g(double a, String b)) {} '''); @@ -705,13 +687,33 @@ class A { m() {} - int test(double a, String b) { - } + int test(double a, String b) {} } useFunction(int g(double a, String b)) {} '''); } + Future<void> test_ifNull() async { + await resolveTestCode(''' +class A { + int Function()? _f; + int Function() get f { + return _f ?? _defaultF; + } +} +'''); + await assertHasFix(''' +class A { + int Function()? _f; + int Function() get f { + return _f ?? _defaultF; + } + + int _defaultF() {} +} +'''); + } + Future<void> test_record_named_tearoff() async { await resolveTestCode(''' class A { @@ -722,8 +724,7 @@ class A { ({bool Function() fn,}) m() => (fn: m2,); - bool m2() { - } + bool m2() {} } '''); } @@ -738,8 +739,7 @@ class A { (bool Function(),) m() => (m2,); - bool m2() { - } + bool m2() {} } '''); } @@ -763,8 +763,7 @@ Future<T> g<T>(Future<T> Function() foo) => foo(); class C { - static Future<int> foo() async { - } + static Future<int> foo() async {} } '''); } @@ -792,8 +791,7 @@ Future<T> g<T>(Future<S> Function<S>() foo) => foo(); class C { - static Future<S> foo<S>() async { - } + static Future<S> foo<S>() async {} } '''); } @@ -821,8 +819,7 @@ Future<T> g<T>(Future<S> Function<S extends num>() foo) => foo(); class C { - static Future<S> foo<S extends num>() async { - } + static Future<S> foo<S extends num>() async {} } '''); } @@ -850,8 +847,7 @@ Future<T> g<T extends num>(Future<S> Function<S extends T>() foo) => foo(); class C { - static Future<S> foo<S extends num>() async { - } + static Future<S> foo<S extends num>() async {} } '''); } @@ -879,8 +875,7 @@ Future<T> g<T>(Future<S> Function<S>() foo) => foo(); class C<S> { - static Future<S> foo<S>() async { - } + static Future<S> foo<S>() async {} } '''); }
diff --git a/pkg/analysis_server/test/src/services/correction/fix/create_mixin_test.dart b/pkg/analysis_server/test/src/services/correction/fix/create_mixin_test.dart index 59b04a2..89d9f12 100644 --- a/pkg/analysis_server/test/src/services/correction/fix/create_mixin_test.dart +++ b/pkg/analysis_server/test/src/services/correction/fix/create_mixin_test.dart
@@ -25,6 +25,18 @@ @override FixKind get kind => DartFixKind.createMixinLowercase; + Future<void> test_ifNull_notType() async { + await resolveTestCode(''' +class A { + int Function()? _f; + int Function() get f { + return _f ?? _defaultF; + } +} +'''); + await assertNoFix(); + } + Future<void> test_lowercaseAssignment() async { await resolveTestCode(''' newName? a;
diff --git a/pkg/analysis_server/test/src/services/correction/fix/ignore_diagnostic_test.dart b/pkg/analysis_server/test/src/services/correction/fix/ignore_diagnostic_test.dart index 4417b81..e70f8bb 100644 --- a/pkg/analysis_server/test/src/services/correction/fix/ignore_diagnostic_test.dart +++ b/pkg/analysis_server/test/src/services/correction/fix/ignore_diagnostic_test.dart
@@ -4,6 +4,7 @@ import 'package:analysis_server_plugin/src/correction/ignore_diagnostic.dart'; import 'package:analyzer_plugin/utilities/fixes/fixes.dart'; +import 'package:linter/src/lint_names.dart'; import 'package:test_reflective_loader/test_reflective_loader.dart'; import 'fix_processor.dart'; @@ -219,7 +220,7 @@ await assertHasFix(''' // Copyright header. -// ignore_for_file: referenced_before_declaration, unused_local_variable +// ignore_for_file: unused_local_variable, referenced_before_declaration // Some other header. @@ -339,7 +340,7 @@ '''); await assertHasFix(''' void f() { - // ignore: undefined_identifier, unused_local_variable + // ignore: unused_local_variable, undefined_identifier var a = b; } '''); @@ -359,6 +360,22 @@ await assertNoFix(); } + Future<void> test_unknown_error_code() async { + createAnalysisOptionsFile(lints: [LintNames.always_specify_types]); + await resolveTestCode(''' +void f() { + // ignore: unused_local_variable, some text + var a = 1; +} +'''); + await assertHasFix(''' +void f() { + // ignore: always_specify_types, unused_local_variable, some text + var a = 1; +} +'''); + } + Future<void> test_unusedCode() async { await resolveTestCode(''' void f() {
diff --git a/pkg/analysis_server_plugin/lib/edit/dart/correction_producer.dart b/pkg/analysis_server_plugin/lib/edit/dart/correction_producer.dart index 2f480d8..4f01338 100644 --- a/pkg/analysis_server_plugin/lib/edit/dart/correction_producer.dart +++ b/pkg/analysis_server_plugin/lib/edit/dart/correction_producer.dart
@@ -644,15 +644,28 @@ } } } - // `v + myFunction();`. if (parent is BinaryExpression) { var binary = parent; var method = binary.element; + // `v + myFunction();`. if (method != null) { if (binary.rightOperand == expression) { var parameters = method.formalParameters; return parameters.length == 1 ? parameters[0].type : null; } + } else if (binary.operator.type == TokenType.QUESTION_QUESTION) { + // `v ?? myFunction();`. + // This handles when the expression is being assigned somewhere. + var type = inferUndefinedExpressionType(binary); + if (binary.rightOperand == expression) { + return type ?? binary.leftOperand.staticType; + } else if (binary.leftOperand == expression) { + type ??= binary.rightOperand.staticType; + return switch (type) { + TypeImpl type => type.withNullability(NullabilitySuffix.question), + _ => null, + }; + } } } // `foo( myFunction() );`.
diff --git a/pkg/analysis_server_plugin/lib/src/correction/ignore_diagnostic.dart b/pkg/analysis_server_plugin/lib/src/correction/ignore_diagnostic.dart index e98ae01..ccaf603 100644 --- a/pkg/analysis_server_plugin/lib/src/correction/ignore_diagnostic.dart +++ b/pkg/analysis_server_plugin/lib/src/correction/ignore_diagnostic.dart
@@ -134,7 +134,6 @@ if (_isCodeUnignorable) return; await builder.addDartFileEdit(file, (builder) { - var eol = builder.eol; var source = unitResult.content; // Look for the last blank line in any leading comments (to insert after @@ -153,21 +152,24 @@ for (var lineNumber = 0; lineNumber < lineCount - 1; lineNumber++) { lineStart = unitResult.lineInfo.getOffsetOfLine(lineNumber); var nextLineStart = unitResult.lineInfo.getOffsetOfLine(lineNumber + 1); - var line = source.substring(lineStart, nextLineStart).trim(); + var line = source.substring(lineStart, nextLineStart); + var trimmedLine = line.trim(); - if (line.startsWith('// $commentPrefix:')) { - // Found an existing ignore; insert at the end of this line. - builder.addSimpleInsertion(nextLineStart - eol.length, ', $_code'); + if (trimmedLine.startsWith(IgnoreInfo.ignoreForFileMatcher)) { + // Found an existing ignore; insert after `// ignore_for_file: ` + // before any existing codes. + var insertOffset = lineStart + line.indexOf(':') + 1; + builder.addSimpleInsertion(insertOffset, ' $_code,'); return; } - if (line.isEmpty) { + if (trimmedLine.isEmpty) { // Track last blank line, as we will insert there. lastBlankLineOffset = lineStart; continue; } - if (line.startsWith('#!') || line.startsWith('//')) { + if (trimmedLine.startsWith('#!') || trimmedLine.startsWith('//')) { // Skip comment/hash-bang. continue; } @@ -201,7 +203,6 @@ if (_isCodeUnignorable) return; await builder.addDartFileEdit(file, (builder) { - var eol = builder.eol; var offset = diagnostic.problemMessage.offset; var lineNumber = unitResult.lineInfo.getLocation(offset).lineNumber - 1; @@ -216,12 +217,12 @@ lineNumber - 1, ); var lineStart = unitResult.lineInfo.getOffsetOfLine(lineNumber); - var line = unitResult.content - .substring(previousLineStart, lineStart) - .trim(); + var line = unitResult.content.substring(previousLineStart, lineStart); - if (line.startsWith(IgnoreInfo.ignoreMatcher)) { - builder.addSimpleInsertion(lineStart - eol.length, ', $_code'); + if (line.trim().startsWith(IgnoreInfo.ignoreMatcher)) { + // Add after the `// ignore: ` before any existing codes. + var insertOffset = previousLineStart + line.indexOf(':') + 1; + builder.addSimpleInsertion(insertOffset, ' $_code,'); } else { insertAt(builder, lineStart); }
diff --git a/pkg/dart2wasm/benchmark/self_compile_benchmark.dart b/pkg/dart2wasm/benchmark/self_compile_benchmark.dart index 6639785..7b37b69 100644 --- a/pkg/dart2wasm/benchmark/self_compile_benchmark.dart +++ b/pkg/dart2wasm/benchmark/self_compile_benchmark.dart
@@ -11,7 +11,7 @@ Future main(List<String> args) async { final sw = Stopwatch()..start(); final fileSystem = WasmCompilerFileSystem(); - final result = await compile( + final result = await compileBenchmark( fileSystem, 'pkg/dart2wasm/benchmark/self_compile_benchmark.dart'); print('Dart2WasmSelfCompile(RunTimeRaw): ${sw.elapsed.inMilliseconds} ms.'); @@ -22,7 +22,7 @@ } } -Future<CompilationSuccess> compile( +Future<CodegenResult> compileBenchmark( WasmCompilerFileSystem fileSystem, String mainFile) async { // Avoid CFE self-detecting whether `stdout`/`stderr` is terminal and supports // colors (as we don't have `dart:io` available when we run dart2wasm in a @@ -35,12 +35,12 @@ options.librariesSpecPath = Uri.file('${fileSystem.sdkRoot}/sdk/lib/libraries.json'); - final result = await compileToModule( + final result = await compile( options, fileSystem, (mod) => Uri.parse('$mod.maps'), (diag) { print('Diagnostics: ${diag.severity} ${diag.plainTextFormatted}'); }); if (result is! CompilationSuccess) { throw 'Compilation Failed: $result'; } - return result; + return result as CodegenResult; }
diff --git a/pkg/dart2wasm/lib/compile.dart b/pkg/dart2wasm/lib/compile.dart index f442f06..08f0a24 100644 --- a/pkg/dart2wasm/lib/compile.dart +++ b/pkg/dart2wasm/lib/compile.dart
@@ -16,9 +16,12 @@ kernelForProgram, CfeSeverity; import 'package:kernel/ast.dart'; +import 'package:kernel/binary/ast_from_binary.dart' + show BinaryBuilderWithMetadata; import 'package:kernel/class_hierarchy.dart'; import 'package:kernel/core_types.dart'; -import 'package:kernel/kernel.dart' show writeComponentToText; +import 'package:kernel/kernel.dart' + show writeComponentToText, loadComponentFromBytes; import 'package:kernel/library_index.dart'; import 'package:kernel/text/ast_to_text.dart'; import 'package:kernel/type_environment.dart'; @@ -41,6 +44,7 @@ import 'dry_run.dart'; import 'dynamic_module_kernel_metadata.dart'; import 'dynamic_modules.dart'; +import 'js/method_collector.dart' show JSMethods; import 'js/runtime_generator.dart' as js; import 'modules.dart'; import 'record_class_generator.dart'; @@ -48,6 +52,7 @@ import 'target.dart' as wasm show Mode; import 'target.dart' hide Mode; import 'translator.dart'; +import 'util.dart'; sealed class CompilationResult {} @@ -57,12 +62,40 @@ class CompilationDryRunSuccess extends CompilationDryRunResult {} -class CompilationSuccess extends CompilationResult { +sealed class CompilationSuccess extends CompilationResult {} + +class CfeResult extends CompilationSuccess { + final Component component; + final CoreTypes coreTypes; + + CfeResult(this.component, this.coreTypes); +} + +class TfaResult extends CompilationSuccess { + final Component component; + final CoreTypes coreTypes; + final LibraryIndex libraryIndex; + final ModuleStrategy moduleStrategy; + final MainModuleMetadata mainModuleMetadata; + final JSMethods jsInteropMethods; + final Map<RecordShape, Class> recordClasses; + + TfaResult( + this.component, + this.coreTypes, + this.libraryIndex, + this.moduleStrategy, + this.mainModuleMetadata, + this.jsInteropMethods, + this.recordClasses); +} + +class CodegenResult extends CompilationSuccess { final Map<String, ({Uint8List moduleBytes, String? sourceMap})> wasmModules; final String jsRuntime; final String supportJs; - CompilationSuccess(this.wasmModules, this.jsRuntime, this.supportJs); + CodegenResult(this.wasmModules, this.jsRuntime, this.supportJs); } abstract class CompilationError extends CompilationResult {} @@ -124,20 +157,12 @@ /// This value will be added to the Wasm module in `sourceMappingURL` section. /// When this argument is null the code generator does not generate source /// mappings. -Future<CompilationResult> compileToModule( +Future<CompilationResult> compile( compiler.WasmCompilerOptions options, FileSystem fileSystem, Uri Function(String moduleName)? sourceMapUrlGenerator, void Function(CfeDiagnosticMessage) handleDiagnosticMessage, {void Function(String, String)? writeFile}) async { - var hadCompileTimeError = false; - void diagnosticMessageHandler(CfeDiagnosticMessage message) { - if (message.severity == CfeSeverity.error) { - hadCompileTimeError = true; - } - handleDiagnosticMessage(message); - } - final wasm.Mode mode; if (options.translatorOptions.jsCompatibility) { mode = wasm.Mode.jsCompatibility; @@ -150,6 +175,68 @@ options.translatorOptions.enableExperimentalWasmInterop, removeAsserts: !options.translatorOptions.enableAsserts, mode: mode); + + if (options.multiRootScheme != null) { + fileSystem = MultiRootFileSystem( + options.multiRootScheme!, + options.multiRoots.isEmpty ? [Uri.base] : options.multiRoots, + fileSystem); + } + + CfeResult? cfeResult; + TfaResult? tfaResult; + CompilationResult? lastResult; + + for (final phase in options.phases) { + switch (phase) { + case compiler.CompilerPhase.cfe: + lastResult = await _runCfePhase( + options, target, fileSystem, handleDiagnosticMessage); + if (lastResult is! CfeResult) return lastResult; + cfeResult = lastResult; + + case compiler.CompilerPhase.tfa: + lastResult = await _runTfaPhase( + cfeResult ?? await _loadCfeResult(options), + options, + target, + fileSystem); + if (lastResult is! TfaResult) return lastResult; + tfaResult = lastResult; + + case compiler.CompilerPhase.codegen: + lastResult = await _runCodegenPhase( + tfaResult ?? await _loadTfaResult(options, target, fileSystem), + options, + fileSystem, + sourceMapUrlGenerator); + } + } + + return lastResult!; +} + +Future<CfeResult> _loadCfeResult(compiler.WasmCompilerOptions options) async { + final component = + loadComponentFromBytes(await File.fromUri(options.mainUri).readAsBytes()); + final coreTypes = CoreTypes(component); + return CfeResult(component, coreTypes); +} + +Future<CompilationResult> _runCfePhase( + compiler.WasmCompilerOptions options, + WasmTarget target, + FileSystem fileSystem, + void Function(CfeDiagnosticMessage) handleDiagnosticMessage, + {void Function(String, String)? writeFile}) async { + var hadCompileTimeError = false; + void diagnosticMessageHandler(CfeDiagnosticMessage message) { + if (message.severity == CfeSeverity.error) { + hadCompileTimeError = true; + } + handleDiagnosticMessage(message); + } + CompilerOptions compilerOptions = CompilerOptions() ..target = target // This is a dummy directory that always exists. This option should be @@ -169,21 +256,6 @@ ..verbose = false ..onDiagnostic = diagnosticMessageHandler ..fileSystem = fileSystem; - if (options.multiRootScheme != null) { - compilerOptions.fileSystem = MultiRootFileSystem( - options.multiRootScheme!, - options.multiRoots.isEmpty ? [Uri.base] : options.multiRoots, - compilerOptions.fileSystem); - } - - Future<Uri?> resolveUri(Uri? uri) async { - if (uri == null) return null; - var fileSystemEntity = compilerOptions.fileSystem.entityForUri(uri); - if (fileSystemEntity is MultiRootFileSystemEntity) { - fileSystemEntity = await fileSystemEntity.delegate; - } - return fileSystemEntity.uri; - } if (options.platformPath != null) { compilerOptions.sdkSummary = options.platformPath; @@ -191,10 +263,8 @@ compilerOptions.compileSdk = true; } - final dynamicMainModuleUri = await resolveUri(options.dynamicMainModuleUri); - final dynamicInterfaceUri = await resolveUri(options.dynamicInterfaceUri); - final isDynamicMainModule = - options.dynamicModuleType == DynamicModuleType.main; + final dynamicMainModuleUri = + await _resolveUri(fileSystem, options.dynamicMainModuleUri); final isDynamicSubmodule = options.dynamicModuleType == DynamicModuleType.submodule; if (isDynamicSubmodule) { @@ -227,19 +297,72 @@ if (hadCompileTimeError) { return CFECompileTimeErrors(compilerResult?.component); } - assert(compilerResult != null); - - Component component = compilerResult!.component!; - CoreTypes coreTypes = compilerResult.coreTypes!; - - ClosedWorldClassHierarchy classHierarchy = - ClassHierarchy(component, coreTypes) as ClosedWorldClassHierarchy; - LibraryIndex libraryIndex = LibraryIndex(component, _librariesToIndex); + final component = compilerResult!.component!; if (options.dumpKernelAfterCfe != null && writeFile != null) { writeFile(options.dumpKernelAfterCfe!, writeComponentToString(component)); } + return CfeResult(component, compilerResult.coreTypes!); +} + +Future<TfaResult> _loadTfaResult(compiler.WasmCompilerOptions options, + WasmTarget target, FileSystem fileSystem) async { + final component = createEmptyComponent(); + final recordClassesRepository = _RecordClassesRepository(); + final interopMethodsRepository = _InteropMethodsRepository(); + component.addMetadataRepository(recordClassesRepository); + component.addMetadataRepository(interopMethodsRepository); + + BinaryBuilderWithMetadata(await File.fromUri(options.mainUri).readAsBytes()) + .readComponent(component); + final coreTypes = CoreTypes(component); + final libraryIndex = LibraryIndex(component, _librariesToIndex); + final classHierarchy = ClassHierarchy(component, coreTypes); + final dynamicMainModuleUri = + await _resolveUri(fileSystem, options.dynamicMainModuleUri); + final dynamicInterfaceUri = + await _resolveUri(fileSystem, options.dynamicInterfaceUri); + + final moduleStrategy = _createModuleStrategy(options, component, coreTypes, + target, classHierarchy, dynamicMainModuleUri, dynamicInterfaceUri); + + final recordClasses = <RecordShape, Class>{}; + recordClassesRepository.mapping.forEach((cls, shape) { + recordClasses[shape] = cls; + }); + + final isDynamicMainModule = + options.dynamicModuleType == DynamicModuleType.main; + final isDynamicSubmodule = + options.dynamicModuleType == DynamicModuleType.submodule; + MainModuleMetadata mainModuleMetadata = + MainModuleMetadata.empty(options.translatorOptions, options.environment); + + if (isDynamicSubmodule) { + mainModuleMetadata = + await deserializeMainModuleMetadata(component, options); + mainModuleMetadata.verifyDynamicSubmoduleOptions(options); + } else if (isDynamicMainModule) { + MainModuleMetadata.verifyMainModuleOptions(options); + } + + return TfaResult(component, coreTypes, libraryIndex, moduleStrategy, + mainModuleMetadata, interopMethodsRepository.mapping, recordClasses); +} + +Future<CompilationResult> _runTfaPhase( + CfeResult cfeResult, + compiler.WasmCompilerOptions options, + WasmTarget target, + FileSystem fileSystem, + {void Function(String, String)? writeFile}) async { + var CfeResult(:component, :coreTypes) = cfeResult; + + ClosedWorldClassHierarchy classHierarchy = + ClassHierarchy(component, coreTypes) as ClosedWorldClassHierarchy; + LibraryIndex libraryIndex = LibraryIndex(component, _librariesToIndex); + if (options.deleteToStringPackageUri.isNotEmpty) { to_string_transformer.transformComponent( component, options.deleteToStringPackageUri); @@ -250,6 +373,15 @@ coreTypes, classHierarchy); + final dynamicMainModuleUri = + await _resolveUri(fileSystem, options.dynamicMainModuleUri); + final dynamicInterfaceUri = + await _resolveUri(fileSystem, options.dynamicInterfaceUri); + final isDynamicMainModule = + options.dynamicModuleType == DynamicModuleType.main; + final isDynamicSubmodule = + options.dynamicModuleType == DynamicModuleType.submodule; + if (isDynamicSubmodule) { // Join the submodule libraries with the TFAed component from the main // module compilation. JS interop transformer must be run before this since @@ -280,26 +412,8 @@ writeFile(options.dumpKernelBeforeTfa!, writeComponentToString(component)); } - ModuleStrategy moduleStrategy; - if (options.translatorOptions.enableDeferredLoading) { - moduleStrategy = - DeferredLoadingModuleStrategy(component, options, target, coreTypes); - } else if (options.translatorOptions.enableMultiModuleStressTestMode) { - moduleStrategy = StressTestModuleStrategy( - component, coreTypes, options, target, classHierarchy); - } else if (isDynamicMainModule) { - moduleStrategy = DynamicMainModuleStrategy( - component, - coreTypes, - options, - File.fromUri(dynamicInterfaceUri!).readAsStringSync(), - options.dynamicInterfaceUri!); - } else if (isDynamicSubmodule) { - moduleStrategy = DynamicSubmoduleStrategy( - component, options, target, coreTypes, dynamicMainModuleUri!); - } else { - moduleStrategy = DefaultModuleStrategy(component, options); - } + final moduleStrategy = _createModuleStrategy(options, component, coreTypes, + target, classHierarchy, dynamicMainModuleUri, dynamicInterfaceUri); // DynamicMainModuleStrategy.prepareComponent() includes // dynamic_interface_annotator transformation which annotates AST nodes with @@ -337,9 +451,19 @@ useRapidTypeAnalysis: false); } - if (options.dumpKernelAfterTfa != null) { - writeComponentToText(component, - path: options.dumpKernelAfterTfa!, showMetadata: true); + if (options.phases.last == compiler.CompilerPhase.tfa) { + // Store metadata needed for codegen so that it can be serialized. + final recordClassesRepo = _RecordClassesRepository(); + recordClasses.forEach((shape, cls) { + recordClassesRepo.mapping[cls] = shape; + }); + component.addMetadataRepository(recordClassesRepo); + + final interopMethodsRepo = _InteropMethodsRepository(); + jsInteropMethods.forEach((method, info) { + interopMethodsRepo.mapping[method] = info; + }); + component.addMetadataRepository(interopMethodsRepo); } assert(() { @@ -348,6 +472,29 @@ return true; }()); + if (options.dumpKernelAfterTfa != null) { + writeComponentToText(component, + path: options.dumpKernelAfterTfa!, showMetadata: true); + } + + return TfaResult(component, coreTypes, libraryIndex, moduleStrategy, + mainModuleMetadata, jsInteropMethods, recordClasses); +} + +Future<CompilationResult> _runCodegenPhase( + TfaResult tfaSuccess, + compiler.WasmCompilerOptions options, + FileSystem fileSystem, + Uri Function(String moduleName)? sourceMapUrlGenerator) async { + final TfaResult( + :component, + :coreTypes, + :moduleStrategy, + :libraryIndex, + :recordClasses, + :mainModuleMetadata, + :jsInteropMethods + ) = tfaSuccess; await moduleStrategy.processComponentAfterTfa(); final moduleOutputData = moduleStrategy.buildModuleOutputData(); @@ -359,8 +506,8 @@ String? depFile = options.depFile; if (depFile != null) { - writeDepfile(compilerOptions.fileSystem, component.uriToSource.keys, - options.outputFile, depFile); + writeDepfile( + fileSystem, component.uriToSource.keys, options.outputFile, depFile); } final generateSourceMaps = options.translatorOptions.generateSourceMaps; @@ -380,6 +527,13 @@ final jsRuntimeFinalizer = js.RuntimeFinalizer(jsInteropMethods); + final dynamicMainModuleUri = + await _resolveUri(fileSystem, options.dynamicMainModuleUri); + final isDynamicMainModule = + options.dynamicModuleType == DynamicModuleType.main; + final isDynamicSubmodule = + options.dynamicModuleType == DynamicModuleType.submodule; + final jsRuntime = isDynamicSubmodule ? jsRuntimeFinalizer.generateDynamicSubmodule( translator.functions.translatedProcedures, @@ -400,7 +554,38 @@ optimized: true); } - return CompilationSuccess(wasmModules, jsRuntime, supportJs); + return CodegenResult(wasmModules, jsRuntime, supportJs); +} + +ModuleStrategy _createModuleStrategy( + compiler.WasmCompilerOptions options, + Component component, + CoreTypes coreTypes, + WasmTarget target, + ClassHierarchy classHierarchy, + Uri? dynamicMainModuleUri, + Uri? dynamicInterfaceUri) { + final isDynamicMainModule = + options.dynamicModuleType == DynamicModuleType.main; + final isDynamicSubmodule = + options.dynamicModuleType == DynamicModuleType.submodule; + if (options.translatorOptions.enableDeferredLoading) { + return DeferredLoadingModuleStrategy(component, options, target, coreTypes); + } else if (options.translatorOptions.enableMultiModuleStressTestMode) { + return StressTestModuleStrategy( + component, coreTypes, options, target, classHierarchy); + } else if (isDynamicMainModule) { + return DynamicMainModuleStrategy( + component, + coreTypes, + options, + File.fromUri(dynamicInterfaceUri!).readAsStringSync(), + options.dynamicInterfaceUri!); + } else if (isDynamicSubmodule) { + return DynamicSubmoduleStrategy( + component, options, target, coreTypes, dynamicMainModuleUri!); + } + return DefaultModuleStrategy(component, options); } // Patches `dart:_internal`s `mainTearOff{0,1,2}` getters. @@ -434,6 +619,69 @@ if (mainHasType(mainArg0Type)) return patchToReturnMainTearOff(mainTearOff0); } +Future<Uri?> _resolveUri(FileSystem fileSystem, Uri? uri) async { + if (uri == null) return null; + var fileSystemEntity = fileSystem.entityForUri(uri); + if (fileSystemEntity is MultiRootFileSystemEntity) { + fileSystemEntity = await fileSystemEntity.delegate; + } + return fileSystemEntity.uri; +} + +class _RecordClassesRepository extends MetadataRepository<RecordShape> { + static const String _tag = 'dart2wasm.recordClasses'; + @override + final Map<Class, RecordShape> mapping = {}; + + @override + RecordShape readFromBinary(Node node, BinarySource source) { + final positionals = source.readUInt30(); + final namesLength = source.readUInt30(); + final names = namesLength == 0 ? const <String>[] : <String>[]; + for (int i = 0; i < namesLength; i++) { + names.add(source.readStringReference()); + } + return RecordShape(positionals, names); + } + + @override + String get tag => _tag; + + @override + void writeToBinary(RecordShape metadata, Node node, BinarySink sink) { + sink.writeUInt30(metadata.positionals); + sink.writeUInt30(metadata.names.length); + for (final name in metadata.names) { + sink.writeStringReference(name); + } + } +} + +class _InteropMethodsRepository + extends MetadataRepository<({String importName, String jsCode})> { + static const String _tag = 'dart2wasm.interopMethods'; + @override + final Map<Procedure, ({String importName, String jsCode})> mapping = {}; + + @override + ({String importName, String jsCode}) readFromBinary( + Node node, BinarySource source) { + final importName = source.readStringReference(); + final jsCode = source.readStringReference(); + return (importName: importName, jsCode: jsCode); + } + + @override + String get tag => _tag; + + @override + void writeToBinary(({String importName, String jsCode}) metadata, Node node, + BinarySink sink) { + sink.writeStringReference(metadata.importName); + sink.writeStringReference(metadata.jsCode); + } +} + String _generateSupportJs(TranslatorOptions options) { // Copied from // https://github.com/GoogleChromeLabs/wasm-feature-detect/blob/main/src/detectors/gc/index.js
diff --git a/pkg/dart2wasm/lib/compiler_options.dart b/pkg/dart2wasm/lib/compiler_options.dart index e36ff6c..dcb81c7 100644 --- a/pkg/dart2wasm/lib/compiler_options.dart +++ b/pkg/dart2wasm/lib/compiler_options.dart
@@ -3,10 +3,33 @@ // BSD-style license that can be found in the LICENSE file. import 'package:front_end/src/api_unstable/vm.dart' as fe; +import 'package:path/path.dart' as path; import 'dynamic_modules.dart' show DynamicModuleType; import 'translator.dart'; +/// Represents a discrete phase of dart2wasm's compilation process. +/// +/// cfe: Runs the common frontend and applies any modular transforms as part of +/// that process. +/// +/// tfa: Runs global transforms on kernel including, but not limited to, TFA. +/// +/// codegen: Runs the main dart2wasm translation process converting the kernel +/// into WASM modules. +enum CompilerPhase { + cfe, + tfa, + codegen; + + static CompilerPhase parse(String name) { + for (final phase in values) { + if (phase.name == name) return phase; + } + throw ArgumentError('Invalid compiler phase name: $name'); + } +} + class WasmCompilerOptions { final TranslatorOptions translatorOptions = TranslatorOptions(); @@ -31,6 +54,11 @@ String? dumpKernelBeforeTfa; String? dumpKernelAfterTfa; bool dryRun = false; + List<CompilerPhase> phases = const [ + CompilerPhase.cfe, + CompilerPhase.tfa, + CompilerPhase.codegen + ]; factory WasmCompilerOptions.defaultOptions() => WasmCompilerOptions(mainUri: Uri(), outputFile: ''); @@ -64,5 +92,57 @@ "compiling dynamic modules."); } } + + _validatePhases(); + } + + void _validatePhases() { + if (phases.isEmpty) { + throw ArgumentError('--phases must contain at least one phase.'); + } + + CompilerPhase? previousPhase; + for (final phase in phases) { + // Ensure phases are consecutive + if (previousPhase != null && previousPhase.index != phase.index - 1) { + throw ArgumentError('--phases must contain consecutive phases.'); + } + previousPhase = phase; + } + + // Ensure correct input file type + final inputExtension = path.extension(mainUri.path); + switch (phases.first) { + case CompilerPhase.cfe: + if (inputExtension != '.dart') { + throw ArgumentError('Input to cfe phase must be a .dart file.'); + } + case CompilerPhase.tfa: + if (inputExtension != '.dill') { + throw ArgumentError('Input to tfa phase must be a .dill file.'); + } + case CompilerPhase.codegen: + if (inputExtension != '.dill') { + throw ArgumentError('Input to codegen phase must be a .dill file.'); + } + } + + // Ensure correct output file type + final outputExtension = path.extension(outputFile); + switch (phases.last) { + case CompilerPhase.cfe: + if (outputExtension != '.dill') { + throw ArgumentError('Output from cfe phase must be a .dill file.'); + } + case CompilerPhase.tfa: + if (outputExtension != '.dill') { + throw ArgumentError('Output from tfa phase must be a .dill file.'); + } + case CompilerPhase.codegen: + if (outputExtension != '.wasm') { + throw ArgumentError( + 'Output from codegen phase must be a .wasm file.'); + } + } } }
diff --git a/pkg/dart2wasm/lib/dart2wasm.dart b/pkg/dart2wasm/lib/dart2wasm.dart index 9c49b4e..8050c1f 100644 --- a/pkg/dart2wasm/lib/dart2wasm.dart +++ b/pkg/dart2wasm/lib/dart2wasm.dart
@@ -27,6 +27,10 @@ Flag("minify", (o, value) => o.translatorOptions.minify = value, defaultsTo: _d.translatorOptions.minify), Flag("dry-run", (o, value) => o.dryRun = value, defaultsTo: _d.dryRun), + StringMultiOption( + "phases", + (o, values) => o.phases = [...values.map(CompilerPhase.parse)] + ..sort((a, b) => a.index.compareTo(b.index))), Flag("polymorphic-specialization", (o, value) => o.translatorOptions.polymorphicSpecialization = value, defaultsTo: _d.translatorOptions.polymorphicSpecialization),
diff --git a/pkg/dart2wasm/lib/dynamic_module_kernel_metadata.dart b/pkg/dart2wasm/lib/dynamic_module_kernel_metadata.dart index f28a6c3..e92ce58 100644 --- a/pkg/dart2wasm/lib/dynamic_module_kernel_metadata.dart +++ b/pkg/dart2wasm/lib/dynamic_module_kernel_metadata.dart
@@ -13,15 +13,6 @@ show writeComponentToBinary, writeComponentToBytes; import 'package:kernel/library_index.dart'; import 'package:path/path.dart' as path; -import 'package:vm/metadata/direct_call.dart' show DirectCallMetadataRepository; -import 'package:vm/metadata/inferred_type.dart' - show - InferredArgTypeMetadataRepository, - InferredReturnTypeMetadataRepository, - InferredTypeMetadataRepository; -import 'package:vm/metadata/procedure_attributes.dart' - show ProcedureAttributesMetadataRepository; -import 'package:vm/metadata/table_selector.dart'; import 'class_info.dart'; import 'compiler_options.dart'; @@ -30,6 +21,7 @@ import 'js/method_collector.dart' show JSMethods; import 'serialization.dart'; import 'translator.dart'; +import 'util.dart'; const String dynamicMainModuleProcedureAttributeMetadataTag = 'dynMod:procedureAttributes'; @@ -494,15 +486,9 @@ concatenatedComponentBytes.setAll(0, optimizedMainComponentBytes); concatenatedComponentBytes.setAll( optimizedMainComponentBytes.length, submoduleComponentBytes); - final newComponent = Component() + final newComponent = createEmptyComponent() ..addMetadataRepository(DynamicModuleGlobalIdRepository()) - ..addMetadataRepository(DynamicModuleConstantRepository()) - ..addMetadataRepository(ProcedureAttributesMetadataRepository()) - ..addMetadataRepository(TableSelectorMetadataRepository()) - ..addMetadataRepository(DirectCallMetadataRepository()) - ..addMetadataRepository(InferredTypeMetadataRepository()) - ..addMetadataRepository(InferredReturnTypeMetadataRepository()) - ..addMetadataRepository(InferredArgTypeMetadataRepository()); + ..addMetadataRepository(DynamicModuleConstantRepository()); BinaryBuilderWithMetadata(concatenatedComponentBytes) .readComponent(newComponent);
diff --git a/pkg/dart2wasm/lib/generate_wasm.dart b/pkg/dart2wasm/lib/generate_wasm.dart index 0b7022f..2bb6b0b 100644 --- a/pkg/dart2wasm/lib/generate_wasm.dart +++ b/pkg/dart2wasm/lib/generate_wasm.dart
@@ -7,6 +7,7 @@ import 'package:front_end/src/api_prototype/standard_file_system.dart' show StandardFileSystem; import 'package:front_end/src/api_unstable/vm.dart' show printDiagnosticMessage; +import 'package:kernel/kernel.dart'; import 'package:path/path.dart' as path; import 'compile.dart'; @@ -18,6 +19,7 @@ Future<int> generateWasm(WasmCompilerOptions options, {PrintError errorPrinter = print}) async { + options.validate(); final translatorOptions = options.translatorOptions; if (translatorOptions.verbose) { print('Running dart compile wasm...'); @@ -75,7 +77,7 @@ ? moduleNameToRelativeSourceMapUri : null; - CompilationResult result = await compileToModule( + CompilationResult result = await compile( options, StandardFileSystem.instance, relativeSourceMapUrlMapper, (message) { if (!options.dryRun) printDiagnosticMessage(message, errorPrinter); @@ -109,26 +111,35 @@ return 255; } - final writeFutures = <Future>[]; - result.wasmModules.forEach((moduleName, moduleInfo) { - final (:moduleBytes, :sourceMap) = moduleInfo; - final File outFile = File(moduleNameToWasmOutputFile(moduleName)); - outFile.parent.createSync(recursive: true); - writeFutures.add(outFile.writeAsBytes(moduleBytes)); + switch (result) { + case CfeResult(:final component): + await File(options.outputFile) + .writeAsBytes(writeComponentToBytes(component)); + case TfaResult(:final component): + await File(options.outputFile) + .writeAsBytes(writeComponentToBytes(component)); + case CodegenResult(:final wasmModules, :final jsRuntime, :final supportJs): + final writeFutures = <Future>[]; + wasmModules.forEach((moduleName, moduleInfo) { + final (:moduleBytes, :sourceMap) = moduleInfo; + final File outFile = File(moduleNameToWasmOutputFile(moduleName)); + outFile.parent.createSync(recursive: true); + writeFutures.add(outFile.writeAsBytes(moduleBytes)); - if (sourceMap != null) { - writeFutures.add( - File(moduleNameToSourceMapFile(moduleName)).writeAsString(sourceMap)); - } - }); - await Future.wait(writeFutures); + if (sourceMap != null) { + writeFutures.add(File(moduleNameToSourceMapFile(moduleName)) + .writeAsString(sourceMap)); + } + }); + await Future.wait(writeFutures); - final jsFile = path.setExtension(options.outputFile, '.mjs'); - final jsRuntime = result.jsRuntime; - await File(jsFile).writeAsString(jsRuntime); + final jsFile = path.setExtension(options.outputFile, '.mjs'); + await File(jsFile).writeAsString(jsRuntime); - final supportJsFile = path.setExtension(options.outputFile, '.support.js'); - await File(supportJsFile).writeAsString(result.supportJs); + final supportJsFile = + path.setExtension(options.outputFile, '.support.js'); + await File(supportJsFile).writeAsString(supportJs); + } return 0; }
diff --git a/pkg/dart2wasm/lib/util.dart b/pkg/dart2wasm/lib/util.dart index 0526c3a..88ea5ff 100644 --- a/pkg/dart2wasm/lib/util.dart +++ b/pkg/dart2wasm/lib/util.dart
@@ -6,6 +6,16 @@ import 'package:kernel/ast.dart'; import 'package:kernel/core_types.dart'; +import 'package:vm/metadata/direct_call.dart' show DirectCallMetadataRepository; +import 'package:vm/metadata/inferred_type.dart' + show + InferredTypeMetadataRepository, + InferredReturnTypeMetadataRepository, + InferredArgTypeMetadataRepository; +import 'package:vm/metadata/procedure_attributes.dart' + show ProcedureAttributesMetadataRepository; +import 'package:vm/metadata/table_selector.dart' + show TableSelectorMetadataRepository; bool hasPragma(CoreTypes coreTypes, Annotatable node, String name) { return getPragma(coreTypes, node, name, defaultValue: '') != null; @@ -67,3 +77,13 @@ } String intToBase64(int i) => base64.encode(_intToLittleEndianBytes(i)); + +Component createEmptyComponent() { + return Component() + ..addMetadataRepository(ProcedureAttributesMetadataRepository()) + ..addMetadataRepository(TableSelectorMetadataRepository()) + ..addMetadataRepository(DirectCallMetadataRepository()) + ..addMetadataRepository(InferredTypeMetadataRepository()) + ..addMetadataRepository(InferredReturnTypeMetadataRepository()) + ..addMetadataRepository(InferredArgTypeMetadataRepository()); +}
diff --git a/pkg/dart2wasm/test/deferred_loading/load_ids_test.dart b/pkg/dart2wasm/test/deferred_loading/load_ids_test.dart index 362f9b9..e48807e 100644 --- a/pkg/dart2wasm/test/deferred_loading/load_ids_test.dart +++ b/pkg/dart2wasm/test/deferred_loading/load_ids_test.dart
@@ -9,19 +9,11 @@ import 'package:expect/expect.dart'; import 'package:path/path.dart' as path; +import '../util.dart'; + const String helperJsLoadIdLookupToken = 'LOAD_ID_LOOKUP'; const String helperJsModuleDirToken = 'MODULE_DIR'; -final dartAotExecutable = Uri.parse(Platform.resolvedExecutable) - .resolve('dartaotruntime') - .toFilePath(); -final dart2wasmSnapshot = Uri.parse(Platform.resolvedExecutable) - .resolve('snapshots/dart2wasm_product.snapshot') - .toFilePath(); -final platformDill = Uri.parse(Platform.resolvedExecutable) - .resolve('../lib/_internal/dart2wasm_platform.dill') - .toFilePath(); - final String goldenPath = '${path.dirname(Platform.script.path)}/data/deferred_load_ids.golden.json'; final String mainDart = '${path.dirname(Platform.script.path)}/data/main.dart'; @@ -33,10 +25,10 @@ final argsResult = parser.parse(args); - final tmpDir = await Directory.systemTemp.createTemp('wasm-load-ids'); - final loadIdsUri = tmpDir.uri.resolve('deferred_load_ids.json'); - final outFilename = '${tmpDir.path}/out.wasm'; - try { + await withTempDir((tmpDirPath) async { + final tmpDir = File(tmpDirPath); + final loadIdsUri = tmpDir.uri.resolve('deferred_load_ids.json'); + final outFilename = '${tmpDir.path}/out.wasm'; // Compile the test await run([ dartAotExecutable, @@ -82,18 +74,5 @@ // Load the helper JS and run the compiled code await run( ['pkg/dart2wasm/tool/run_benchmark', helperFile.path, outFilename]); - } finally { - await tmpDir.delete(recursive: true); - } -} - -Future<void> run(List<String> command) async { - print('Running: ${command.join(' ')}'); - final result = await Process.run(command.first, command.skip(1).toList()); - if (result.exitCode != 0) { - print('-> Failed with exit code ${result.exitCode}'); - print('-> stdout:\n${result.stdout}'); - print('-> stderr:\n${result.stderr}'); - throw 'Subprocess failed'; - } + }); }
diff --git a/pkg/dart2wasm/test/ir_test.dart b/pkg/dart2wasm/test/ir_test.dart index a92c125..25dd4f8 100644 --- a/pkg/dart2wasm/test/ir_test.dart +++ b/pkg/dart2wasm/test/ir_test.dart
@@ -13,7 +13,7 @@ import 'package:wasm_builder/src/serialize/deserializer.dart'; import 'package:wasm_builder/src/serialize/printer.dart'; -import 'self_compile_test.dart' show withTempDir; +import 'util.dart'; void main(List<String> args) async { final result = argParser.parse(args);
diff --git a/pkg/dart2wasm/test/phases/data/main.dart b/pkg/dart2wasm/test/phases/data/main.dart new file mode 100644 index 0000000..454b085 --- /dev/null +++ b/pkg/dart2wasm/test/phases/data/main.dart
@@ -0,0 +1,7 @@ +// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +void main() { + print('hello world'); +}
diff --git a/pkg/dart2wasm/test/phases/phases_test.dart b/pkg/dart2wasm/test/phases/phases_test.dart new file mode 100644 index 0000000..9ccb297 --- /dev/null +++ b/pkg/dart2wasm/test/phases/phases_test.dart
@@ -0,0 +1,244 @@ +// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'dart:io'; + +import 'package:expect/expect.dart'; +import 'package:path/path.dart' as path; + +import '../util.dart'; + +final String mainDart = '${path.dirname(Platform.script.path)}/data/main.dart'; +final String cfeDillName = 'main.cfe.dill'; +final String tfaDillName = 'main.tfa.dill'; +final String wasmOutName = 'main.wasm'; + +Future<void> main() async { + await testSuccessCases(); + await testFailureCases(); +} + +Future<void> testSuccessCases() async { + await withTempDir((tmpDirPath) async { + final tmpDir = File(tmpDirPath); + + final cfeDill = File.fromUri(tmpDir.uri.resolve(cfeDillName)); + final tfaDill = File.fromUri(tmpDir.uri.resolve(tfaDillName)); + final wasmOut = File.fromUri(tmpDir.uri.resolve(wasmOutName)); + + // Run CFE and expect output + await run([ + dartAotExecutable, + dart2wasmSnapshot, + '--platform=$platformDill', + '--phases=cfe', + mainDart, + cfeDill.path, + ]); + Expect.isTrue(await cfeDill.exists()); + Expect.isTrue((await cfeDill.stat()).size > 0); + + // Run TFA and expect output + await run([ + dartAotExecutable, + dart2wasmSnapshot, + '--platform=$platformDill', + '--phases=tfa', + cfeDill.path, + tfaDill.path, + ]); + Expect.isTrue(await tfaDill.exists()); + Expect.isTrue((await tfaDill.stat()).size > 0); + + // Run codegen and expect output + await run([ + dartAotExecutable, + dart2wasmSnapshot, + '--platform=$platformDill', + '--phases=codegen', + tfaDill.path, + wasmOut.path, + ]); + Expect.isTrue(await wasmOut.exists()); + Expect.isTrue((await wasmOut.stat()).size > 0); + }); + + await withTempDir((tmpDirPath) async { + final tmpDir = File(tmpDirPath); + + final tfaDill = File.fromUri(tmpDir.uri.resolve(tfaDillName)); + final wasmOut = File.fromUri(tmpDir.uri.resolve(wasmOutName)); + + // Run CFE & TFA and expect output + await run([ + dartAotExecutable, + dart2wasmSnapshot, + '--platform=$platformDill', + '--phases=cfe,tfa', + mainDart, + tfaDill.path, + ]); + Expect.isTrue(await tfaDill.exists()); + Expect.isTrue((await tfaDill.stat()).size > 0); + + // Run codegen and expect output + await run([ + dartAotExecutable, + dart2wasmSnapshot, + '--platform=$platformDill', + '--phases=codegen', + tfaDill.path, + wasmOut.path, + ]); + Expect.isTrue(await wasmOut.exists()); + Expect.isTrue((await wasmOut.stat()).size > 0); + }); + + await withTempDir((tmpDirPath) async { + final tmpDir = File(tmpDirPath); + + final cfeDill = File.fromUri(tmpDir.uri.resolve(tfaDillName)); + final wasmOut = File.fromUri(tmpDir.uri.resolve(wasmOutName)); + + // Run CFE and expect output + await run([ + dartAotExecutable, + dart2wasmSnapshot, + '--platform=$platformDill', + '--phases=cfe', + mainDart, + cfeDill.path, + ]); + Expect.isTrue(await cfeDill.exists()); + Expect.isTrue((await cfeDill.stat()).size > 0); + + // Run TFA & codegen and expect output + await run([ + dartAotExecutable, + dart2wasmSnapshot, + '--platform=$platformDill', + '--phases=tfa,codegen', + cfeDill.path, + wasmOut.path, + ]); + Expect.isTrue(await wasmOut.exists()); + Expect.isTrue((await wasmOut.stat()).size > 0); + }); + + await withTempDir((tmpDirPath) async { + final tmpDir = File(tmpDirPath); + + final wasmOut = File.fromUri(tmpDir.uri.resolve(wasmOutName)); + + // Run CFE & TFA & codegen and expect output + await run([ + dartAotExecutable, + dart2wasmSnapshot, + '--platform=$platformDill', + '--phases=cfe,tfa,codegen', + mainDart, + wasmOut.path, + ]); + Expect.isTrue(await wasmOut.exists()); + Expect.isTrue((await wasmOut.stat()).size > 0); + }); +} + +Future<void> testFailureCases() async { + await withTempDir((tmpDirPath) async { + final tmpDir = File(tmpDirPath); + + final cfeDill = File.fromUri(tmpDir.uri.resolve(cfeDillName)); + final tfaDill = File.fromUri(tmpDir.uri.resolve(tfaDillName)); + final wasmOut = File.fromUri(tmpDir.uri.resolve(wasmOutName)); + + // CFE checks + await expectFailedRun([ + dartAotExecutable, + dart2wasmSnapshot, + '--platform=$platformDill', + '--phases=cfe', + tfaDill.path, + cfeDill.path + ], 'Input to cfe phase must be a .dart file'); + + await expectFailedRun([ + dartAotExecutable, + dart2wasmSnapshot, + '--platform=$platformDill', + '--phases=cfe', + mainDart, + wasmOut.path + ], 'Output from cfe phase must be a .dill file'); + + // TFA checks + await expectFailedRun([ + dartAotExecutable, + dart2wasmSnapshot, + '--platform=$platformDill', + '--platform=$platformDill', + '--phases=tfa', + mainDart, + tfaDill.path + ], 'Input to tfa phase must be a .dill file'); + + await expectFailedRun([ + dartAotExecutable, + dart2wasmSnapshot, + '--platform=$platformDill', + '--phases=tfa', + cfeDill.path, + wasmOut.path + ], 'Output from tfa phase must be a .dill file'); + + // Codegen checks + await expectFailedRun([ + dartAotExecutable, + dart2wasmSnapshot, + '--platform=$platformDill', + '--platform=$platformDill', + '--phases=codegen', + mainDart, + wasmOut.path + ], 'Input to codegen phase must be a .dill file'); + + await expectFailedRun([ + dartAotExecutable, + dart2wasmSnapshot, + '--platform=$platformDill', + '--phases=codegen', + tfaDill.path, + cfeDill.path + ], 'Output from codegen phase must be a .wasm file'); + + // Other checks + await expectFailedRun([ + dartAotExecutable, + dart2wasmSnapshot, + '--platform=$platformDill', + '--phases=cfe,codegen', + mainDart, + wasmOut.path + ], 'must contain consecutive phases'); + + await expectFailedRun([ + dartAotExecutable, + dart2wasmSnapshot, + '--platform=$platformDill', + '--phases=notReal', + mainDart, + cfeDill.path + ], 'Invalid compiler phase name'); + }); +} + +Future<void> expectFailedRun( + List<String> command, String expectedSubstring) async { + try { + await run(command, throwOutputOnFailure: true); + Expect.fail('Expected dart2wasm error.'); + } catch (e) { + Expect.contains(expectedSubstring, '$e'); + } +}
diff --git a/pkg/dart2wasm/test/self_compile_test.dart b/pkg/dart2wasm/test/self_compile_test.dart index cdb8097..d84ba4c 100644 --- a/pkg/dart2wasm/test/self_compile_test.dart +++ b/pkg/dart2wasm/test/self_compile_test.dart
@@ -7,6 +7,8 @@ import 'package:path/path.dart' as path; +import 'util.dart'; + Future main() async { if (!Platform.isLinux && !Platform.isMacOS) return; @@ -44,17 +46,6 @@ }); } -Future run(List<String> command) async { - print('Running: ${command.join(' ')}'); - final result = await Process.run(command.first, command.skip(1).toList()); - if (result.exitCode != 0) { - print('-> Failed with exit code ${result.exitCode}'); - print('-> stdout:\n${result.stdout}'); - print('-> stderr:\n${result.stderr}'); - throw 'Subprocess failed'; - } -} - void expectEqualBytes(Uint8List a, Uint8List b) { if (a.length != b.length) { throw 'Mismatch in length ${a.length} vs ${b.length}'; @@ -65,18 +56,3 @@ } } } - -Future withTempDir(Future Function(String directory) fun) async { - final dir = Directory.systemTemp.createTempSync('dart2wasm_self_compile'); - try { - print('Running with temporary directory: ${dir.path}'); - return await fun(dir.path); - } finally { - if (!keepTemporaryDirectory) { - dir.deleteSync(recursive: true); - } - } -} - -final bool keepTemporaryDirectory = - (Platform.environment['KEEP_TEMPORARY_DIRECTORIES'] ?? 'false') != 'false';
diff --git a/pkg/dart2wasm/test/util.dart b/pkg/dart2wasm/test/util.dart new file mode 100644 index 0000000..3952b7c --- /dev/null +++ b/pkg/dart2wasm/test/util.dart
@@ -0,0 +1,46 @@ +// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'dart:io'; + +final dartAotExecutable = Uri.parse(Platform.resolvedExecutable) + .resolve('dartaotruntime') + .toFilePath(); +final dart2wasmSnapshot = Uri.parse(Platform.resolvedExecutable) + .resolve('snapshots/dart2wasm_product.snapshot') + .toFilePath(); +final platformDill = Uri.parse(Platform.resolvedExecutable) + .resolve('../lib/_internal/dart2wasm_platform.dill') + .toFilePath(); + +Future<void> run(List<String> command, + {bool throwOutputOnFailure = false}) async { + print('Running: ${command.join(' ')}'); + final result = await Process.run(command.first, command.skip(1).toList()); + if (result.exitCode != 0) { + if (throwOutputOnFailure) { + throw '${result.stdout}\n${result.stderr}'; + } + + print('-> Failed with exit code ${result.exitCode}'); + print('-> stdout:\n${result.stdout}'); + print('-> stderr:\n${result.stderr}'); + throw 'Subprocess failed'; + } +} + +Future withTempDir(Future Function(String directory) fun) async { + final dir = Directory.systemTemp.createTempSync('dart2wasm_self_compile'); + try { + print('Running with temporary directory: ${dir.path}'); + return await fun(dir.path); + } finally { + if (!keepTemporaryDirectory) { + dir.deleteSync(recursive: true); + } + } +} + +final bool keepTemporaryDirectory = + (Platform.environment['KEEP_TEMPORARY_DIRECTORIES'] ?? 'false') != 'false';
diff --git a/pkg/dart2wasm/test/wasm2wat_test.dart b/pkg/dart2wasm/test/wasm2wat_test.dart index 94fb59d..bda08cf 100644 --- a/pkg/dart2wasm/test/wasm2wat_test.dart +++ b/pkg/dart2wasm/test/wasm2wat_test.dart
@@ -8,7 +8,7 @@ import 'package:path/path.dart' as path; import 'package:wasm_builder/wasm_builder.dart'; -import 'self_compile_test.dart' show withTempDir, run; +import 'util.dart'; Future main() async { if (!Platform.isLinux && !Platform.isMacOS) return;
diff --git a/pkg/dart2wasm/test/wasm_read_write_test.dart b/pkg/dart2wasm/test/wasm_read_write_test.dart index a005f79..55b7ccd 100644 --- a/pkg/dart2wasm/test/wasm_read_write_test.dart +++ b/pkg/dart2wasm/test/wasm_read_write_test.dart
@@ -8,7 +8,8 @@ import 'package:path/path.dart' as path; import 'package:wasm_builder/wasm_builder.dart'; -import 'self_compile_test.dart' show withTempDir, run, expectEqualBytes; +import 'self_compile_test.dart' show expectEqualBytes; +import 'util.dart'; Future main() async { if (!Platform.isLinux && !Platform.isMacOS) return;
diff --git a/pkg/dart2wasm/tool/compile_benchmark b/pkg/dart2wasm/tool/compile_benchmark index 79fcbf1..9c111b5 100755 --- a/pkg/dart2wasm/tool/compile_benchmark +++ b/pkg/dart2wasm/tool/compile_benchmark
@@ -81,7 +81,7 @@ DART2WASM_ARGS=("--require-js-string-builtin") ADDITIONAL_BINARYEN_FLAGS=() DART_FILE="" -WASM_FILE="" +OUTPUT_FILE="" while [ $# -gt 0 ]; do case "$1" in --compile-benchmark=*) @@ -176,7 +176,7 @@ -o) shift - WASM_FILE="$1" + OUTPUT_FILE="$1" shift ;; @@ -190,8 +190,8 @@ DART_FILE="$1" shift else - if [ -z "$WASM_FILE" ]; then - WASM_FILE="$1" + if [ -z "$OUTPUT_FILE" ]; then + OUTPUT_FILE="$1" shift else echo "Unexpected argument $1" @@ -203,10 +203,10 @@ done if [ $GENERATE_SOURCE_MAP -eq 1 ]; then - BINARYEN_FLAGS+=("-ism" "${WASM_FILE}.map" "-osm" "${WASM_FILE}.map") + BINARYEN_FLAGS+=("-ism" "${OUTPUT_FILE}.map" "-osm" "${OUTPUT_FILE}.map") fi -if [ -z "$DART_FILE" -o -z "$WASM_FILE" ]; then +if [ -z "$DART_FILE" -o -z "$OUTPUT_FILE" ]; then echo "Expected <file.dart> <file.wasm>" exit 1 fi @@ -237,7 +237,7 @@ } function run_if_binaryen_enabled() { - if [ $RUN_BINARYEN -eq 1 ]; then + if [ $RUN_BINARYEN -eq 1 ] && [[ $OUTPUT_FILE == *.wasm ]]; then $@ fi } @@ -247,9 +247,9 @@ function run_compiler() { if [ $RUN_SRC -eq 1 ]; then - dart2wasm_command=("$DART" "${VM_ARGS[@]}" "$DART2WASM_SRC" "$LIBRARIES_JSON_ARG" "${DART2WASM_ARGS[@]}" "$DART_FILE" "$WASM_FILE") + dart2wasm_command=("$DART" "${VM_ARGS[@]}" "$DART2WASM_SRC" "$LIBRARIES_JSON_ARG" "${DART2WASM_ARGS[@]}" "$DART_FILE" "$OUTPUT_FILE") else - dart2wasm_command=("$DART_AOT_RUNTIME" "${VM_ARGS[@]}" "$DART2WASM_AOT_SNAPSHOT" "$PLATFORM_ARG" "${DART2WASM_ARGS[@]}" "$DART_FILE" "$WASM_FILE") + dart2wasm_command=("$DART_AOT_RUNTIME" "${VM_ARGS[@]}" "$DART2WASM_AOT_SNAPSHOT" "$PLATFORM_ARG" "${DART2WASM_ARGS[@]}" "$DART_FILE" "$OUTPUT_FILE") fi if [ -n "$COMPILE_BENCHMARK_BASE_NAME" ]; then @@ -257,18 +257,18 @@ COMPILER_TIME=$TIME COMPILER_MEMORY=$MEMORY - measure_size ${WASM_FILE%.wasm}.mjs + measure_size ${OUTPUT_FILE%.wasm}.mjs MJS_SIZE=$SIZE MJS_GZIP_SIZE=$GZIP_SIZE if [ $MULTI_MODULE -eq 1 ]; then - for WASM_FILE in "${WASM_FILE%.wasm}"*.wasm; do - measure_size $WASM_FILE + for OUTPUT_FILE in "${OUTPUT_FILE%.wasm}"*.wasm; do + measure_size $OUTPUT_FILE (( COMPILER_SIZE+=$SIZE )) (( COMPILER_GZIP_SIZE+=$GZIP_SIZE )) done else - measure_size $WASM_FILE + measure_size $OUTPUT_FILE COMPILER_SIZE=$SIZE COMPILER_GZIP_SIZE=$GZIP_SIZE fi @@ -283,13 +283,13 @@ BINARYEN_GZIP_SIZE=0 function run_binaryen_single() { - binaryen_command=("$BINARYEN" "$@" "${ADDITIONAL_BINARYEN_FLAGS[@]}" "$WASM_FILE" -o "$WASM_FILE") + binaryen_command=("$BINARYEN" "$@" "${ADDITIONAL_BINARYEN_FLAGS[@]}" "$OUTPUT_FILE" -o "$OUTPUT_FILE") if [ -n "$COMPILE_BENCHMARK_BASE_NAME" ]; then # If we're measuring run each binaryen command sequentially. measure ${binaryen_command[@]} BINARYEN_TIME=$(echo "$BINARYEN_TIME + $TIME" | bc) BINARYEN_MEMORY=$(($BINARYEN_MEMORY > $MEMORY ? $BINARYEN_MEMORY : $MEMORY )) - measure_size $WASM_FILE + measure_size $OUTPUT_FILE BINARYEN_SIZE=$(echo "$BINARYEN_SIZE + $SIZE" | bc) BINARYEN_GZIP_SIZE=$(echo "$BINARYEN_GZIP_SIZE + $GZIP_SIZE" | bc) else @@ -301,7 +301,7 @@ if [ $MULTI_MODULE -eq 1 ]; then # Iterate over all matching wasm files and optimize them concurrently in # different processes. - for WASM_FILE in "${WASM_FILE%.wasm}"*.wasm; do + for OUTPUT_FILE in "${OUTPUT_FILE%.wasm}"*.wasm; do run_binaryen_single "${BINARYEN_FLAGS_DEFERRED_LOADING[@]}" done else
diff --git a/pkg/dartdev/lib/src/commands/compile.dart b/pkg/dartdev/lib/src/commands/compile.dart index 542e278..2a6adab 100644 --- a/pkg/dartdev/lib/src/commands/compile.dart +++ b/pkg/dartdev/lib/src/commands/compile.dart
@@ -949,13 +949,7 @@ outputFile = '$inputWithoutDart.wasm'; } - if (!outputFile.endsWith('.wasm')) { - log.stderr( - 'Error: The output file "$outputFile" does not end with ".wasm"'); - return 255; - } - final outputFileBasename = - outputFile.substring(0, outputFile.length - '.wasm'.length); + final outputFileBasename = path.withoutExtension(outputFile); final packages = args.option(packagesOption.flag); final defines = args.multiOption(defineOption.flag); @@ -974,7 +968,9 @@ extraCompilerOptions .any((e) => e.contains('enable-multi-module-stress-test')); final optimizationLevel = int.parse(args.option('optimization-level')!); - final runWasmOpt = optimizationLevel >= 1; + + final runWasmOpt = + optimizationLevel >= 1 && path.extension(outputFile) == '.wasm'; if (runWasmOpt && !checkArtifactExists(sdk.wasmOpt)) { return 255;
diff --git a/pkg/dartdev/test/commands/compile_test.dart b/pkg/dartdev/test/commands/compile_test.dart index 4667ed4..f0176da 100644 --- a/pkg/dartdev/test/commands/compile_test.dart +++ b/pkg/dartdev/test/commands/compile_test.dart
@@ -815,24 +815,6 @@ ); }, skip: isRunningOnIA32); - test('Compile wasm with wrong output filename', () async { - final p = project(mainSrc: 'void main() {}'); - final inFile = path.canonicalize(path.join(p.dirPath, p.relativeFilePath)); - final result = await p.run( - [ - 'compile', - 'wasm', - '-o', - 'foo', - inFile, - ], - ); - - expect(result.stderr, - contains('Error: The output file "foo" does not end with ".wasm"')); - expect(result.exitCode, genericErrorExitCode); - }, skip: isRunningOnIA32); - test('Compile wasm with error', () async { final p = project(mainSrc: ''' void main() {
diff --git a/pkg/wasm_builder/lib/src/ir/module.dart b/pkg/wasm_builder/lib/src/ir/module.dart index 112e514..000e3d9 100644 --- a/pkg/wasm_builder/lib/src/ir/module.dart +++ b/pkg/wasm_builder/lib/src/ir/module.dart
@@ -119,7 +119,8 @@ SourceMapSection(sourceMapUrl).serialize(s); } - static Module deserialize(Deserializer d) { + static (Map<int, List<Deserializer>>, Map<String, List<Deserializer>>) + _deserializeTopLevel(Deserializer d) { final preamble = d.readBytes(8); if (preamble[0] != 0x00 || preamble[1] != 0x61 || @@ -151,6 +152,12 @@ } } + return (sections, customSections); + } + + static Module deserialize(Deserializer d) { + final (sections, customSections) = _deserializeTopLevel(d); + final Module module = Module.uninitialized(); // We read the sections in the order they should be in the binary. @@ -235,6 +242,14 @@ ); } + /// Deserialize just the `sourceMapUrl` section of a module as a [Uri]. + static Uri? deserializeSourceMapUrl(Deserializer d) { + final (sections, customSections) = _deserializeTopLevel(d); + final sourceMapUrl = SourceMapSection.deserialize( + customSections[SourceMapSection.customSectionName]?.single); + return sourceMapUrl; + } + String printAsWat() { final mp = ModulePrinter(this);
diff --git a/tools/VERSION b/tools/VERSION index d610def..6a0006f 100644 --- a/tools/VERSION +++ b/tools/VERSION
@@ -27,5 +27,5 @@ MAJOR 3 MINOR 11 PATCH 0 -PRERELEASE 33 +PRERELEASE 34 PRERELEASE_PATCH 0