Version 3.12.0-234.0.dev Merge 2b36b13ecdf10f30c96175165b03b4aeff23ebae into dev
diff --git a/BUILD.gn b/BUILD.gn index 2a6e0a1..c117953 100644 --- a/BUILD.gn +++ b/BUILD.gn
@@ -61,6 +61,7 @@ deps += [ "runtime/bin:dartaotruntime", "runtime/bin:dartaotruntime_product", + "utils/dart_runtime_service_vm:dart_runtime_service_vm_aot_snapshot", "utils/dartdev:dartdev_aot_snapshot", "utils/dds:dds_aot", "utils/dtd:dtd_aot", @@ -68,6 +69,7 @@ ] } else { deps += [ + "utils/dart_runtime_service_vm", "utils/dds", "utils/dtd", "utils/kernel-service:frontend_server",
diff --git a/pkg/analysis_server/lib/src/handler/legacy/edit_format.dart b/pkg/analysis_server/lib/src/handler/legacy/edit_format.dart index 7f7d30b..a2da8d5 100644 --- a/pkg/analysis_server/lib/src/handler/legacy/edit_format.dart +++ b/pkg/analysis_server/lib/src/handler/legacy/edit_format.dart
@@ -7,9 +7,9 @@ import 'package:analysis_server/protocol/protocol.dart'; import 'package:analysis_server/protocol/protocol_generated.dart'; import 'package:analysis_server/src/handler/legacy/legacy_handler.dart'; -import 'package:analysis_server/src/utilities/extensions/formatter_options.dart'; import 'package:analyzer/dart/analysis/results.dart'; import 'package:analyzer_plugin/protocol/protocol_common.dart'; +import 'package:analyzer_plugin/src/utilities/formatter.dart'; import 'package:dart_style/dart_style.dart' hide TrailingCommas; /// The handler for the `edit.format` request. @@ -47,25 +47,17 @@ length = null; } + var formatter = createFormatter(unit, defaultPageWidth: params.lineLength); var unformattedCode = unit.content; - var code = SourceCode( - unformattedCode, - selectionStart: start, - selectionLength: length, - ); - - var formatterOptions = unit.analysisOptions.formatterOptions; - var effectivePageWidth = formatterOptions.pageWidth ?? params.lineLength; - var effectiveTrailingCommas = formatterOptions.dartStyleTrailingCommas; - var effectiveLanguageVersion = unit.unit.languageVersion.effective; - var formatter = DartFormatter( - pageWidth: effectivePageWidth, - trailingCommas: effectiveTrailingCommas, - languageVersion: effectiveLanguageVersion, - ); SourceCode formattedResult; try { - formattedResult = formatter.formatSource(code); + formattedResult = formatter.formatSource( + SourceCode( + unformattedCode, + selectionStart: start, + selectionLength: length, + ), + ); } on FormatterException { sendResponse(Response.formatWithErrors(request)); return;
diff --git a/pkg/analysis_server/lib/src/handler/legacy/edit_format_if_enabled.dart b/pkg/analysis_server/lib/src/handler/legacy/edit_format_if_enabled.dart index acbfc4a..17ba7d2 100644 --- a/pkg/analysis_server/lib/src/handler/legacy/edit_format_if_enabled.dart +++ b/pkg/analysis_server/lib/src/handler/legacy/edit_format_if_enabled.dart
@@ -36,6 +36,9 @@ var originalContent = file.readAsStringSync(); var code = SourceCode(originalContent); + // TODO(dantup): Consider using createFormatter() which takes a result and + // correctly applies settings like page_width, trailing_commas, and enabled + // experiments. var formatter = DartFormatter( languageVersion: languageVersion ?? DartFormatter.latestLanguageVersion, );
diff --git a/pkg/analysis_server/lib/src/lsp/source_edits.dart b/pkg/analysis_server/lib/src/lsp/source_edits.dart index e07a1a8..3adf3f8 100644 --- a/pkg/analysis_server/lib/src/lsp/source_edits.dart +++ b/pkg/analysis_server/lib/src/lsp/source_edits.dart
@@ -8,7 +8,6 @@ import 'package:analysis_server/src/protocol_server.dart' as server show SourceEdit; -import 'package:analysis_server/src/utilities/extensions/formatter_options.dart'; import 'package:analyzer/dart/analysis/features.dart'; import 'package:analyzer/dart/analysis/results.dart'; import 'package:analyzer/dart/ast/token.dart'; @@ -17,6 +16,7 @@ import 'package:analyzer/source/source.dart'; import 'package:analyzer/src/dart/scanner/scanner.dart'; import 'package:analyzer_plugin/protocol/protocol_common.dart' as plugin; +import 'package:analyzer_plugin/src/utilities/formatter.dart'; import 'package:dart_style/dart_style.dart' hide TrailingCommas; /// Checks whether a string contains only characters that are allowed to differ @@ -130,25 +130,10 @@ }) { var unformattedSource = result.content; - var formatterOptions = result.analysisOptions.formatterOptions; - // The analysis options page width always takes priority over the default from - // the LSP configuration. - var effectivePageWidth = formatterOptions.pageWidth ?? defaultPageWidth; - var effectiveTrailingCommas = formatterOptions.dartStyleTrailingCommas; - var effectiveLanguageVersion = result.unit.languageVersion.effective; - - var code = SourceCode(unformattedSource); - SourceCode formattedResult; + var formatter = createFormatter(result, defaultPageWidth: defaultPageWidth); + String formattedSource; try { - // Create a new formatter on every request because it may contain state that - // affects repeated formats. - // https://github.com/dart-lang/dart_style/issues/1337 - var formatter = DartFormatter( - pageWidth: effectivePageWidth, - trailingCommas: effectiveTrailingCommas, - languageVersion: effectiveLanguageVersion, - ); - formattedResult = formatter.formatSource(code); + formattedSource = formatter.format(unformattedSource); } on FormatterException { // If the document fails to parse, just return no edits to avoid the // use seeing edits on every save with invalid code (if LSP gains the @@ -156,7 +141,6 @@ // we may wish to change this to return an error for that case). return success(null); } - var formattedSource = formattedResult.text; if (formattedSource == unformattedSource) { return success(null);
diff --git a/pkg/analyzer/lib/src/dart/ast/ast.dart b/pkg/analyzer/lib/src/dart/ast/ast.dart index 4387c7b..1a71fd8 100644 --- a/pkg/analyzer/lib/src/dart/ast/ast.dart +++ b/pkg/analyzer/lib/src/dart/ast/ast.dart
@@ -13850,6 +13850,7 @@ ], ) final class FunctionExpressionInvocationImpl extends InvocationExpressionImpl + with DotShorthandMixin implements RewrittenMethodInvocationImpl, FunctionExpressionInvocation { @generated ExpressionImpl _function;
diff --git a/pkg/analyzer/lib/src/fasta/ast_builder.dart b/pkg/analyzer/lib/src/fasta/ast_builder.dart index 8bd1bce..90a43e7 100644 --- a/pkg/analyzer/lib/src/fasta/ast_builder.dart +++ b/pkg/analyzer/lib/src/fasta/ast_builder.dart
@@ -4042,16 +4042,14 @@ var dotShorthand = pop() as ExpressionImpl; if (dotShorthand is DotShorthandMixin) { dotShorthand.isDotShorthand = true; + } else { + assert( + false, + "'$dotShorthand' must be a 'DotShorthandMixin' because we " + "should only call 'handleDotShorthandContext' after parsing " + "expressions that have a context type we can cache.", + ); } - // TODO(kallentu): Add this assert once we've applied the DotShorthandMixin - // on all possible expressions that can be a dot shorthand. - // } else { - // assert( - // false, - // "'$dotShorthand' must be a 'DotShorthandMixin' because we " - // "should only call 'handleDotShorthandContext' after parsing " - // "expressions that have a context type we can cache."); - // } push(dotShorthand); }
diff --git a/pkg/analyzer/lib/src/generated/resolver.dart b/pkg/analyzer/lib/src/generated/resolver.dart index 7fbb5b7..33b034d 100644 --- a/pkg/analyzer/lib/src/generated/resolver.dart +++ b/pkg/analyzer/lib/src/generated/resolver.dart
@@ -3084,6 +3084,12 @@ TypeImpl contextType = UnknownInferredType.instance, }) { inferenceLogWriter?.enterExpression(node, contextType); + + // If [isDotShorthand] is set, cache the context type for resolution. + if (isDotShorthand(node)) { + pushDotShorthandContext(node, SharedTypeSchemaView(contextType)); + } + analyzeExpression( node.function, SharedTypeSchemaView(UnknownInferredType.instance), @@ -3107,6 +3113,11 @@ whyNotPromotedArguments, ); _insertImplicitCallReference(replacement, contextType: contextType); + + if (isDotShorthand(node)) { + popDotShorthandContext(); + } + inferenceLogWriter?.exitExpression(node); }
diff --git a/pkg/analyzer/test/src/dart/resolution/dot_shorthand_constructor_invocation_test.dart b/pkg/analyzer/test/src/dart/resolution/dot_shorthand_constructor_invocation_test.dart index dd54e93..a499c1a 100644 --- a/pkg/analyzer/test/src/dart/resolution/dot_shorthand_constructor_invocation_test.dart +++ b/pkg/analyzer/test/src/dart/resolution/dot_shorthand_constructor_invocation_test.dart
@@ -869,6 +869,279 @@ ); } + test_functionExpression() async { + await assertErrorsInCode( + r''' +class C {} + +void main() { + final C _ = .new()(); +} +''', + [error(diag.invocationOfNonFunctionExpression, 40, 6)], + ); + } + + test_functionExpression_call() async { + await assertNoErrorsInCode(r''' +class C { + C call() => this; +} + +void main() { + final C _ = .new()(); +} +'''); + + var node = findNode.singleFunctionExpressionInvocation; + assertResolvedNodeText(node, r''' +FunctionExpressionInvocation + function: DotShorthandConstructorInvocation + period: . + constructorName: SimpleIdentifier + token: new + element: <testLibrary>::@class::C::@constructor::new + staticType: null + argumentList: ArgumentList + leftParenthesis: ( + rightParenthesis: ) + isDotShorthand: false + staticType: C + argumentList: ArgumentList + leftParenthesis: ( + rightParenthesis: ) + element: <testLibrary>::@class::C::@method::call + staticInvokeType: C Function() + staticType: C +'''); + } + + test_functionExpression_call_argument() async { + await assertNoErrorsInCode(r''' +class C { + C call(int x) => this; +} + +void main() { + C _ = .new()(0); +} +'''); + + var node = findNode.singleFunctionExpressionInvocation; + assertResolvedNodeText(node, r''' +FunctionExpressionInvocation + function: DotShorthandConstructorInvocation + period: . + constructorName: SimpleIdentifier + token: new + element: <testLibrary>::@class::C::@constructor::new + staticType: null + argumentList: ArgumentList + leftParenthesis: ( + rightParenthesis: ) + isDotShorthand: false + staticType: C + argumentList: ArgumentList + leftParenthesis: ( + arguments + IntegerLiteral + literal: 0 + correspondingParameter: <testLibrary>::@class::C::@method::call::@formalParameter::x + staticType: int + rightParenthesis: ) + element: <testLibrary>::@class::C::@method::call + staticInvokeType: C Function(int) + staticType: C +'''); + } + + test_functionExpression_call_extension() async { + await assertNoErrorsInCode(r''' +class C {} + +extension CallC on C { + C call() => this; +} + +void main() { + final C _ = .new()(); +} +'''); + + var node = findNode.singleFunctionExpressionInvocation; + assertResolvedNodeText(node, r''' +FunctionExpressionInvocation + function: DotShorthandConstructorInvocation + period: . + constructorName: SimpleIdentifier + token: new + element: <testLibrary>::@class::C::@constructor::new + staticType: null + argumentList: ArgumentList + leftParenthesis: ( + rightParenthesis: ) + isDotShorthand: false + staticType: C + argumentList: ArgumentList + leftParenthesis: ( + rightParenthesis: ) + element: <testLibrary>::@extension::CallC::@method::call + staticInvokeType: C Function() + staticType: C +'''); + } + + test_functionExpression_call_generic() async { + await assertNoErrorsInCode(r''' +class C { + C call<T>(T t) => this; +} + +void main() { + C _ = .new()<int>(0); +} +'''); + + var constructor = findNode.singleFunctionExpressionInvocation; + assertResolvedNodeText(constructor, r''' +FunctionExpressionInvocation + function: DotShorthandConstructorInvocation + period: . + constructorName: SimpleIdentifier + token: new + element: <testLibrary>::@class::C::@constructor::new + staticType: null + argumentList: ArgumentList + leftParenthesis: ( + rightParenthesis: ) + isDotShorthand: false + staticType: C + typeArguments: TypeArgumentList + leftBracket: < + arguments + NamedType + name: int + element: dart:core::@class::int + type: int + rightBracket: > + argumentList: ArgumentList + leftParenthesis: ( + arguments + IntegerLiteral + literal: 0 + correspondingParameter: ParameterMember + baseElement: <testLibrary>::@class::C::@method::call::@formalParameter::t + substitution: {T: int} + staticType: int + rightParenthesis: ) + element: <testLibrary>::@class::C::@method::call + staticInvokeType: C Function(int) + staticType: C + typeArgumentTypes + int +'''); + } + + test_functionExpression_call_namedConstructor() async { + await assertNoErrorsInCode(r''' +class C { + C.named(); + C call() => this; +} + +void main() { + final C _ = .named()(); +} +'''); + + var node = findNode.singleFunctionExpressionInvocation; + assertResolvedNodeText(node, r''' +FunctionExpressionInvocation + function: DotShorthandConstructorInvocation + period: . + constructorName: SimpleIdentifier + token: named + element: <testLibrary>::@class::C::@constructor::named + staticType: null + argumentList: ArgumentList + leftParenthesis: ( + rightParenthesis: ) + isDotShorthand: false + staticType: C + argumentList: ArgumentList + leftParenthesis: ( + rightParenthesis: ) + element: <testLibrary>::@class::C::@method::call + staticInvokeType: C Function() + staticType: C +'''); + } + + test_functionExpression_call_nested() async { + await assertNoErrorsInCode(r''' +class C { + C(C c); + C.a(); + C call() => this; +} + +void main() { + C _ = .new(.a())(); +} +'''); + + var node = findNode.singleFunctionExpressionInvocation; + assertResolvedNodeText(node, r''' +FunctionExpressionInvocation + function: DotShorthandConstructorInvocation + period: . + constructorName: SimpleIdentifier + token: new + element: <testLibrary>::@class::C::@constructor::new + staticType: null + argumentList: ArgumentList + leftParenthesis: ( + arguments + DotShorthandConstructorInvocation + period: . + constructorName: SimpleIdentifier + token: a + element: <testLibrary>::@class::C::@constructor::a + staticType: null + argumentList: ArgumentList + leftParenthesis: ( + rightParenthesis: ) + isDotShorthand: true + correspondingParameter: <testLibrary>::@class::C::@constructor::new::@formalParameter::c + staticType: C + rightParenthesis: ) + isDotShorthand: false + staticType: C + argumentList: ArgumentList + leftParenthesis: ( + rightParenthesis: ) + element: <testLibrary>::@class::C::@method::call + staticInvokeType: C Function() + staticType: C +'''); + } + + test_functionExpression_nested() async { + await assertErrorsInCode( + r''' +class C { + C(C c); + C.a(); +} + +void main() { + C _ = .new(.a())(); +} +''', + [error(diag.invocationOfNonFunctionExpression, 54, 10)], + ); + } + test_nested_invocation() async { await assertNoErrorsInCode(r''' class C<T> {
diff --git a/pkg/analyzer/test/src/dart/resolution/dot_shorthand_invocation_test.dart b/pkg/analyzer/test/src/dart/resolution/dot_shorthand_invocation_test.dart index 903705b..4c7fb72 100644 --- a/pkg/analyzer/test/src/dart/resolution/dot_shorthand_invocation_test.dart +++ b/pkg/analyzer/test/src/dart/resolution/dot_shorthand_invocation_test.dart
@@ -149,95 +149,6 @@ '''); } - test_call_getter() async { - await assertNoErrorsInCode(r''' -class C { - const C(); - static C get id1 => const C(); - C call() => const C(); -} - -void main() { - C c1 = .id1(); - print(c1); -} -'''); - - // The [DotShorthandInvocation] is rewritten to a - // [FunctionExpressionInvocation]. - var node = findNode.singleFunctionExpressionInvocation; - assertResolvedNodeText(node, r''' -FunctionExpressionInvocation - function: DotShorthandPropertyAccess - period: . - propertyName: SimpleIdentifier - token: id1 - element: <testLibrary>::@class::C::@getter::id1 - staticType: C - isDotShorthand: false - staticType: C - argumentList: ArgumentList - leftParenthesis: ( - rightParenthesis: ) - element: <testLibrary>::@class::C::@method::call - staticInvokeType: C Function() - staticType: C -'''); - } - - test_call_noCallMethod() async { - await assertErrorsInCode( - r''' -class C { - const C(); - static C id1 = const C(); -} - -void main() { - C c1 = .id1(); - print(c1); -} -''', - [error(diag.invocationOfNonFunctionExpression, 77, 4)], - ); - } - - test_call_property() async { - await assertNoErrorsInCode(r''' -class C { - const C(); - static C id1 = const C(); - C call() => const C(); -} - -void main() { - C c1 = .id1(); - print(c1); -} -'''); - - // The [DotShorthandInvocation] is rewritten to a - // [FunctionExpressionInvocation]. - var node = findNode.singleFunctionExpressionInvocation; - assertResolvedNodeText(node, r''' -FunctionExpressionInvocation - function: DotShorthandPropertyAccess - period: . - propertyName: SimpleIdentifier - token: id1 - element: <testLibrary>::@class::C::@getter::id1 - staticType: C - isDotShorthand: false - staticType: C - argumentList: ArgumentList - leftParenthesis: ( - rightParenthesis: ) - element: <testLibrary>::@class::C::@method::call - staticInvokeType: C Function() - staticType: C -'''); - } - test_chain_method() async { await assertNoErrorsInCode(r''' class C { @@ -471,6 +382,257 @@ '''); } + test_functionExpression() async { + await assertErrorsInCode( + r''' +class C { + static C member() => C(); +} + +void main() { + final C _ = .member()(); +} +''', + [error(diag.invocationOfNonFunctionExpression, 69, 9)], + ); + } + + test_functionExpression_call() async { + await assertNoErrorsInCode(r''' +class C { + static C member() => C(); + C call() => this; +} + +void main() { + final C _ = .member()(); +} +'''); + + var node = findNode.singleFunctionExpressionInvocation; + assertResolvedNodeText(node, r''' +FunctionExpressionInvocation + function: DotShorthandInvocation + period: . + memberName: SimpleIdentifier + token: member + element: <testLibrary>::@class::C::@method::member + staticType: C Function() + argumentList: ArgumentList + leftParenthesis: ( + rightParenthesis: ) + isDotShorthand: false + staticInvokeType: C Function() + staticType: C + argumentList: ArgumentList + leftParenthesis: ( + rightParenthesis: ) + element: <testLibrary>::@class::C::@method::call + staticInvokeType: C Function() + staticType: C +'''); + } + + test_functionExpression_call_argument() async { + await assertNoErrorsInCode(r''' +class C { + static C member() => C(); + C call(int a) => this; +} + +void main() { + final C _ = .member()(1); +} +'''); + + var node = findNode.singleFunctionExpressionInvocation; + assertResolvedNodeText(node, r''' +FunctionExpressionInvocation + function: DotShorthandInvocation + period: . + memberName: SimpleIdentifier + token: member + element: <testLibrary>::@class::C::@method::member + staticType: C Function() + argumentList: ArgumentList + leftParenthesis: ( + rightParenthesis: ) + isDotShorthand: false + staticInvokeType: C Function() + staticType: C + argumentList: ArgumentList + leftParenthesis: ( + arguments + IntegerLiteral + literal: 1 + correspondingParameter: <testLibrary>::@class::C::@method::call::@formalParameter::a + staticType: int + rightParenthesis: ) + element: <testLibrary>::@class::C::@method::call + staticInvokeType: C Function(int) + staticType: C +'''); + } + + test_functionExpression_call_extension() async { + await assertNoErrorsInCode(r''' +class C { + static C member() => C(); +} + +extension CallC on C { + C call() => this; +} + +void main() { + final C _ = .member()(); +} +'''); + + var node = findNode.singleFunctionExpressionInvocation; + assertResolvedNodeText(node, r''' +FunctionExpressionInvocation + function: DotShorthandInvocation + period: . + memberName: SimpleIdentifier + token: member + element: <testLibrary>::@class::C::@method::member + staticType: C Function() + argumentList: ArgumentList + leftParenthesis: ( + rightParenthesis: ) + isDotShorthand: false + staticInvokeType: C Function() + staticType: C + argumentList: ArgumentList + leftParenthesis: ( + rightParenthesis: ) + element: <testLibrary>::@extension::CallC::@method::call + staticInvokeType: C Function() + staticType: C +'''); + } + + test_functionExpression_call_generic() async { + await assertNoErrorsInCode(r''' +class C { + static C member() => C(); + C call<T>(T t) => this; +} + +void main() { + final C _ = .member()<int>(1); +} +'''); + + var node = findNode.singleFunctionExpressionInvocation; + assertResolvedNodeText(node, r''' +FunctionExpressionInvocation + function: DotShorthandInvocation + period: . + memberName: SimpleIdentifier + token: member + element: <testLibrary>::@class::C::@method::member + staticType: C Function() + argumentList: ArgumentList + leftParenthesis: ( + rightParenthesis: ) + isDotShorthand: false + staticInvokeType: C Function() + staticType: C + typeArguments: TypeArgumentList + leftBracket: < + arguments + NamedType + name: int + element: dart:core::@class::int + type: int + rightBracket: > + argumentList: ArgumentList + leftParenthesis: ( + arguments + IntegerLiteral + literal: 1 + correspondingParameter: ParameterMember + baseElement: <testLibrary>::@class::C::@method::call::@formalParameter::t + substitution: {T: int} + staticType: int + rightParenthesis: ) + element: <testLibrary>::@class::C::@method::call + staticInvokeType: C Function(int) + staticType: C + typeArgumentTypes + int +'''); + } + + test_functionExpression_call_nested() async { + await assertNoErrorsInCode(r''' +class C { + static C member(C c) => C(); + static C one() => C(); + C call() => this; +} + +void main() { + C _ = .member(.one())(); +} +'''); + + var node = findNode.singleFunctionExpressionInvocation; + assertResolvedNodeText(node, r''' +FunctionExpressionInvocation + function: DotShorthandInvocation + period: . + memberName: SimpleIdentifier + token: member + element: <testLibrary>::@class::C::@method::member + staticType: C Function(C) + argumentList: ArgumentList + leftParenthesis: ( + arguments + DotShorthandInvocation + period: . + memberName: SimpleIdentifier + token: one + element: <testLibrary>::@class::C::@method::one + staticType: C Function() + argumentList: ArgumentList + leftParenthesis: ( + rightParenthesis: ) + isDotShorthand: true + correspondingParameter: <testLibrary>::@class::C::@method::member::@formalParameter::c + staticInvokeType: C Function() + staticType: C + rightParenthesis: ) + isDotShorthand: false + staticInvokeType: C Function(C) + staticType: C + argumentList: ArgumentList + leftParenthesis: ( + rightParenthesis: ) + element: <testLibrary>::@class::C::@method::call + staticInvokeType: C Function() + staticType: C +'''); + } + + test_functionExpression_nested() async { + await assertErrorsInCode( + r''' +class C { + static C member(C c) => C(); + static C one() => C(); +} + +void main() { + C _ = .member(.one())(); +} +''', + [error(diag.invocationOfNonFunctionExpression, 92, 15)], + ); + } + test_futureOr() async { await assertNoErrorsInCode(r''' import 'dart:async';
diff --git a/pkg/analyzer/test/src/dart/resolution/dot_shorthand_property_access_test.dart b/pkg/analyzer/test/src/dart/resolution/dot_shorthand_property_access_test.dart index f9175fb..0c7f1de 100644 --- a/pkg/analyzer/test/src/dart/resolution/dot_shorthand_property_access_test.dart +++ b/pkg/analyzer/test/src/dart/resolution/dot_shorthand_property_access_test.dart
@@ -464,6 +464,256 @@ '''); } + test_functionExpression_call_argument() async { + await assertNoErrorsInCode(r''' +class C { + static final C field = C(); + C call(int a) => this; +} + +void main() { + final C _ = .field(1); +} +'''); + + var node = findNode.singleFunctionExpressionInvocation; + assertResolvedNodeText(node, r''' +FunctionExpressionInvocation + function: DotShorthandPropertyAccess + period: . + propertyName: SimpleIdentifier + token: field + element: <testLibrary>::@class::C::@getter::field + staticType: C + isDotShorthand: false + staticType: C + argumentList: ArgumentList + leftParenthesis: ( + arguments + IntegerLiteral + literal: 1 + correspondingParameter: <testLibrary>::@class::C::@method::call::@formalParameter::a + staticType: int + rightParenthesis: ) + element: <testLibrary>::@class::C::@method::call + staticInvokeType: C Function(int) + staticType: C +'''); + } + + test_functionExpression_call_extension_field() async { + await assertNoErrorsInCode(r''' +class C { + static final C field = C(); +} + +extension CallC on C { + C call() => this; +} + +void main() { + final C _ = .field(); +} +'''); + + var node = findNode.singleFunctionExpressionInvocation; + assertResolvedNodeText(node, r''' +FunctionExpressionInvocation + function: DotShorthandPropertyAccess + period: . + propertyName: SimpleIdentifier + token: field + element: <testLibrary>::@class::C::@getter::field + staticType: C + isDotShorthand: false + staticType: C + argumentList: ArgumentList + leftParenthesis: ( + rightParenthesis: ) + element: <testLibrary>::@extension::CallC::@method::call + staticInvokeType: C Function() + staticType: C +'''); + } + + test_functionExpression_call_extension_getter() async { + await assertNoErrorsInCode(r''' +class C { + static C get getter => C(); +} + +extension CallC on C { + C call() => this; +} + +void main() { + final C _ = .getter(); +} +'''); + + var node = findNode.singleFunctionExpressionInvocation; + assertResolvedNodeText(node, r''' +FunctionExpressionInvocation + function: DotShorthandPropertyAccess + period: . + propertyName: SimpleIdentifier + token: getter + element: <testLibrary>::@class::C::@getter::getter + staticType: C + isDotShorthand: false + staticType: C + argumentList: ArgumentList + leftParenthesis: ( + rightParenthesis: ) + element: <testLibrary>::@extension::CallC::@method::call + staticInvokeType: C Function() + staticType: C +'''); + } + + test_functionExpression_call_field() async { + await assertNoErrorsInCode(r''' +class C { + static final C field = C(); + C call() => this; +} + +void main() { + final C _ = .field(); +} +'''); + + var node = findNode.singleFunctionExpressionInvocation; + assertResolvedNodeText(node, r''' +FunctionExpressionInvocation + function: DotShorthandPropertyAccess + period: . + propertyName: SimpleIdentifier + token: field + element: <testLibrary>::@class::C::@getter::field + staticType: C + isDotShorthand: false + staticType: C + argumentList: ArgumentList + leftParenthesis: ( + rightParenthesis: ) + element: <testLibrary>::@class::C::@method::call + staticInvokeType: C Function() + staticType: C +'''); + } + + test_functionExpression_call_generic() async { + await assertNoErrorsInCode(r''' +class C { + static final C field = C(); + C call<T>(T t) => this; +} + +void main() { + final C _ = .field<int>(1); +} +'''); + + var node = findNode.singleFunctionExpressionInvocation; + assertResolvedNodeText(node, r''' +FunctionExpressionInvocation + function: DotShorthandPropertyAccess + period: . + propertyName: SimpleIdentifier + token: field + element: <testLibrary>::@class::C::@getter::field + staticType: C + isDotShorthand: false + staticType: C + typeArguments: TypeArgumentList + leftBracket: < + arguments + NamedType + name: int + element: dart:core::@class::int + type: int + rightBracket: > + argumentList: ArgumentList + leftParenthesis: ( + arguments + IntegerLiteral + literal: 1 + correspondingParameter: ParameterMember + baseElement: <testLibrary>::@class::C::@method::call::@formalParameter::t + substitution: {T: int} + staticType: int + rightParenthesis: ) + element: <testLibrary>::@class::C::@method::call + staticInvokeType: C Function(int) + staticType: C + typeArgumentTypes + int +'''); + } + + test_functionExpression_call_getter() async { + await assertNoErrorsInCode(r''' +class C { + static C get getter => C(); + C call() => this; +} + +void main() { + final C _ = .getter(); +} +'''); + + var node = findNode.singleFunctionExpressionInvocation; + assertResolvedNodeText(node, r''' +FunctionExpressionInvocation + function: DotShorthandPropertyAccess + period: . + propertyName: SimpleIdentifier + token: getter + element: <testLibrary>::@class::C::@getter::getter + staticType: C + isDotShorthand: false + staticType: C + argumentList: ArgumentList + leftParenthesis: ( + rightParenthesis: ) + element: <testLibrary>::@class::C::@method::call + staticInvokeType: C Function() + staticType: C +'''); + } + + test_functionExpression_field() async { + await assertErrorsInCode( + r''' +class C { + static final C field = C(); +} + +void main() { + final C _ = .field(); +} +''', + [error(diag.invocationOfNonFunctionExpression, 71, 6)], + ); + } + + test_functionExpression_getter() async { + await assertErrorsInCode( + r''' +class C { + static C get getter => C(); +} + +void main() { + final C _ = .getter(); +} +''', + [error(diag.invocationOfNonFunctionExpression, 71, 7)], + ); + } + test_functionReference() async { await assertNoErrorsInCode(r''' class C<T> {
diff --git a/pkg/analyzer_plugin/lib/src/utilities/change_builder/change_builder_dart.dart b/pkg/analyzer_plugin/lib/src/utilities/change_builder/change_builder_dart.dart index 69448af..57d348d 100644 --- a/pkg/analyzer_plugin/lib/src/utilities/change_builder/change_builder_dart.dart +++ b/pkg/analyzer_plugin/lib/src/utilities/change_builder/change_builder_dart.dart
@@ -23,6 +23,7 @@ import 'package:analyzer_plugin/src/utilities/charcodes.dart'; import 'package:analyzer_plugin/src/utilities/directive_sort.dart'; import 'package:analyzer_plugin/src/utilities/extensions/resolved_unit_result.dart'; +import 'package:analyzer_plugin/src/utilities/formatter.dart'; import 'package:analyzer_plugin/src/utilities/library.dart'; import 'package:analyzer_plugin/src/utilities/string_utilities.dart'; import 'package:analyzer_plugin/utilities/change_builder/change_builder_core.dart'; @@ -1895,16 +1896,14 @@ } } - var languageVersion = resolvedUnit.libraryElement.languageVersion.effective; - var formattedResult = DartFormatter(languageVersion: languageVersion) - .formatSource( - SourceCode( - newContent, - isCompilationUnit: true, - selectionStart: newRangeOffset, - selectionLength: newRangeLength, - ), - ); + var formatter = createFormatter(resolvedUnit); + var formattedResult = formatter.formatSource( + SourceCode( + newContent, + selectionStart: newRangeOffset, + selectionLength: newRangeLength, + ), + ); replaceEdits( range,
diff --git a/pkg/analysis_server/lib/src/utilities/extensions/formatter_options.dart b/pkg/analyzer_plugin/lib/src/utilities/extensions/formatter_options.dart similarity index 100% rename from pkg/analysis_server/lib/src/utilities/extensions/formatter_options.dart rename to pkg/analyzer_plugin/lib/src/utilities/extensions/formatter_options.dart
diff --git a/pkg/analyzer_plugin/lib/src/utilities/formatter.dart b/pkg/analyzer_plugin/lib/src/utilities/formatter.dart new file mode 100644 index 0000000..152983c --- /dev/null +++ b/pkg/analyzer_plugin/lib/src/utilities/formatter.dart
@@ -0,0 +1,42 @@ +// Copyright (c) 2026, 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 'package:analyzer/dart/analysis/features.dart'; +import 'package:analyzer/dart/analysis/results.dart'; +import 'package:analyzer/src/dart/analysis/experiments.dart' + show ExperimentStatus; +import 'package:analyzer_plugin/src/utilities/extensions/formatter_options.dart'; +import 'package:dart_style/dart_style.dart'; + +/// A list of all features that are currently enabled by an experiment flag. +final _allowedExperiments = ExperimentStatus.knownFeatures.values + .where((feature) => feature.status == FeatureStatus.future) + .toList(); + +/// Creates a formatter with the appropriate settings for [result]. +DartFormatter createFormatter( + ParsedUnitResult result, { + int? defaultPageWidth, +}) { + var featureSet = result.unit.featureSet; + var formatterOptions = result.analysisOptions.formatterOptions; + var effectivePageWidth = formatterOptions.pageWidth ?? defaultPageWidth; + var effectiveTrailingCommas = formatterOptions.dartStyleTrailingCommas; + var effectiveLanguageVersion = result.unit.languageVersion.effective; + return DartFormatter( + pageWidth: effectivePageWidth, + trailingCommas: effectiveTrailingCommas, + languageVersion: effectiveLanguageVersion, + experimentFlags: _getExperiments(featureSet), + ); +} + +/// Gets the list of experiment strings enabled by [featureSet] that are +/// required for future features. +List<String> _getExperiments(FeatureSet featureSet) { + return _allowedExperiments + .where(featureSet.isEnabled) + .map((feature) => feature.enableString) + .toList(); +}
diff --git a/pkg/analyzer_plugin/test/src/utilities/formatter_test.dart b/pkg/analyzer_plugin/test/src/utilities/formatter_test.dart new file mode 100644 index 0000000..2c6cd462 --- /dev/null +++ b/pkg/analyzer_plugin/test/src/utilities/formatter_test.dart
@@ -0,0 +1,59 @@ +// Copyright (c) 2026, 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 'package:analyzer_plugin/src/utilities/formatter.dart'; +import 'package:analyzer_testing/experiments/experiments.dart'; +import 'package:dart_style/dart_style.dart'; +import 'package:pub_semver/pub_semver.dart'; +import 'package:test/test.dart'; +import 'package:test_reflective_loader/test_reflective_loader.dart'; + +import '../../support/abstract_single_unit.dart'; + +void main() { + defineReflectiveSuite(() { + defineReflectiveTests(FormatterTest); + }); +} + +@reflectiveTest +class FormatterTest extends AbstractSingleUnitTest { + Future<void> test_experiments() async { + await resolveTestCode(''); + var formatter = createFormatter(result); + expect(formatter.experimentFlags, experimentsForTests); + } + + Future<void> test_languageVersion_default() async { + await resolveTestCode(''); + var formatter = createFormatter(result); + expect(formatter.languageVersion, DartFormatter.latestLanguageVersion); + } + + Future<void> test_languageVersion_override() async { + await resolveTestCode('// @dart=2.12'); + var formatter = createFormatter(result); + expect(formatter.languageVersion, Version(2, 12, 0)); + } + + Future<void> test_pageWidth() async { + newFile(convertPath('$testPackageRootPath/analysis_options.yaml'), ''' +formatter: + page_width: 123 +'''); + await resolveTestCode(''); + var formatter = createFormatter(result); + expect(formatter.pageWidth, 123); + } + + Future<void> test_trailingCommas() async { + newFile(convertPath('$testPackageRootPath/analysis_options.yaml'), ''' +formatter: + trailing_commas: preserve +'''); + await resolveTestCode(''); + var formatter = createFormatter(result); + expect(formatter.trailingCommas, TrailingCommas.preserve); + } +}
diff --git a/pkg/analyzer_plugin/test/src/utilities/test_all.dart b/pkg/analyzer_plugin/test/src/utilities/test_all.dart index fb88ce8..49bf1b4 100644 --- a/pkg/analyzer_plugin/test/src/utilities/test_all.dart +++ b/pkg/analyzer_plugin/test/src/utilities/test_all.dart
@@ -7,6 +7,7 @@ import 'change_builder/test_all.dart' as change_builder; import 'client_uri_converter_test.dart' as client_uri_converter; import 'completion/test_all.dart' as completion; +import 'formatter_test.dart' as formatter; import 'navigation/test_all.dart' as navigation; import 'string_utilities_test.dart' as string_utilities; import 'visitors/test_all.dart' as visitors; @@ -16,6 +17,7 @@ change_builder.main(); client_uri_converter.main(); completion.main(); + formatter.main(); navigation.main(); string_utilities.main(); visitors.main();
diff --git a/pkg/dart_runtime_service/lib/dart_runtime_service.dart b/pkg/dart_runtime_service/lib/dart_runtime_service.dart index 4adf99d..5d7cd08 100644 --- a/pkg/dart_runtime_service/lib/dart_runtime_service.dart +++ b/pkg/dart_runtime_service/lib/dart_runtime_service.dart
@@ -6,3 +6,4 @@ export 'src/dart_runtime_service_backend.dart'; export 'src/dart_runtime_service_options.dart'; export 'src/exceptions.dart'; +export 'src/rpc_exceptions.dart';
diff --git a/pkg/dart_runtime_service/lib/src/clients.dart b/pkg/dart_runtime_service/lib/src/clients.dart index 38f9e1f..2e8060a 100644 --- a/pkg/dart_runtime_service/lib/src/clients.dart +++ b/pkg/dart_runtime_service/lib/src/clients.dart
@@ -7,6 +7,8 @@ import 'package:meta/meta.dart'; import 'package:stream_channel/stream_channel.dart'; +import 'dart_runtime_service.dart'; +import 'dart_runtime_service_backend.dart'; import 'dart_runtime_service_rpcs.dart'; import 'event_streams.dart'; import 'rpc_exceptions.dart'; @@ -22,6 +24,7 @@ required StreamChannel<String> connection, required UnmodifiableNamedLookup<Client> clients, required EventStreamMethods eventStreamMethods, + required this.backend, }) { _clientPeer = json_rpc.Peer(connection, strictProtocolChecks: false); _internalRpcs = DartRuntimeServiceRpcs( @@ -35,6 +38,7 @@ late json_rpc.Peer _clientPeer; late final DartRuntimeServiceRpcs _internalRpcs; + final DartRuntimeServiceBackend backend; /// The logger to be used when handling requests from this client. Logger get logger => Logger('Client ($name)'); @@ -66,6 +70,9 @@ @mustCallSuper void registerRpcHandlers() { _internalRpcs.registerRpcsWithPeer(_clientPeer); + backend.registerRpcs(_clientPeer); + _internalRpcs.registerServiceExtensionForwarder(_clientPeer); + backend.registerFallbacks(_clientPeer); } /// Attempts to register a [service] to be provided by this client. @@ -159,10 +166,10 @@ /// /// Call [addClient] when a client connects to your service. base class ClientManager { - ClientManager({required this.eventStreamMethods}); + ClientManager({required this.backend, required this.eventStreamMethods}); static const _kServicePrologue = 's'; - + final DartRuntimeServiceBackend backend; final EventStreamMethods eventStreamMethods; /// The set of [Client]s currently connected to the service. @@ -183,6 +190,7 @@ connection: connection, clients: UnmodifiableNamedLookup(clients), eventStreamMethods: eventStreamMethods, + backend: backend, ); final namespace = clients.add(client); client.initialize(namespace: namespace).then((_) {
diff --git a/pkg/dart_runtime_service/lib/src/dart_runtime_service.dart b/pkg/dart_runtime_service/lib/src/dart_runtime_service.dart index 3ceaff1..754883d 100644 --- a/pkg/dart_runtime_service/lib/src/dart_runtime_service.dart +++ b/pkg/dart_runtime_service/lib/src/dart_runtime_service.dart
@@ -18,6 +18,8 @@ import 'handlers.dart'; import 'utils.dart'; +typedef RpcResponse = Map<String, Object?>; + class DartRuntimeService { DartRuntimeService._({required this.config, required this.backend}) : authCode = config.disableAuthCodes ? null : generateSecret() { @@ -26,12 +28,12 @@ } } - static Future<DartRuntimeService> start({ + static Future<DartRuntimeService> initialize({ required DartRuntimeServiceOptions config, required DartRuntimeServiceBackend backend, }) async { final service = DartRuntimeService._(config: config, backend: backend); - await service._startService(); + await service._initialize(); return service; } @@ -40,10 +42,31 @@ final DartRuntimeServiceBackend backend; /// The ws:// URI pointing to this [DartRuntimeService]'s server. - Uri get uri => _uri!; + /// + /// Throws [DartRuntimeServiceServerNotRunning] if the HTTP server is not + /// active. + Uri get uri { + if (_server == null) { + throw const DartRuntimeServiceServerNotRunning(); + } + return _uri!; + } + Uri? _uri; + /// The http:// URI pointing to this [DartRuntimeService]'s server. + /// + /// Throws [DartRuntimeServiceServerNotRunning] if the HTTP server is not + /// active. + Uri get httpUri => uri.replace(scheme: 'http'); + /// The sse:// URI pointing to this [DartRuntimeService]'s server. + /// + /// Throws [StateError] if [DartRuntimeServiceOptions.sseHandlerPath] is not + /// set. + /// + /// Throws [DartRuntimeServiceServerNotRunning] if the HTTP server is not + /// active. Uri get sseUri { if (config.sseHandlerPath == null) { throw StateError('SSE handler path not configured.'); @@ -63,6 +86,7 @@ @visibleForTesting late final ClientManager clientManager = ClientManager( + backend: backend, eventStreamMethods: eventStreamManager, ); @@ -73,15 +97,42 @@ HttpServer? _server; + /// Initializes the service's state without starting the web server. + Future<void> _initialize() async { + await backend.initialize(); + + if (config.autoStart) { + _logger.info('Autostart enabled. Starting server.'); + await _startServer(); + } + await backend.onServiceReady(this); + } + /// Shuts down the service and cleans up backend state. Future<void> shutdown() async { - await _server?.close(force: true); - await clientManager.shutdown(); + await backend.clearState(); await backend.shutdown(); + await _shutdownServer(); + await clientManager.shutdown(); Logger.root.clearListeners(); } - Future<void> _startService() async { + Future<void> toggleServer() async { + // TODO(bkonyi): verify there's no race conditions + if (_server != null) { + await _shutdownServer(); + } else { + await _startServer(); + } + } + + Future<void> _startServer() async { + if (_server != null) { + _logger.warning( + "Attempted to start the HTTP server, but it's already running.", + ); + throw const DartRuntimeServiceServerAlreadyRunning(); + } // TODO(bkonyi): support IPv6 final host = InternetAddress.loopbackIPv4.host; @@ -120,11 +171,28 @@ port: server.port, path: authCode != null ? '/$authCode' : '', ); + await backend.onServerStarted(httpUri: httpUri, wsUri: uri); _logger.info( - 'Dart Runtime Service started successfully and is listening at $uri.', + 'Dart Runtime Service HTTP server started successfully and is listening ' + 'at $uri.', ); } + Future<void> _shutdownServer() async { + final server = _server; + if (server == null) { + _logger.warning( + "Attempting to shut down the HTTP server, but it's not " + 'running.', + ); + throw const DartRuntimeServiceServerNotRunning(); + } + _logger.info('Dart Runtime Service HTTP server is shutting down.'); + _server = null; + _uri = null; + await server.close(); + } + shelf.Handler _handlers() { _logger.info('Building Shelf handlers.'); var pipeline = const shelf.Pipeline();
diff --git a/pkg/dart_runtime_service/lib/src/dart_runtime_service_backend.dart b/pkg/dart_runtime_service/lib/src/dart_runtime_service_backend.dart index e84aa1d..fc814d3 100644 --- a/pkg/dart_runtime_service/lib/src/dart_runtime_service_backend.dart +++ b/pkg/dart_runtime_service/lib/src/dart_runtime_service_backend.dart
@@ -2,10 +2,50 @@ // 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 'package:json_rpc_2/json_rpc_2.dart' as json_rpc; + import 'dart_runtime_service.dart'; /// A backend implementation of a service used to inject non-common /// functionality into a [DartRuntimeService]. abstract class DartRuntimeServiceBackend { + /// Invoked by the [DartRuntimeService] when the service is initializing, + /// before the service's HTTP server is started. + /// + /// The backend should not expect for this to be invoked more than once. + Future<void> initialize(); + + /// Invoked by the [DartRuntimeService] once it has completely finished + /// initializing. + /// + /// The backend should not expect for this to be invoked more than once. + Future<void> onServiceReady(DartRuntimeService service); + + /// Invoked by the [DartRuntimeService] when the service is shutting down, + /// allowing for the backend to clean up its state. + /// + /// The backend should not expect to be reinitialized after shutting down. Future<void> shutdown(); + + /// Invoked by the [DartRuntimeService] when the service is no longer + /// available, either due to the HTTP server being disabled or the service + /// shutting down. + /// + /// This is always invoked immediately before [shutdown]. + Future<void> clearState(); + + /// Invoked by the [DartRuntimeService] when the service's HTTP server has + /// started. + Future<void> onServerStarted({required Uri httpUri, required Uri wsUri}); + + /// Invoked by the [DartRuntimeService] to register handlers for the RPCs + /// provided by the backend. + void registerRpcs(json_rpc.Peer clientPeer); + + /// Invoked by the [DartRuntimeService] to register fallback handlers + /// provided by the backend. + /// + /// Backend fallbacks are executed after incoming RPC requests fail to match + /// any registered RPCs or service extensions provided by other clients. + void registerFallbacks(json_rpc.Peer clientPeer); }
diff --git a/pkg/dart_runtime_service/lib/src/dart_runtime_service_options.dart b/pkg/dart_runtime_service/lib/src/dart_runtime_service_options.dart index a33a9bf..6325503 100644 --- a/pkg/dart_runtime_service/lib/src/dart_runtime_service_options.dart +++ b/pkg/dart_runtime_service/lib/src/dart_runtime_service_options.dart
@@ -11,6 +11,7 @@ this.port = 0, this.disableAuthCodes = false, this.sseHandlerPath, + this.autoStart = true, }); /// If true, enables log output for the service. @@ -33,17 +34,22 @@ /// Defaults to null. final String? sseHandlerPath; + /// If true, the HTTP server will be started on initialization. + final bool autoStart; + DartRuntimeServiceOptions copyWith({ bool? enableLogging, int? port, bool? disableAuthCodes, String? sseHandlerPath, + bool? autoStart, }) { return DartRuntimeServiceOptions( enableLogging: enableLogging ?? this.enableLogging, port: port ?? this.port, disableAuthCodes: disableAuthCodes ?? this.disableAuthCodes, sseHandlerPath: sseHandlerPath ?? this.sseHandlerPath, + autoStart: autoStart ?? this.autoStart, ); } }
diff --git a/pkg/dart_runtime_service/lib/src/dart_runtime_service_rpcs.dart b/pkg/dart_runtime_service/lib/src/dart_runtime_service_rpcs.dart index 47e0607..ff9066c 100644 --- a/pkg/dart_runtime_service/lib/src/dart_runtime_service_rpcs.dart +++ b/pkg/dart_runtime_service/lib/src/dart_runtime_service_rpcs.dart
@@ -15,7 +15,6 @@ import 'rpc_exceptions.dart'; import 'utils.dart'; -typedef RpcResponse = Map<String, Object?>; typedef RpcHandlerWithNoParameters = FutureOr<RpcResponse> Function(); typedef RpcHandlerWithParameters = FutureOr<RpcResponse> Function(json_rpc.Parameters); @@ -80,7 +79,9 @@ } }); } + } + void registerServiceExtensionForwarder(json_rpc.Peer clientPeer) { clientPeer.registerFallback(serviceExtensionForwarderFallback); }
diff --git a/pkg/dart_runtime_service/lib/src/exceptions.dart b/pkg/dart_runtime_service/lib/src/exceptions.dart index 11c7d60..6b3cc5c 100644 --- a/pkg/dart_runtime_service/lib/src/exceptions.dart +++ b/pkg/dart_runtime_service/lib/src/exceptions.dart
@@ -20,3 +20,19 @@ const DartRuntimeServiceFailedToStartException({required String message}) : super(message: 'Failed to start: $message'); } + +/// Thrown when the [DartRuntimeService] attempts to start the server when it's +/// already active. +final class DartRuntimeServiceServerAlreadyRunning + extends DartRuntimeServiceException { + const DartRuntimeServiceServerAlreadyRunning() + : super(message: 'The HTTP server is already running.'); +} + +/// Thrown when the [DartRuntimeService] attempts to shutdown the server when +/// it's not active. +final class DartRuntimeServiceServerNotRunning + extends DartRuntimeServiceException { + const DartRuntimeServiceServerNotRunning() + : super(message: 'The HTTP server is not running.'); +}
diff --git a/pkg/dart_runtime_service/lib/src/rpc_exceptions.dart b/pkg/dart_runtime_service/lib/src/rpc_exceptions.dart index 09d1099..05252cb 100644 --- a/pkg/dart_runtime_service/lib/src/rpc_exceptions.dart +++ b/pkg/dart_runtime_service/lib/src/rpc_exceptions.dart
@@ -8,8 +8,9 @@ enum RpcException { // These error codes must be kept in sync with those in vm/json_stream.h and // vmservice.dart. - serverError(code: SERVER_ERROR, message: 'Server error'), - methodNotFound(code: METHOD_NOT_FOUND, message: 'Method not found'), + serverError(code: SERVER_ERROR, message: 'Server error.'), + methodNotFound(code: METHOD_NOT_FOUND, message: 'Method not found.'), + internalError(code: INTERNAL_ERROR, message: 'Internal error.'), connectionDisposed(code: -32010, message: 'Service connection disposed.'), featureDisabled(code: 100, message: 'Feature is disabled.'), streamAlreadySubscribed(code: 103, message: 'Stream already subscribed.'),
diff --git a/pkg/dart_runtime_service/test/utils/mocks.dart b/pkg/dart_runtime_service/test/utils/mocks.dart index b098f0e..b55dc26 100644 --- a/pkg/dart_runtime_service/test/utils/mocks.dart +++ b/pkg/dart_runtime_service/test/utils/mocks.dart
@@ -3,6 +3,7 @@ // BSD-style license that can be found in the LICENSE file. import 'package:dart_runtime_service/dart_runtime_service.dart'; +import 'package:json_rpc_2/json_rpc_2.dart' as json_rpc; import 'package:test/fake.dart'; /// Fake implementation of [DartRuntimeServiceBackend] that throws when @@ -13,5 +14,26 @@ base class FakeDartRuntimeServiceBackend extends Fake implements DartRuntimeServiceBackend { @override + Future<void> initialize() async {} + + @override + Future<void> onServiceReady(DartRuntimeService service) async {} + + @override Future<void> shutdown() async {} + + @override + Future<void> clearState() async {} + + @override + Future<void> onServerStarted({ + required Uri httpUri, + required Uri wsUri, + }) async {} + + @override + void registerRpcs(json_rpc.Peer clientPeer) {} + + @override + void registerFallbacks(json_rpc.Peer clientPeer) {} }
diff --git a/pkg/dart_runtime_service/test/utils/utilities.dart b/pkg/dart_runtime_service/test/utils/utilities.dart index 6366f16..7f25c23 100644 --- a/pkg/dart_runtime_service/test/utils/utilities.dart +++ b/pkg/dart_runtime_service/test/utils/utilities.dart
@@ -22,7 +22,7 @@ DartRuntimeService? service; addTearDown(() async => await service?.shutdown()); - service = await DartRuntimeService.start( + service = await DartRuntimeService.initialize( config: config, backend: FakeDartRuntimeServiceBackend(), );
diff --git a/pkg/dart_runtime_service_vm/OWNERS b/pkg/dart_runtime_service_vm/OWNERS new file mode 100644 index 0000000..104bde4 --- /dev/null +++ b/pkg/dart_runtime_service_vm/OWNERS
@@ -0,0 +1 @@ +file:/tools/OWNERS_DEV_INFRA \ No newline at end of file
diff --git a/pkg/dart_runtime_service_vm/analysis_options.yaml b/pkg/dart_runtime_service_vm/analysis_options.yaml new file mode 100644 index 0000000..d5248da --- /dev/null +++ b/pkg/dart_runtime_service_vm/analysis_options.yaml
@@ -0,0 +1 @@ +include: ../dart_runtime_service/analysis_options.yaml
diff --git a/pkg/dart_runtime_service_vm/bin/vm_service_entrypoint.dart b/pkg/dart_runtime_service_vm/bin/vm_service_entrypoint.dart new file mode 100644 index 0000000..2afe4c9 --- /dev/null +++ b/pkg/dart_runtime_service_vm/bin/vm_service_entrypoint.dart
@@ -0,0 +1,113 @@ +// Copyright (c) 2026, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'dart:async'; +import 'dart:io'; + +import 'package:dart_runtime_service/dart_runtime_service.dart'; +import 'package:dart_runtime_service_vm/dart_runtime_service_vm.dart'; + +// ignore: unreachable_from_main +const entrypoint = pragma( + 'vm:entry-point', + !bool.fromEnvironment('dart.vm.product'), +); + +// The TCP IP that DDS listens on. +@entrypoint +// ignore: unused_element +String _ddsIP = ''; + +// The TCP port that DDS listens on. +@entrypoint +// ignore: unused_element +int _ddsPort = 0; + +// The TCP port that the HTTP server listens on. +@entrypoint +int _port = 0; + +// The TCP IP that the HTTP server listens on. +@entrypoint +// ignore: unused_element +String _ip = ''; + +// Should the HTTP server auto start? +@entrypoint +bool _autoStart = false; + +// Should the HTTP server require an auth code? +@entrypoint +bool _authCodesDisabled = false; + +// Should the HTTP server run in devmode? +@entrypoint +// ignore: unused_element +bool _originCheckDisabled = false; + +// Location of file to output VM service connection info. +@entrypoint +// ignore: unused_element +String? _serviceInfoFilename; + +@entrypoint +// ignore: unused_element +bool _isWindows = false; + +@entrypoint +// ignore: unused_element +bool _isFuchsia = false; + +@entrypoint +Stream<ProcessSignal> Function(ProcessSignal signal)? _signalWatch; + +@entrypoint +// ignore: unused_element +StreamSubscription<ProcessSignal>? _signalSubscription; + +@entrypoint +// ignore: unused_element +bool _serveDevtools = true; + +@entrypoint +// ignore: unused_element +bool _enableServicePortFallback = false; + +@entrypoint +// ignore: unused_element +bool _waitForDdsToAdvertiseService = false; + +@entrypoint +// ignore: unused_element +bool _printDtd = false; + +// ignore: unused_element +File? _residentCompilerInfoFile; + +@entrypoint +// ignore: unused_element +void _populateResidentCompilerInfoFile( + /// If either `--resident-compiler-info-file` or `--resident-server-info-file` + /// was supplied on the command line, the CLI argument should be forwarded as + /// the argument to this parameter. If neither option was supplied, the + /// argument to this parameter should be null. + String? residentCompilerInfoFilePathArgumentFromCli, +) { + // TODO(bkonyi): implement +} + +Future<void> main([List<String> args = const []]) async { + if (args case ['--help']) { + return; + } + await DartRuntimeService.initialize( + config: DartRuntimeServiceOptions( + enableLogging: true, + port: _port, + disableAuthCodes: _authCodesDisabled, + autoStart: _autoStart, + ), + backend: DartRuntimeServiceVMBackend(signalWatch: _signalWatch!), + ); +}
diff --git a/pkg/dart_runtime_service_vm/lib/dart_runtime_service_vm.dart b/pkg/dart_runtime_service_vm/lib/dart_runtime_service_vm.dart new file mode 100644 index 0000000..0404404 --- /dev/null +++ b/pkg/dart_runtime_service_vm/lib/dart_runtime_service_vm.dart
@@ -0,0 +1,93 @@ +// Copyright (c) 2026, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'dart:async'; +import 'dart:io'; + +import 'package:dart_runtime_service/dart_runtime_service.dart'; + +import 'package:json_rpc_2/json_rpc_2.dart' as json_rpc; +import 'package:logging/logging.dart'; + +import 'src/native_bindings.dart'; + +class DartRuntimeServiceVMBackend extends DartRuntimeServiceBackend { + /// The backend implementation for the Dart VM Service. + /// + /// [signalWatch] is the internal implementation of [ProcessSignal.watch], + /// which bypasses checks that prevent [ProcessSignal.sigquit] from being + /// watched. + DartRuntimeServiceVMBackend({required this.signalWatch}); + + /// The internal implementation of [ProcessSignal.watch]. + final Stream<ProcessSignal> Function(ProcessSignal signal) signalWatch; + + final _nativeBindings = NativeBindings(); + final _logger = Logger('VM Backend'); + + StreamSubscription<ProcessSignal>? _sigquitSubscription; + + @override + Future<void> initialize() async { + _logger.info('Initializing...'); + _nativeBindings.onStart(); + _logger.info('Initialized!'); + } + + @override + Future<void> onServiceReady(DartRuntimeService service) async { + // SIGQUIT isn't supported on Fuchsia or Windows. + if (Platform.isFuchsia || Platform.isWindows) { + return; + } + _sigquitSubscription = signalWatch(ProcessSignal.sigquit).listen((_) { + _logger.info('SIGQUIT received. Toggling VM Service HTTP server.'); + service.toggleServer(); + }); + } + + @override + Future<void> onServerStarted({ + required Uri httpUri, + required Uri wsUri, + }) async { + // TODO(bkonyi): handle DDS connection case. + stdout.writeln('The Dart VM service is listening on $httpUri'); + _nativeBindings.onServerAddressChange(httpUri.toString()); + } + + @override + Future<void> clearState() async { + // Do nothing for now. + } + + @override + Future<void> shutdown() async { + await _sigquitSubscription?.cancel(); + _nativeBindings.onExit(); + } + + @override + void registerRpcs(json_rpc.Peer clientPeer) { + // The VM service handles its service requests in service.cc. + } + + @override + void registerFallbacks(json_rpc.Peer clientPeer) { + // If the registered Dart RPC handlers can't handle a request, forward it + // it to the native VM service implementation for processing. + clientPeer.registerFallback(sendToRuntime); + } + + /// Sends service requests to the Dart VM runtime for processing. + Future<RpcResponse> sendToRuntime(json_rpc.Parameters request) async { + final method = request.method; + final params = request.asMap.cast<String, Object?>(); + if (params case {'isolateId': final String _}) { + // TODO(bkonyi): handle isolate requests + RpcException.serverError.throwException(); + } + return await _nativeBindings.sendToVM(method: method, params: params); + } +}
diff --git a/pkg/dart_runtime_service_vm/lib/src/native_bindings.dart b/pkg/dart_runtime_service_vm/lib/src/native_bindings.dart new file mode 100644 index 0000000..ee400b3 --- /dev/null +++ b/pkg/dart_runtime_service_vm/lib/src/native_bindings.dart
@@ -0,0 +1,148 @@ +// Copyright (c) 2026, 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 is a special situation where we're allowed to import dart:_vmservice +// from outside the core libraries to access the native entrypoints. +// +// See VmTarget in package:vm for the exception for this library to access +// dart:_vmservice. +// ignore: uri_does_not_exist +import 'dart:_vmservice' as vm_service_natives; +import 'dart:async'; +import 'dart:convert'; +import 'dart:isolate'; +import 'dart:typed_data'; + +import 'package:dart_runtime_service/dart_runtime_service.dart'; +import 'package:json_rpc_2/json_rpc_2.dart' as json_rpc; + +/// Allows for sending messages to the native VM service implementation. +class NativeBindings { + static final jsonUtf8Decoder = json.fuse(utf8); + + /// Sends a general RPC to the VM for processing. + /// + /// The RPC is not executed in the scope of any particular isolate. + Future<RpcResponse> sendToVM({ + required String method, + required Map<String, Object?> params, + }) { + final receivePort = RawReceivePort(null, 'VM Message'); + final completer = Completer<RpcResponse>(); + receivePort.handler = (Object value) { + receivePort.close(); + try { + completer.complete(_toResponse(value: value)); + } on json_rpc.RpcException catch (e) { + completer.completeError(e); + } + }; + vm_service_natives.sendRootServiceMessage( + _toRequest(responsePort: receivePort, method: method, params: params), + ); + return completer.future; + } + + /// Notifies the VM that the VM service server has finished initializing. + void onStart() => vm_service_natives.onStart(); + + /// Notifies the VM that the VM service server has finished exiting. + void onExit() => vm_service_natives.onExit(); + + /// Notifies the VM that the VM service server address has been updated. + /// + /// If [address] is null, the VM will assume the server is not running. + void onServerAddressChange(String? address) => + vm_service_natives.onServerAddressChange(address); + + RpcResponse _toResponse({required Object value}) { + const kResult = 'result'; + const kError = 'error'; + const kCode = 'code'; + const kMessage = 'message'; + const kData = 'data'; + + final Object? converted; + if (value case [final Uint8List utf8String]) { + converted = jsonUtf8Decoder.decode(utf8String); + } else { + RpcException.internalError.throwException(); + } + if (converted case {kResult: final Map<String, Object?> result}) { + return result; + } else if (converted case { + kError: {kCode: final int code, kMessage: final String message}, + }) { + final data = converted[kData]; + throw json_rpc.RpcException(code, message, data: data); + } else { + RpcException.internalError.throwException(); + } + } + + // Calls toString on all non-String elements of [list]. We do this so all + // elements in the list are strings, making consumption by C++ simpler. + // This has a side effect that boolean literal values like true become 'true' + // and thus indistinguishable from the string literal 'true'. + static void _convertAllToStringInPlace(List<Object?> list) { + for (var i = 0; i < list.length; i++) { + list[i] = list[i].toString(); + } + } + + List<Object?> _toRequest({ + required RawReceivePort responsePort, + required String method, + required Map<String, Object?> params, + }) { + final parametersAreObjects = _methodNeedsObjectParameters(method); + final keys = params.keys.toList(growable: false); + final values = params.values.cast<Object?>().toList(growable: false); + if (!parametersAreObjects) { + _convertAllToStringInPlace(values); + } + + // This is the request ID that will be inserted into the JSON response in + // service.cc. package:json_rpc_2 already handles these IDs, so we just + // pass in a placeholder for now until we can update Service::InvokeMethod + // to not expect it. + // TODO(bkonyi): remove request ID from service message. + const kPlaceholderRequestId = -1; + + // Keep in sync with Service::InvokeMethod in service.cc. + return List<Object?>.filled(7, null) + ..[0] = + 0 // Make room for OOB message type. + ..[1] = responsePort.sendPort + ..[2] = kPlaceholderRequestId + ..[3] = method + ..[4] = parametersAreObjects + ..[5] = keys + ..[6] = values; + } + + // We currently support two ways of passing parameters from Dart code to C + // code. The original way always converts the parameters to strings before + // passing them over. Our goal is to convert all C handlers to take the + // parameters as Dart objects but until the conversion is complete, we + // maintain the list of supported methods below. + bool _methodNeedsObjectParameters(String method) { + switch (method) { + case '_listDevFS': + case '_listDevFSFiles': + case '_createDevFS': + case '_deleteDevFS': + case '_writeDevFSFile': + case '_writeDevFSFiles': + case '_readDevFSFile': + case '_spawnUri': + case '_reloadKernel': + case '_reloadSources': + case 'reloadSources': + return true; + default: + return false; + } + } +}
diff --git a/pkg/dart_runtime_service_vm/pubspec.yaml b/pkg/dart_runtime_service_vm/pubspec.yaml new file mode 100644 index 0000000..0f7be90 --- /dev/null +++ b/pkg/dart_runtime_service_vm/pubspec.yaml
@@ -0,0 +1,14 @@ +name: dart_runtime_service_vm +# This package is not intended for consumption on pub.dev. DO NOT publish. +publish_to: none + +environment: + sdk: ^3.8.0 + +resolution: workspace + +# Use 'any' constraints here; we get our versions from the DEPS file. +dependencies: + dart_runtime_service: any + json_rpc_2: any + logging: any
diff --git a/pkg/dtd_impl/bin/dtd.dart b/pkg/dtd_impl/bin/dtd.dart index a8a3f0d..1a26bc9 100644 --- a/pkg/dtd_impl/bin/dtd.dart +++ b/pkg/dtd_impl/bin/dtd.dart
@@ -12,7 +12,7 @@ /// When [port] is non-null, the [DartToolingDaemon.startService] method will /// send information about the DTD connection back over [port] instead of /// printing it to stdout. -void main(List<String> args, dynamic port) async { +void main(List<String> args, Object? port) async { await DartToolingDaemon.startService( args, sendPort: port as SendPort?,
diff --git a/pkg/dtd_impl/lib/src/dtd_client.dart b/pkg/dtd_impl/lib/src/dtd_client.dart index 602348e..c44f517 100644 --- a/pkg/dtd_impl/lib/src/dtd_client.dart +++ b/pkg/dtd_impl/lib/src/dtd_client.dart
@@ -59,15 +59,18 @@ Future<void> close() => _clientPeer.close(); @override - Future<dynamic> sendRequest({ + Future<Object?> sendRequest({ required String method, - dynamic parameters, + Object? parameters, }) async { if (_clientPeer.isClosed) { - return; + return null; } - return await _clientPeer.sendRequest(method, parameters.value); + if (parameters == null) { + return await _clientPeer.sendRequest(method); + } + return await _clientPeer.sendRequest(method, parameters); } @override @@ -399,7 +402,7 @@ return await client.sendRequest( method: combinedName, - parameters: parameters, + parameters: parameters.value, ); }
diff --git a/pkg/dtd_impl/test/dtd_test.dart b/pkg/dtd_impl/test/dtd_test.dart index cb8caa2..682dfdf 100644 --- a/pkg/dtd_impl/test/dtd_test.dart +++ b/pkg/dtd_impl/test/dtd_test.dart
@@ -404,8 +404,7 @@ await client.close(); // TODO: replace this polling when notification streams are implemented. - - dynamic client2RegisterResult; + Object? client2RegisterResult; for (var i = 0; i < 10; i++) { try { // The service method registration should succeed once the other
diff --git a/pkg/linter/messages.yaml b/pkg/linter/messages.yaml index f9c491c..fb1b205 100644 --- a/pkg/linter/messages.yaml +++ b/pkg/linter/messages.yaml
@@ -13663,6 +13663,58 @@ stable: "2.19" categories: [brevity] hasPublishedDocs: false + documentation: |- + #### Description + + The analyzer produces this diagnostic when a `library` directive + doesn't have a documentation comment or any annotations. + + If no documentation comment or annotation precedes the library directive, + then either it's unnecessary or the documentation comment and/or + annotations are misplaced and should be moved. + + #### Examples + + The following code produces this diagnostic because the `library` + directive doesn't have a documentation comment or annotation: + + ```dart + [!library;!] + + class C {} + ``` + + The following code produces this diagnostic because the library's + documentation comment is below the `library` directive: + + ```dart + [!library;!] + + /// This library provides the [C] class. + + /// The [C] class. + class C {} + ``` + + #### Common fixes + + If you don't want to add a documentation comment or annotation + to the library, then remove the `library` directive: + + ```dart + class C {} + ``` + + If you intend to add a documentation comment or annotation, + then add or move it to the `library` directive: + + ```dart + /// This library provides the [C] class. + library; + + /// The [C] class. + class C {} + ``` deprecatedDetails: |- **DO** use library directives if you want to document a library and/or annotate a library.
diff --git a/pkg/vm/lib/modular/target/vm.dart b/pkg/vm/lib/modular/target/vm.dart index 5e85637..3c9a4bf0 100644 --- a/pkg/vm/lib/modular/target/vm.dart +++ b/pkg/vm/lib/modular/target/vm.dart
@@ -442,7 +442,9 @@ importer.path.contains('runtime/tests/vm/dart') || importer.path.contains('tests/standalone/io') || importer.path.contains('test-lib') || - importer.path.contains('tests/ffi'); + importer.path.contains('tests/ffi') || + (importer.path == 'dart_runtime_service_vm/src/native_bindings.dart' && + imported.path == '_vmservice'); @override Component configureComponent(Component component) {
diff --git a/pubspec.yaml b/pubspec.yaml index cbb9143..43b6e92 100644 --- a/pubspec.yaml +++ b/pubspec.yaml
@@ -39,6 +39,7 @@ - pkg/dart_data_home - pkg/dart_internal - pkg/dart_runtime_service + - pkg/dart_runtime_service_vm - pkg/dart_service_protocol_shared - pkg/dds - pkg/dds_service_extensions
diff --git a/runtime/BUILD.gn b/runtime/BUILD.gn index 7f46c4b..ea85616 100644 --- a/runtime/BUILD.gn +++ b/runtime/BUILD.gn
@@ -245,6 +245,10 @@ defines += [ "DART_DYNAMIC_MODULES" ] } + if (include_experimental_vm_service) { + defines += [ "EXPERIMENTAL_VM_SERVICE" ] + } + if (is_fuchsia) { lib_dirs = [ "${fuchsia_arch_root}/lib" ]
diff --git a/runtime/bin/BUILD.gn b/runtime/bin/BUILD.gn index c73d77d..a1a7bda 100644 --- a/runtime/bin/BUILD.gn +++ b/runtime/bin/BUILD.gn
@@ -886,7 +886,6 @@ ":crashpad", ":icudtl_cc", "//third_party/boringssl", - "//third_party/icu:icui18n", "//third_party/icu:icuuc", "//third_party/zlib", ]
diff --git a/runtime/bin/dartdev.cc b/runtime/bin/dartdev.cc index 42938f6..9829558 100644 --- a/runtime/bin/dartdev.cc +++ b/runtime/bin/dartdev.cc
@@ -1065,24 +1065,12 @@ Loader::InitOnce(); - // Setup script_name to point to the dartdev AOT snapshot. - auto dartdev_path = DartDev::ResolvedSnapshotPath(); - char* script_name = dartdev_path.get(); - if (script_name == nullptr || !CheckForInvalidPath(script_name)) { - Syslog::PrintErr("Unable to find AOT snapshot for dartdev\n"); + auto [app_snapshot, script_name] = + Snapshot::TryReadSDKSnapshot("dartdev_aot.dart.snapshot"); + if (app_snapshot == nullptr) { FreeConvertedArgs(argc, argv, argv_converted); Platform::Exit(kErrorExitCode); } - AppSnapshot* app_snapshot = Snapshot::TryReadAppSnapshot( - script_name, /*force_load_from_memory*/ false, /*decode_uri*/ false); - if (app_snapshot == nullptr || !app_snapshot->IsAOT()) { - Syslog::PrintErr("%s is not an AOT snapshot\n", script_name); - FreeConvertedArgs(argc, argv, argv_converted); - if (app_snapshot != nullptr) { - delete app_snapshot; - } - Platform::Exit(kErrorExitCode); - } app_snapshot->SetBuffers( &ignore_vm_snapshot_data, &ignore_vm_snapshot_instructions, &app_isolate_snapshot_data, &app_isolate_snapshot_instructions); @@ -1152,7 +1140,7 @@ // - Exit the process due to some command parsing errors // - Run the Dart script in a JIT mode by execing the JIT runtime // - Run the Dart AOT snapshot by creating a new Isolate - DartDev::RunDartDev(script_name, &dart_vm_options, &dart_options); + DartDev::RunDartDev(script_name.get(), &dart_vm_options, &dart_options); // Terminate process exit-code handler. Process::TerminateExitCodeHandler();
diff --git a/runtime/bin/main_impl.cc b/runtime/bin/main_impl.cc index 966f527..472dc1c 100644 --- a/runtime/bin/main_impl.cc +++ b/runtime/bin/main_impl.cc
@@ -532,8 +532,6 @@ #endif // !defined(EXCLUDE_CFE_AND_KERNEL_PLATFORM) // Returns newly created Service Isolate on success, nullptr on failure. -// For now we only support the service isolate coming up from sources -// which are compiled by the VM parser. static Dart_Isolate CreateAndSetupServiceIsolate(const char* script_uri, const char* packages_config, Dart_IsolateFlags* flags, @@ -547,30 +545,48 @@ packages_config, nullptr, false); ASSERT(flags != nullptr); + const uint8_t* isolate_snapshot_data = nullptr; + const uint8_t* isolate_snapshot_instructions = nullptr; + +#if defined(EXPERIMENTAL_VM_SERVICE) + if (Options::experimental_vm_service()) { + VmService::enable_experimental_vm_service = true; + auto [app_snapshot, script_name] = Snapshot::TryReadSDKSnapshot( +#if defined(DART_PRECOMIPLED_RUNTIME) + "dart_runtime_service_vm_aot.dart.snapshot"); +#else + "dart_runtime_service_vm.dart.snapshot"); +#endif // defined(DART_PRECOMPILED_RUNTIME) + if (app_snapshot == nullptr) { + Platform::Exit(kErrorExitCode); + } + const uint8_t* ignore_vm_snapshot_data; + const uint8_t* ignore_vm_snapshot_instructions; + app_snapshot->SetBuffers( + &ignore_vm_snapshot_data, &ignore_vm_snapshot_instructions, + &isolate_snapshot_data, &isolate_snapshot_instructions); + } else { +#endif // defined(EXPERIMENTAL_VM_SERVICE) #if defined(DART_PRECOMPILED_RUNTIME) - // AOT: The service isolate is included in any AOT snapshot in non-PRODUCT - // mode - so we launch the vm-service from the main app AOT snapshot. - const uint8_t* isolate_snapshot_data = app_isolate_snapshot_data; - const uint8_t* isolate_snapshot_instructions = - app_isolate_snapshot_instructions; - isolate = Dart_CreateIsolateGroup( - script_uri, DART_VM_SERVICE_ISOLATE_NAME, isolate_snapshot_data, - isolate_snapshot_instructions, flags, isolate_group_data, - /*isolate_data=*/nullptr, error); + // AOT: The service isolate is included in any AOT snapshot in non-PRODUCT + // mode - so we launch the vm-service from the main app AOT snapshot. + isolate_snapshot_data = app_isolate_snapshot_data; + isolate_snapshot_instructions = app_isolate_snapshot_instructions; #else // JIT: Service isolate uses the core libraries snapshot. - // Set flag to load and retain the vmservice library. flags->load_vmservice_library = true; flags->null_safety = true; // Service isolate runs in sound null safe mode. - const uint8_t* isolate_snapshot_data = core_isolate_snapshot_data; - const uint8_t* isolate_snapshot_instructions = - core_isolate_snapshot_instructions; + isolate_snapshot_data = core_isolate_snapshot_data; + isolate_snapshot_instructions = core_isolate_snapshot_instructions; +#endif // defined(DART_PRECOMPILED_RUNTIME) +#if defined(EXPERIMENTAL_VM_SERVICE) + } +#endif // defined(EXPERIMENTAL_VM_SERVICE) isolate = Dart_CreateIsolateGroup( script_uri, DART_VM_SERVICE_ISOLATE_NAME, isolate_snapshot_data, isolate_snapshot_instructions, flags, isolate_group_data, /*isolate_data=*/nullptr, error); -#endif // !defined(DART_PRECOMPILED_RUNTIME) if (isolate == nullptr) { delete isolate_group_data; return nullptr;
diff --git a/runtime/bin/main_options.h b/runtime/bin/main_options.h index 7210550..0c093ee 100644 --- a/runtime/bin/main_options.h +++ b/runtime/bin/main_options.h
@@ -62,7 +62,8 @@ V(profile_microtasks, profile_microtasks) \ /* The purpose of this flag is documented in */ \ /* pkg/dartdev/lib/src/commands/run.dart. */ \ - V(resident, resident) + V(resident, resident) \ + V(experimental_vm_service, experimental_vm_service) // Boolean flags that have a short form. #define SHORT_BOOL_OPTIONS_LIST(V) \
diff --git a/runtime/bin/snapshot_utils.cc b/runtime/bin/snapshot_utils.cc index e5e2e1c..d48aa11 100644 --- a/runtime/bin/snapshot_utils.cc +++ b/runtime/bin/snapshot_utils.cc
@@ -11,6 +11,7 @@ #include "bin/dfe.h" #include "bin/elf_loader.h" #include "bin/error_exit.h" +#include "bin/exe_utils.h" #include "bin/file.h" #include "bin/macho_loader.h" #include "bin/platform.h" @@ -846,6 +847,67 @@ } #endif +// TODO(bkonyi): dedup +static bool CheckForInvalidPath(const char* path) { + // TODO(zichangguo): "\\?\" is a prefix for paths on Windows. + // Arguments passed are parsed as an URI. "\\?\" causes problems as a part + // of URIs. This is a temporary workaround to prevent VM from crashing. + // Issue: https://github.com/dart-lang/sdk/issues/42779 + if (strncmp(path, R"(\\?\)", 4) == 0) { + Syslog::PrintErr(R"(\\?\ prefix is not supported)"); + return false; + } + return true; +} + +std::pair<AppSnapshot*, CStringUniquePtr> Snapshot::TryReadSDKSnapshot( + const char* snapshot_name) { + auto try_resolve_path = [&](CStringUniquePtr dir_prefix) { + // |dir_prefix| includes the last path separator. + // First assume we're in dart-sdk/bin. + char* snapshot_path = + Utils::SCreate("%ssnapshots/%s", dir_prefix.get(), snapshot_name); + if (File::Exists(nullptr, snapshot_path)) { + return CStringUniquePtr(snapshot_path); + } + free(snapshot_path); + + // If we're not in dart-sdk/bin, we might be in one of the $SDK/out*/ + // directories, Try to use a snapshot from that directory. + snapshot_path = Utils::SCreate("%s%s", dir_prefix.get(), snapshot_name); + if (File::Exists(nullptr, snapshot_path)) { + return CStringUniquePtr(snapshot_path); + } + free(snapshot_path); + return CStringUniquePtr(nullptr); + }; + + auto script_path = + try_resolve_path(EXEUtils::GetDirectoryPrefixFromResolvedExeName()); + if (script_path == nullptr) { + script_path = + try_resolve_path(EXEUtils::GetDirectoryPrefixFromUnresolvedExeName()); + } + if (script_path == nullptr || !CheckForInvalidPath(script_path.get())) { + Syslog::PrintErr("Unable to find snapshot: %s\n", snapshot_name); + return std::make_pair(static_cast<AppSnapshot*>(nullptr), + std::move(script_path)); + } + + AppSnapshot* app_snapshot = + TryReadAppSnapshot(script_path.get(), /*force_load_from_memory*/ false, + /*decode_uri*/ false); + if (app_snapshot == nullptr) { + Syslog::PrintErr("%s is not a valid snapshot\n", script_path.get()); + if (app_snapshot != nullptr) { + delete app_snapshot; + } + return std::make_pair(static_cast<AppSnapshot*>(nullptr), + std::move(script_path)); + } + return std::make_pair(app_snapshot, std::move(script_path)); +} + static bool WriteInt64(File* file, int64_t size) { return file->WriteFully(&size, sizeof(size)); }
diff --git a/runtime/bin/snapshot_utils.h b/runtime/bin/snapshot_utils.h index 3103d12..a68d152ee 100644 --- a/runtime/bin/snapshot_utils.h +++ b/runtime/bin/snapshot_utils.h
@@ -5,6 +5,8 @@ #ifndef RUNTIME_BIN_SNAPSHOT_UTILS_H_ #define RUNTIME_BIN_SNAPSHOT_UTILS_H_ +#include <utility> + #include "bin/dartutils.h" #include "platform/globals.h" @@ -59,6 +61,8 @@ static AppSnapshot* TryReadAppSnapshot(const char* script_uri, bool force_load_from_memory = false, bool decode_uri = true); + static std::pair<AppSnapshot*, CStringUniquePtr> TryReadSDKSnapshot( + const char* snapshot_name); static void WriteAppSnapshot(const char* filename, uint8_t* isolate_data_buffer, intptr_t isolate_data_size,
diff --git a/runtime/bin/vmservice_impl.cc b/runtime/bin/vmservice_impl.cc index 4220875..a99635a 100644 --- a/runtime/bin/vmservice_impl.cc +++ b/runtime/bin/vmservice_impl.cc
@@ -20,6 +20,8 @@ #if !defined(PRODUCT) +bool VmService::enable_experimental_vm_service = false; + #define RETURN_ERROR_HANDLE(handle) \ if (Dart_IsError(handle)) { \ return handle; \ @@ -33,9 +35,6 @@ return false; \ } -static constexpr const char* kVMServiceIOLibraryUri = "dart:vmservice_io"; -static constexpr const char* DEFAULT_VM_SERVICE_SERVER_IP = "localhost"; - void NotifyServerState(Dart_NativeArguments args) { Dart_EnterScope(); const char* uri_chars; @@ -66,6 +65,8 @@ }; static VmServiceIONativeEntry _VmServiceIONativeEntries[] = { + // TODO(bkonyi): these aren't used by any known embedders and can be + // removed. {"VMServiceIO_NotifyServerState", 1, NotifyServerState}, {"VMServiceIO_Shutdown", 0, Shutdown}, }; @@ -105,7 +106,12 @@ const char* VmService::error_msg_ = nullptr; char VmService::server_uri_[kServerUriStringBufferSize]; +static constexpr const char* kVMServiceIOLibraryUri = "dart:vmservice_io"; + void VmService::SetNativeResolver() { + if (enable_experimental_vm_service) { + return; + } Dart_Handle url = DartUtils::NewString(kVMServiceIOLibraryUri); Dart_Handle library = Dart_LookupLibrary(url); if (!Dart_IsError(library)) { @@ -114,6 +120,8 @@ } } +static constexpr const char* DEFAULT_VM_SERVICE_SERVER_IP = "localhost"; + bool VmService::Setup(const char* server_ip, intptr_t server_port, bool dev_mode_server, @@ -140,14 +148,16 @@ /*flag_profile_microtasks=*/false, DartIoSettings{}); SHUTDOWN_ON_ERROR(result); - Dart_Handle url = DartUtils::NewString(kVMServiceIOLibraryUri); - Dart_Handle library = Dart_LookupLibrary(url); - SHUTDOWN_ON_ERROR(library); - result = Dart_SetRootLibrary(library); - SHUTDOWN_ON_ERROR(library); - result = Dart_SetNativeResolver(library, VmServiceIONativeResolver, - VmServiceIONativeSymbol); - SHUTDOWN_ON_ERROR(result); + if (!enable_experimental_vm_service) { + Dart_Handle url = DartUtils::NewString(kVMServiceIOLibraryUri); + Dart_Handle library = Dart_LookupLibrary(url); + SHUTDOWN_ON_ERROR(library); + result = Dart_SetRootLibrary(library); + SHUTDOWN_ON_ERROR(library); + result = Dart_SetNativeResolver(library, VmServiceIONativeResolver, + VmServiceIONativeSymbol); + SHUTDOWN_ON_ERROR(result); + } // Make runnable. Dart_ExitScope(); @@ -161,7 +171,7 @@ Dart_EnterIsolate(isolate); Dart_EnterScope(); - library = Dart_RootLibrary(); + Dart_Handle library = Dart_RootLibrary(); SHUTDOWN_ON_ERROR(library); // Set HTTP server state. @@ -172,6 +182,12 @@ // port when the HTTP server is started. server_port = 0; } +#if defined(EXPERIMENTAL_VM_SERVICE) + if (enable_experimental_vm_service) { + // TODO(bkonyi): remove once DDS support is added. + wait_for_dds_to_advertise_service = false; + } +#endif if (wait_for_dds_to_advertise_service) { result = DartUtils::SetStringField(library, "_ddsIP", server_ip); SHUTDOWN_ON_ERROR(result);
diff --git a/runtime/bin/vmservice_impl.h b/runtime/bin/vmservice_impl.h index 71a3309..dbb24fb 100644 --- a/runtime/bin/vmservice_impl.h +++ b/runtime/bin/vmservice_impl.h
@@ -64,6 +64,12 @@ // argument to this parameter should be null. const char* resident_compiler_info_file_path); + // Specifies that the experimental VM service implementation should be used. + // TODO(bkonyi): remove this variable when the experimental service is + // stable. This was only added as a public static to avoid temporarily + // modifying the signature of Setup(...). + static bool enable_experimental_vm_service; + static void SetNativeResolver(); // Error message if startup failed.
diff --git a/runtime/lib/vmservice.cc b/runtime/lib/vmservice.cc index cef09e9..c638151 100644 --- a/runtime/lib/vmservice.cc +++ b/runtime/lib/vmservice.cc
@@ -139,5 +139,4 @@ #endif return Object::null(); } - } // namespace dart
diff --git a/runtime/tests/concurrency/stress_test_list.json b/runtime/tests/concurrency/stress_test_list.json index 94f51e70..a99aa99 100644 --- a/runtime/tests/concurrency/stress_test_list.json +++ b/runtime/tests/concurrency/stress_test_list.json
@@ -3091,8 +3091,8 @@ "../../../tests/language/variable/scope_variable_runtime_test.dart", "../../../tests/language/variable/scoped_variables_try_catch_test.dart", "../../../tests/language/variable/variable_named_dart_test.dart", - "../../../tests/language/variance/syntax/variance_disabled_keyword_identifier_syntax_test.dart", - "../../../tests/language/variance/syntax/variance_keyword_identifier_syntax_test.dart", + "../../../tests/language/variance/syntax/variance_builtin_identifier_syntax_test.dart", + "../../../tests/language/variance/syntax/variance_disabled_builtin_identifier_syntax_test.dart", "../../../tests/language/void/generalized_void_syntax_test.dart", "../../../tests/language/void/generalized_void_usage_test.dart", "../../../tests/language/void/return_future_future_or_void_async_test.dart",
diff --git a/runtime/vm/BUILD.gn b/runtime/vm/BUILD.gn index dc4f61a..4b17532 100644 --- a/runtime/vm/BUILD.gn +++ b/runtime/vm/BUILD.gn
@@ -73,10 +73,7 @@ target_type = "source_set" extra_product_deps = [] extra_nonproduct_deps = [] - extra_deps = [ - "//third_party/icu:icui18n", - "//third_party/icu:icuuc", - ] + extra_deps = [ "//third_party/icu:icuuc" ] if (dart_support_perfetto) { extra_deps += [ "//third_party/perfetto:libprotozero" ] } @@ -273,7 +270,6 @@ ] deps = [ "../platform:libdart_platform_jit", - "//third_party/icu:icui18n", "//third_party/icu:icuuc", ] sources = [ "regexp/gen_regexp_special_case.cc" ]
diff --git a/sdk/BUILD.gn b/sdk/BUILD.gn index 64fe45d..b430522 100644 --- a/sdk/BUILD.gn +++ b/sdk/BUILD.gn
@@ -54,6 +54,8 @@ # ........dart2bytecode.snapshot (AOT snapshot, for selected targets) # ........dart2js_aot.dart.snapshot (AOT snapshot) # ........dart2wasm_product.snapshot (AOT snapshot) +# ........dart_runtime_service_vm_aot.snapshot (AOT snapshot) +# ........dart_runtime_service_vm.snapshot (JIT snapshot) # ........dartdev_aot.dart.snapshot (AOT snapshot) # ........dartdevc_aot.dart.snapshot (AOT snapshot) # ........dds_aot.dart.snapshot (AOT snapshot) @@ -153,6 +155,13 @@ "dart_mcp_server_aot", ], ] + if (include_experimental_vm_service) { + _platform_sdk_snapshots += [ [ + "dart_runtime_service_vm_aot", + "../utils/dart_runtime_service_vm:dart_runtime_service_vm_aot", + "dart_runtime_service_vm_aot", + ] ] + } } else { _platform_sdk_snapshots += [ [ "dds", @@ -160,12 +169,20 @@ "dds", ] ] } + if (dart_snapshot_kind == "app-jit") { _platform_sdk_snapshots += [ [ "kernel-service", "../utils/kernel-service:kernel-service_snapshot", "kernel-service", ] ] + if (include_experimental_vm_service) { + _platform_sdk_snapshots += [ [ + "dart_runtime_service_vm", + "../utils/dart_runtime_service_vm:dart_runtime_service_vm", + "dart_runtime_service_vm", + ] ] + } } # dart2bytecode is an AOT snapshot, which is not supported on the ia32 @@ -398,9 +415,7 @@ "tsan", ] if (is_clang) { - sanitizers += [ - "msan", - ] + sanitizers += [ "msan" ] } } else if (current_cpu == "riscv64") { # Fuchsia Clang is missing the riscv64 MSAN runtime. @@ -536,7 +551,8 @@ # The dartdev, dds and dtd snapshots are output to root_out_dir in order to # be compatible with the way the dart sdk is distributed internally. if (snapshot[0] == "dartdev_aot" || snapshot[0] == "dds_aot_product" || - snapshot[0] == "dart_tooling_daemon_aot_product") { + snapshot[0] == "dart_tooling_daemon_aot_product" || + snapshot[0] == "dart_runtime_service_vm_aot") { root = root_out_dir } copy("copy_${snapshot[0]}_snapshot") {
diff --git a/sdk/lib/_internal/vm_shared/lib/bigint_patch.dart b/sdk/lib/_internal/vm_shared/lib/bigint_patch.dart index da90df3..238b5b3 100644 --- a/sdk/lib/_internal/vm_shared/lib/bigint_patch.dart +++ b/sdk/lib/_internal/vm_shared/lib/bigint_patch.dart
@@ -86,6 +86,7 @@ int remUsed, int rem_nsh, ) { + _dividendDigits = dividendDigits; _dividendUsed = dividendUsed; _divisorDigits = divisorDigits; _divisorUsed = divisorUsed;
diff --git a/sdk/lib/vmservice/vmservice.dart b/sdk/lib/vmservice/vmservice.dart index bbdc863..2aa66d8 100644 --- a/sdk/lib/vmservice/vmservice.dart +++ b/sdk/lib/vmservice/vmservice.dart
@@ -290,7 +290,7 @@ clients.remove(client); for (final streamId in client.streams) { if (!_isAnyClientSubscribed(streamId)) { - _vmCancelStream(streamId); + vmCancelStream(streamId); } } for (final pair in client.createdServiceIdZones) { @@ -444,7 +444,7 @@ await VMServiceEmbedderHooks.cleanup!(); await clearState(); // Notify the VM that we have exited. - _onExit(); + onExit(); } void messageHandler(message) { @@ -497,7 +497,7 @@ if (instance == null) { instance = VMService._internal(); VMService._instance = instance; - _onStart(); + onStart(); } return instance; } @@ -533,7 +533,7 @@ if (!_isAnyClientSubscribed(streamId)) { final includePrivates = message.params['_includePrivateMembers'] == true; if (!serviceStreams.contains(streamId) && - !_vmListenStream(streamId, includePrivates)) { + !vmListenStream(streamId, includePrivates)) { return encodeRpcError( message, kInvalidParams, @@ -569,7 +569,7 @@ client.streams.remove(streamId); if (!serviceStreams.contains(streamId) && !_isAnyClientSubscribed(streamId)) { - _vmCancelStream(streamId); + vmCancelStream(streamId); } return encodeSuccess(message); @@ -807,24 +807,19 @@ /// Notify the VM that the service is running. @pragma("vm:external-name", "VMService_OnStart") -external void _onStart(); +external void onStart(); /// Notify the VM that the service is no longer running. @pragma("vm:external-name", "VMService_OnExit") -external void _onExit(); - -/// Notify the VM that the server's address has changed. -void onServerAddressChange(String? address) { - _onServerAddressChange(address); -} +external void onExit(); @pragma("vm:external-name", "VMService_OnServerAddressChange") -external void _onServerAddressChange(String? address); +external void onServerAddressChange(String? address); /// Subscribe to a service stream. @pragma("vm:external-name", "VMService_ListenStream") -external bool _vmListenStream(String streamId, bool include_privates); +external bool vmListenStream(String streamId, bool include_privates); /// Cancel a subscription to a service stream. @pragma("vm:external-name", "VMService_CancelStream") -external void _vmCancelStream(String streamId); +external void vmCancelStream(String streamId);
diff --git a/sdk_args.gni b/sdk_args.gni index 542d7b8..4013efd 100644 --- a/sdk_args.gni +++ b/sdk_args.gni
@@ -38,6 +38,12 @@ # can significantly improve iteration time when iteration on changes in # core libraries. precompile_tools = false + + # When set to `true`, the Dart Runtime Service based implementation of the + # VM service will be included in the build. By default, the legacy VM + # service implementation will be launched by the VM, unless the + # --include-experimental-vm-service VM flag is provided. + include_experimental_vm_service = false } if (default_git_folder == "") {
diff --git a/tests/language/primary_constructors/syntax/empty_body_error_test.dart b/tests/language/primary_constructors/syntax/empty_body_error_test.dart index bb85f80..069755f 100644 --- a/tests/language/primary_constructors/syntax/empty_body_error_test.dart +++ b/tests/language/primary_constructors/syntax/empty_body_error_test.dart
@@ -2,11 +2,11 @@ // 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. +// SharedOptions=--enable-experiment=primary-constructors + // Enums that have an empty body (i.e. `;`) can be parsed, but will cause a // compile-time error when there's no enum constant declared. -// SharedOptions=--enable-experiment=primary-constructors - enum E1; // ^ // [analyzer] unspecified @@ -26,3 +26,24 @@ // ^ // [analyzer] unspecified // [cfe] unspecified + +// Mixin application classes cannot have an explicit (empty) class body. + +class S; +mixin M on S; +class C = S with M {} +// ^ +// [analyzer] SYNTACTIC_ERROR.EXPECTED_TOKEN +// [cfe] Expected ';' after this. +// ^ +// [analyzer] SYNTACTIC_ERROR.EXPECTED_EXECUTABLE +// [cfe] Expected a declaration, but got '{'. + +class I; +class C2 = S with M implements I {} +// ^ +// [analyzer] SYNTACTIC_ERROR.EXPECTED_TOKEN +// [cfe] Expected ';' after this. +// ^ +// [analyzer] SYNTACTIC_ERROR.EXPECTED_EXECUTABLE +// [cfe] Expected a declaration, but got '{'.
diff --git a/tools/VERSION b/tools/VERSION index aa32dab..32706cd 100644 --- a/tools/VERSION +++ b/tools/VERSION
@@ -27,5 +27,5 @@ MAJOR 3 MINOR 12 PATCH 0 -PRERELEASE 233 +PRERELEASE 234 PRERELEASE_PATCH 0
diff --git a/tools/bots/test_matrix.json b/tools/bots/test_matrix.json index a8406fb..7a7c829 100644 --- a/tools/bots/test_matrix.json +++ b/tools/bots/test_matrix.json
@@ -1045,6 +1045,7 @@ "script": "tools/build.py", "arguments": [ "--codesigning-identity=-", + "--include-experimental-vm-service", "runtime" ] }, @@ -1075,6 +1076,7 @@ "script": "tools/build.py", "arguments": [ "--codesigning-identity=-", + "--include-experimental-vm-service", "runtime" ] }, @@ -1155,6 +1157,7 @@ "script": "tools/build.py", "arguments": [ "--codesigning-identity=-", + "--include-experimental-vm-service", "runtime", "dartaotruntime" ] @@ -3611,4 +3614,4 @@ "macos": "buildtools/mac-x64/clang/bin/llvm-symbolizer", "windows": "buildtools/win-x64/clang/bin/llvm-symbolizer.exe" } -} +} \ No newline at end of file
diff --git a/tools/gn.py b/tools/gn.py index 90e4001..a01d121 100755 --- a/tools/gn.py +++ b/tools/gn.py
@@ -280,6 +280,10 @@ if not args.platform_sdk: gn_args['dart_platform_sdk'] = args.platform_sdk + if args.include_experimental_vm_service: + gn_args[ + 'include_experimental_vm_service'] = args.include_experimental_vm_service + # We don't support stripping on Windows if host_os != 'win': gn_args['dart_stripped_binary'] = 'exe.stripped/dart' @@ -555,6 +559,11 @@ help='Sign executables using the given identity.', default='', type=str) + parser.add_argument( + '--include-experimental-vm-service', + help='Use the Dart Runtime Service based VM service implementation.', + default=False, + action='store_true') def AddCommonConfigurationArgs(parser):
diff --git a/utils/dart_runtime_service_vm/BUILD.gn b/utils/dart_runtime_service_vm/BUILD.gn new file mode 100644 index 0000000..9f256fa --- /dev/null +++ b/utils/dart_runtime_service_vm/BUILD.gn
@@ -0,0 +1,33 @@ +# Copyright (c) 2026, 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("../../runtime/runtime_args.gni") +import("../aot_snapshot.gni") +import("../application_snapshot.gni") + +group("dart_runtime_service_vm_aot") { + public_deps = [ ":dart_runtime_service_vm_aot_snapshot" ] +} + +aot_snapshot("dart_runtime_service_vm_aot_snapshot") { + main_dart = "../../pkg/dart_runtime_service_vm/bin/vm_service_entrypoint.dart" + output = "$root_out_dir/dart_runtime_service_vm_aot.dart.snapshot" +} + +group("dart_runtime_service_vm") { + public_deps = [ ":copy_dart_runtime_service_vm_snapshot" ] +} + +copy("copy_dart_runtime_service_vm_snapshot") { + visibility = [ ":dart_runtime_service_vm" ] + public_deps = [ ":generate_dart_runtime_service_vm_snapshot" ] + sources = [ "$root_gen_dir/dart_runtime_service_vm.dart.snapshot" ] + outputs = [ "$root_out_dir/dart_runtime_service_vm.dart.snapshot" ] +} + +application_snapshot("generate_dart_runtime_service_vm_snapshot") { + main_dart = "../../pkg/dart_runtime_service_vm/bin/vm_service_entrypoint.dart" + training_args = [ "--help" ] + output = "$root_gen_dir/dart_runtime_service_vm.dart.snapshot" +}