Version 2.18.0-170.0.dev
Merge commit '84402e7824bfd9e6c056ca3f89f5eb550c5fb2f5' into 'dev'
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2a5483d..2670af5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -104,6 +104,11 @@
- Added `dartify` and a number of minor helper functions.
+#### `dart:core`
+
+- Allow omitting the `unencodedPath` positional argument to `Uri.http` and
+ `Uri.https` to default to an empty path.
+
### Tools
#### Linter
diff --git a/DEPS b/DEPS
index 5a145db..b09d373 100644
--- a/DEPS
+++ b/DEPS
@@ -89,7 +89,7 @@
"chrome_rev": "19997",
"cli_util_rev": "b0adbba89442b2ea6fef39c7a82fe79cb31e1168",
"clock_rev": "f594d86da123015186d5680b0d0e8255c52fc162",
- "collection_rev": "69766daafbaa8535d1343fb7cd87e713f57c107f",
+ "collection_rev": "f9b433dfc7dba5c8a987b1a8e6a9050292f5582e",
"convert_rev": "00b251529c074df394b3391c7e3eea3dd9e5778e",
"crypto_rev": "4297d240b0e1e780ec0a9eab23eaf1ad491f3e68",
"csslib_rev": "518761b166974537f334dbf264e7f56cb157a96a",
@@ -145,7 +145,7 @@
"sse_rev": "9a54f1cdd91c8d79a6bf5ef8e849a12756607453",
"stack_trace_rev": "17f09c2c6845bb31c7c385acecce5befb8527a13",
"stream_channel_rev": "3fa3e40c75c210d617b8b943b9b8f580e9866a89",
- "string_scanner_rev": "6579871b528036767b3200b390a3ecef28e4900d",
+ "string_scanner_rev": "c637deb8d998b72a5807afbd06aba8370db725c0",
"sync_http_rev": "b6bd47965694dddffb6e62fb8a6c12d17c4ae4cd",
"term_glyph_rev": "d0f205c67ea70eea47b9f41c8440129a72a9c86e",
"test_descriptor_rev": "5ed5d7f6bf1191592995dcb8eedbbc17df69d386",
diff --git a/pkg/_fe_analyzer_shared/lib/src/messages/codes_generated.dart b/pkg/_fe_analyzer_shared/lib/src/messages/codes_generated.dart
index a7e9b00..345e79e 100644
--- a/pkg/_fe_analyzer_shared/lib/src/messages/codes_generated.dart
+++ b/pkg/_fe_analyzer_shared/lib/src/messages/codes_generated.dart
@@ -6940,6 +6940,16 @@
r"""Try removing the 'external' keyword or adding a JS interop annotation.""");
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
+const Code<Null> codeJsInteropInvalidStaticClassMemberName =
+ messageJsInteropInvalidStaticClassMemberName;
+
+// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
+const MessageCode messageJsInteropInvalidStaticClassMemberName = const MessageCode(
+ "JsInteropInvalidStaticClassMemberName",
+ problemMessage:
+ r"""JS interop static class members cannot have '.' in their JS name.""");
+
+// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
const Template<
Message Function(
String name,
diff --git a/pkg/_js_interop_checks/lib/js_interop_checks.dart b/pkg/_js_interop_checks/lib/js_interop_checks.dart
index 34a6b09..9638d1e 100644
--- a/pkg/_js_interop_checks/lib/js_interop_checks.dart
+++ b/pkg/_js_interop_checks/lib/js_interop_checks.dart
@@ -14,6 +14,7 @@
messageJsInteropEnclosingClassJSAnnotationContext,
messageJsInteropExternalExtensionMemberOnTypeInvalid,
messageJsInteropExternalMemberNotJSAnnotated,
+ messageJsInteropInvalidStaticClassMemberName,
messageJsInteropNamedParameters,
messageJsInteropNonExternalConstructor,
messageJsInteropNonExternalMember,
@@ -274,6 +275,18 @@
// named parameters.
_checkNoNamedParameters(procedure.function);
}
+
+ // JS static methods cannot use a JS name with dots.
+ if (procedure.isStatic && procedure.enclosingClass != null) {
+ String name = getJSName(procedure);
+ if (name.contains('.')) {
+ _diagnosticsReporter.report(
+ messageJsInteropInvalidStaticClassMemberName,
+ procedure.fileOffset,
+ procedure.name.text.length,
+ procedure.fileUri);
+ }
+ }
}
if (_classHasStaticInteropAnnotation &&
diff --git a/pkg/analysis_server/lib/src/analytics/analytics_manager.dart b/pkg/analysis_server/lib/src/analytics/analytics_manager.dart
index 12fb6cb..9941bc2 100644
--- a/pkg/analysis_server/lib/src/analytics/analytics_manager.dart
+++ b/pkg/analysis_server/lib/src/analytics/analytics_manager.dart
@@ -23,6 +23,12 @@
/// Record that the set of plugins known to the [pluginManager] has changed.
void changedPlugins(PluginManager pluginManager);
+ /// Record that the given [notification] was received and has been handled.
+ void handledNotificationMessage(
+ {required NotificationMessage notification,
+ required DateTime startTime,
+ required DateTime endTime}) {}
+
/// Record that the given [response] was sent to the client.
void sentResponse({required Response response});
diff --git a/pkg/analysis_server/lib/src/analytics/google_analytics_manager.dart b/pkg/analysis_server/lib/src/analytics/google_analytics_manager.dart
index 1111d47..ddeef8c 100644
--- a/pkg/analysis_server/lib/src/analytics/google_analytics_manager.dart
+++ b/pkg/analysis_server/lib/src/analytics/google_analytics_manager.dart
@@ -30,6 +30,10 @@
/// been responded to.
final Map<String, _RequestData> _completedRequests = {};
+ /// A map from the name of a notification to data about all such notifications
+ /// that have been handled.
+ final Map<String, _NotificationData> _completedNotifications = {};
+
/// Initialize a newly created analytics manager to report to the [analytics]
/// service.
GoogleAnalyticsManager(this.analytics);
@@ -40,6 +44,23 @@
}
@override
+ void handledNotificationMessage(
+ {required NotificationMessage notification,
+ required DateTime startTime,
+ required DateTime endTime}) {
+ var method = notification.method.toString();
+ var requestTime = notification.clientRequestTime;
+ var start = startTime.millisecondsSinceEpoch;
+ var end = endTime.millisecondsSinceEpoch;
+ var data = _completedNotifications.putIfAbsent(
+ method, () => _NotificationData(method));
+ if (requestTime != null) {
+ data.latencyTimes.addValue(start - requestTime);
+ }
+ data.handlingTimes.addValue(end - start);
+ }
+
+ @override
void sentResponse({required Response response}) {
var sendTime = DateTime.now();
_recordResponseData(response.id, sendTime);
@@ -64,6 +85,7 @@
_sendSessionData(sessionData);
_sendServerResponseTimes();
_sendPluginResponseTimes();
+ _sendNotificationHandlingTimes();
analytics.waitForLastPing(timeout: Duration(milliseconds: 200)).then((_) {
analytics.close();
@@ -122,6 +144,17 @@
requestData.responseTimes.addValue(responseTime);
}
+ /// Send information about the notifications handled by the server.
+ void _sendNotificationHandlingTimes() {
+ for (var data in _completedNotifications.values) {
+ analytics.sendEvent('language_server', 'notification', parameters: {
+ 'latency': data.latencyTimes.toAnalyticsString(),
+ 'method': data.method,
+ 'duration': data.handlingTimes.toAnalyticsString(),
+ });
+ }
+ }
+
/// Send information about the response times of plugins.
void _sendPluginResponseTimes() {
var responseTimes = PluginManager.pluginResponseTimes;
@@ -177,6 +210,27 @@
_ActiveRequestData(this.requestName, this.clientRequestTime, this.startTime);
}
+/// Data about the notifications that have been handled that have the same
+/// method.
+class _NotificationData {
+ /// The name of the notifications.
+ final String method;
+
+ /// The percentile calculator for latency times. The _latency time_ is the
+ /// time from when the client sent the request until the time the server
+ /// started processing the request.
+ final PercentileCalculator latencyTimes = PercentileCalculator();
+
+ /// The percentile calculator for handling times. The _handling time_ is the
+ /// time from when the server started processing the notification until the
+ /// handling was complete.
+ final PercentileCalculator handlingTimes = PercentileCalculator();
+
+ /// Initialize a newly create data holder for notifications with the given
+ /// [method].
+ _NotificationData(this.method);
+}
+
/// Data about the plugins associated with the context roots.
class _PluginData {
/// The number of times that plugin information has been recorded.
@@ -219,7 +273,8 @@
}
}
-/// Data about the requests that have been responded to that have the same name.
+/// Data about the requests that have been responded to that have the same
+/// method.
class _RequestData {
/// The name of the requests.
final String method;
diff --git a/pkg/analysis_server/lib/src/analytics/noop_analytics_manager.dart b/pkg/analysis_server/lib/src/analytics/noop_analytics_manager.dart
index d52e59d..0c08132 100644
--- a/pkg/analysis_server/lib/src/analytics/noop_analytics_manager.dart
+++ b/pkg/analysis_server/lib/src/analytics/noop_analytics_manager.dart
@@ -14,6 +14,12 @@
void changedPlugins(PluginManager pluginManager) {}
@override
+ void handledNotificationMessage(
+ {required NotificationMessage notification,
+ required DateTime startTime,
+ required DateTime endTime}) {}
+
+ @override
void sentResponse({required Response response}) {}
@override
diff --git a/pkg/analysis_server/lib/src/lsp/lsp_analysis_server.dart b/pkg/analysis_server/lib/src/lsp/lsp_analysis_server.dart
index 28a2360..23f1958 100644
--- a/pkg/analysis_server/lib/src/lsp/lsp_analysis_server.dart
+++ b/pkg/analysis_server/lib/src/lsp/lsp_analysis_server.dart
@@ -374,6 +374,10 @@
await _handleRequestMessage(message, messageInfo);
} else if (message is NotificationMessage) {
await _handleNotificationMessage(message, messageInfo);
+ analyticsManager.handledNotificationMessage(
+ notification: message,
+ startTime: startTime,
+ endTime: DateTime.now());
} else {
showErrorMessageToUser('Unknown incoming message type');
}
diff --git a/pkg/analysis_server/test/src/analytics/google_analytics_manager_test.dart b/pkg/analysis_server/test/src/analytics/google_analytics_manager_test.dart
index 225e33d..453956b 100644
--- a/pkg/analysis_server/test/src/analytics/google_analytics_manager_test.dart
+++ b/pkg/analysis_server/test/src/analytics/google_analytics_manager_test.dart
@@ -4,6 +4,7 @@
import 'dart:convert';
+import 'package:analysis_server/lsp_protocol/protocol.dart';
import 'package:analysis_server/protocol/protocol.dart';
import 'package:analysis_server/src/analytics/google_analytics_manager.dart';
import 'package:analysis_server/src/analytics/percentile_calculator.dart';
@@ -41,6 +42,26 @@
PluginManager.pluginResponseTimes.clear();
}
+ void test_server_notification() {
+ _defaultStartup();
+ manager.handledNotificationMessage(
+ notification: NotificationMessage(
+ clientRequestTime: 2,
+ jsonrpc: '',
+ method: Method.workspace_didCreateFiles),
+ startTime: _now(),
+ endTime: _now());
+ manager.shutdown();
+ analytics.assertEvents([
+ _ExpectedEvent.session(),
+ _ExpectedEvent.notification(parameters: {
+ 'latency': _IsPercentiles(),
+ 'method': Method.workspace_didCreateFiles.toString(),
+ 'duration': _IsPercentiles(),
+ }),
+ ]);
+ }
+
void test_server_request() {
_defaultStartup();
manager.startedRequest(
@@ -160,6 +181,9 @@
this.value, // ignore: unused_element
this.parameters});
+ _ExpectedEvent.notification({Map<String, Object>? parameters})
+ : this('language_server', 'notification', parameters: parameters);
+
_ExpectedEvent.pluginRequest({Map<String, Object>? parameters})
: this('language_server', 'pluginRequest', parameters: parameters);
diff --git a/pkg/analyzer/lib/src/dart/micro/utils.dart b/pkg/analyzer/lib/src/dart/micro/utils.dart
index 6908497..fc88c98 100644
--- a/pkg/analyzer/lib/src/dart/micro/utils.dart
+++ b/pkg/analyzer/lib/src/dart/micro/utils.dart
@@ -307,8 +307,7 @@
length = 0;
}
references.add(MatchInfo(offset, length, kind));
- }
- if (e!.enclosingElement == element) {
+ } else if (e != null && e.enclosingElement == element) {
kind = MatchKind.REFERENCE;
offset = node.offset;
length = element.nameLength;
diff --git a/pkg/compiler/lib/src/js_backend/native_data.dart b/pkg/compiler/lib/src/js_backend/native_data.dart
index edc6de8..67f6246 100644
--- a/pkg/compiler/lib/src/js_backend/native_data.dart
+++ b/pkg/compiler/lib/src/js_backend/native_data.dart
@@ -650,11 +650,16 @@
String getFixedBackendName(MemberEntity element) {
String name = _nativeMemberName[element];
if (name == null && isJsInteropMember(element)) {
- // If an element isJsInterop but _isJsInterop is false that means it is
- // considered interop as the parent class is interop.
- name = element.isConstructor
- ? _jsClassNameHelper(element.enclosingClass)
- : _jsMemberNameHelper(element);
+ if (element.isConstructor) {
+ name = _jsClassNameHelper(element.enclosingClass);
+ } else {
+ name = _jsMemberNameHelper(element);
+ // Top-level static JS interop members can be associated with a dotted
+ // name, if so, fixedBackendName is the last segment.
+ if (element.isTopLevel && name.contains('.')) {
+ name = name.substring(name.lastIndexOf('.') + 1);
+ }
+ }
_nativeMemberName[element] = name;
}
return name;
@@ -705,6 +710,15 @@
..write('.')
..write(_jsClassNameHelper(element.enclosingClass));
}
+
+ // Top-level static JS interop members can be associated with a dotted
+ // name, if so, fixedBackendPath includes all but the last segment.
+ final name = _jsMemberNameHelper(element);
+ if (element.isTopLevel && name.contains('.')) {
+ sb
+ ..write('.')
+ ..write(name.substring(0, name.lastIndexOf('.')));
+ }
return sb.toString();
}
diff --git a/pkg/compiler/lib/src/js_emitter/native_emitter.dart b/pkg/compiler/lib/src/js_emitter/native_emitter.dart
index 491d2b2..40f80f5 100644
--- a/pkg/compiler/lib/src/js_emitter/native_emitter.dart
+++ b/pkg/compiler/lib/src/js_emitter/native_emitter.dart
@@ -344,7 +344,7 @@
1, indexOfLastOptionalArgumentInParameters + 1);
} else {
// Native methods that are not intercepted must be static.
- assert(member.isStatic, failedAt(member));
+ assert(member.isStatic || member.isTopLevel, failedAt(member));
arguments = argumentsBuffer.sublist(
0, indexOfLastOptionalArgumentInParameters + 1);
if (_nativeData.isJsInteropMember(member)) {
diff --git a/pkg/dev_compiler/lib/src/kernel/compiler.dart b/pkg/dev_compiler/lib/src/kernel/compiler.dart
index 2b3a91d..6fe471c 100644
--- a/pkg/dev_compiler/lib/src/kernel/compiler.dart
+++ b/pkg/dev_compiler/lib/src/kernel/compiler.dart
@@ -2645,16 +2645,9 @@
js_ast.LiteralString _emitJSInteropStaticMemberName(NamedNode n) {
if (!usesJSInterop(n)) return null;
- var name = _annotationName(n, isPublicJSAnnotation);
- if (name != null) {
- if (name.contains('.')) {
- throw UnsupportedError(
- 'static members do not support "." in their names. '
- 'See https://github.com/dart-lang/sdk/issues/27926');
- }
- } else {
- name = getTopLevelName(n);
- }
+ var name = _annotationName(n, isPublicJSAnnotation) ?? getTopLevelName(n);
+ assert(name != null && !name.contains('.'),
+ 'JS interop checker rejects dotted names on static class members');
return js.escapedString(name, "'");
}
diff --git a/pkg/front_end/messages.status b/pkg/front_end/messages.status
index 7c33194..4a1486a 100644
--- a/pkg/front_end/messages.status
+++ b/pkg/front_end/messages.status
@@ -562,6 +562,8 @@
JsInteropExternalExtensionMemberOnTypeInvalid/example: Fail # Web compiler specific
JsInteropExternalMemberNotJSAnnotated/analyzerCode: Fail # Web compiler specific
JsInteropExternalMemberNotJSAnnotated/example: Fail # Web compiler specific
+JsInteropInvalidStaticClassMemberName/analyzerCode: Fail
+JsInteropInvalidStaticClassMemberName/example: Fail
JsInteropJSClassExtendsDartClass/analyzerCode: Fail # Web compiler specific
JsInteropJSClassExtendsDartClass/example: Fail # Web compiler specific
JsInteropNamedParameters/analyzerCode: Fail # Web compiler specific
diff --git a/pkg/front_end/messages.yaml b/pkg/front_end/messages.yaml
index 0d6ce5d..829904c 100644
--- a/pkg/front_end/messages.yaml
+++ b/pkg/front_end/messages.yaml
@@ -5173,6 +5173,9 @@
problemMessage: "JS interop classes do not support operator methods."
correctionMessage: "Try replacing this with a normal method."
+JsInteropInvalidStaticClassMemberName:
+ problemMessage: "JS interop static class members cannot have '.' in their JS name."
+
JsInteropStaticInteropWithInstanceMembers:
problemMessage: "JS interop class '#name' with `@staticInterop` annotation cannot declare instance members."
correctionMessage: "Try moving the instance member to a static extension."
diff --git a/sdk/lib/core/uri.dart b/sdk/lib/core/uri.dart
index f74f55e..bb84862 100644
--- a/sdk/lib/core/uri.dart
+++ b/sdk/lib/core/uri.dart
@@ -238,11 +238,13 @@
/// The `path` component is set from the [unencodedPath]
/// argument. The path passed must not be encoded as this constructor
/// encodes the path.
+ /// If omitted, the path defaults to being empty.
///
/// The `query` component is set from the optional [queryParameters]
/// argument.
- factory Uri.http(String authority, String unencodedPath,
- [Map<String, dynamic>? queryParameters]) = _Uri.http;
+ factory Uri.http(String authority,
+ [String unencodedPath,
+ Map<String, dynamic>? queryParameters]) = _Uri.http;
/// Creates a new `https` URI from authority, path and query.
///
@@ -263,8 +265,9 @@
/// uri = Uri.https('example.org', '/a%2F');
/// print(uri); // https://example.org/a%252F
/// ```
- factory Uri.https(String authority, String unencodedPath,
- [Map<String, dynamic>? queryParameters]) = _Uri.https;
+ factory Uri.https(String authority,
+ [String unencodedPath,
+ Map<String, dynamic>? queryParameters]) = _Uri.https;
/// Creates a new file URI from an absolute or relative file path.
///
@@ -1667,14 +1670,14 @@
}
/// Implementation of [Uri.http].
- factory _Uri.http(String authority, String unencodedPath,
- [Map<String, dynamic>? queryParameters]) {
+ factory _Uri.http(String authority,
+ [String unencodedPath = '', Map<String, dynamic>? queryParameters]) {
return _makeHttpUri("http", authority, unencodedPath, queryParameters);
}
/// Implementation of [Uri.https].
- factory _Uri.https(String authority, String unencodedPath,
- [Map<String, dynamic>? queryParameters]) {
+ factory _Uri.https(String authority,
+ [String unencodedPath = '', Map<String, dynamic>? queryParameters]) {
return _makeHttpUri("https", authority, unencodedPath, queryParameters);
}
diff --git a/tests/corelib/uri_http_test.dart b/tests/corelib/uri_http_test.dart
index bfc57bf..5cd8ccb 100644
--- a/tests/corelib/uri_http_test.dart
+++ b/tests/corelib/uri_http_test.dart
@@ -10,10 +10,13 @@
}
check(new Uri.http("", ""), "http:");
+ check(new Uri.http(""), "http:");
check(new Uri.http("@:", ""), "http://");
+ check(new Uri.http("@:"), "http://");
check(new Uri.http("@:8080", ""), "http://:8080");
+ check(new Uri.http("@:8080"), "http://:8080");
check(new Uri.http("@host:", ""), "http://host");
- check(new Uri.http("@host:", ""), "http://host");
+ check(new Uri.http("@host:"), "http://host");
check(new Uri.http("xxx:yyy@host:8080", ""), "http://xxx:yyy@host:8080");
check(new Uri.http("host", "a"), "http://host/a");
check(new Uri.http("host", "/a"), "http://host/a");
@@ -46,10 +49,13 @@
}
check(new Uri.https("", ""), "https:");
+ check(new Uri.https(""), "https:");
check(new Uri.https("@:", ""), "https://");
+ check(new Uri.https("@:"), "https://");
check(new Uri.https("@:8080", ""), "https://:8080");
+ check(new Uri.https("@:8080"), "https://:8080");
check(new Uri.https("@host:", ""), "https://host");
- check(new Uri.https("@host:", ""), "https://host");
+ check(new Uri.https("@host:"), "https://host");
check(new Uri.https("xxx:yyy@host:8080", ""), "https://xxx:yyy@host:8080");
check(new Uri.https("host", "a"), "https://host/a");
check(new Uri.https("host", "/a"), "https://host/a");
diff --git a/tests/corelib_2/uri_http_test.dart b/tests/corelib_2/uri_http_test.dart
index 9d02ebf..2441c28 100644
--- a/tests/corelib_2/uri_http_test.dart
+++ b/tests/corelib_2/uri_http_test.dart
@@ -12,10 +12,13 @@
}
check(new Uri.http("", ""), "http:");
+ check(new Uri.http(""), "http:");
check(new Uri.http("@:", ""), "http://");
+ check(new Uri.http("@:"), "http://");
check(new Uri.http("@:8080", ""), "http://:8080");
+ check(new Uri.http("@:8080"), "http://:8080");
check(new Uri.http("@host:", ""), "http://host");
- check(new Uri.http("@host:", ""), "http://host");
+ check(new Uri.http("@host:"), "http://host");
check(new Uri.http("xxx:yyy@host:8080", ""), "http://xxx:yyy@host:8080");
check(new Uri.http("host", "a"), "http://host/a");
check(new Uri.http("host", "/a"), "http://host/a");
diff --git a/tests/lib/js/static_class_member_static_test.dart b/tests/lib/js/static_class_member_static_test.dart
new file mode 100644
index 0000000..cbaa314
--- /dev/null
+++ b/tests/lib/js/static_class_member_static_test.dart
@@ -0,0 +1,15 @@
+// Copyright (c) 2022, 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:js/js.dart';
+
+@JS('a.Foo')
+class Foo {
+ @JS('c.d.plus')
+ external static plus1(arg);
+// ^
+// [web] JS interop static class members cannot have '.' in their JS name.
+}
+
+main() {}
diff --git a/tests/web/regress/issue/49129_test.dart b/tests/web/regress/issue/49129_test.dart
new file mode 100644
index 0000000..bb7b822
--- /dev/null
+++ b/tests/web/regress/issue/49129_test.dart
@@ -0,0 +1,39 @@
+// Copyright (c) 2022, 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 regression test for a bug in how tear-offs of JSinterop top-level
+/// methods were generated.
+///
+/// A `.` in the JS-name of a interop method means that the method is accessible
+/// via a path of selectors, not that the `.` was meant to be part of the
+/// selector name.
+///
+/// See: https://github.com/dart-lang/sdk/issues/49129
+
+import 'package:js/js.dart';
+import 'package:expect/expect.dart';
+
+@JS('a.Foo.c.d.plus')
+external plus1(arg1);
+
+@JS('a.Foo.c.d')
+class Foo {
+ // Note: dots are not allowed in static class members, so the issue didn't
+ // arise in this case.
+ @JS('plus')
+ external static plus1(arg);
+}
+
+@JS()
+external eval(String s);
+
+main() {
+ eval('self.a = {Foo: {c: {d: {plus: function(a, b) { return a + 1; }}}}};');
+
+ // Tear-off for top-level with dotted names.
+ Expect.equals(2, (plus1)(1));
+
+ // Can also be accessed as a static method (with no dots).
+ Expect.equals(3, (Foo.plus1)(2));
+}
diff --git a/tests/web/web.status b/tests/web/web.status
index 9613b8b..091f925 100644
--- a/tests/web/web.status
+++ b/tests/web/web.status
@@ -41,6 +41,7 @@
deferred_custom_loader_test: SkipByDesign # Issue 25683
deferred_fail_and_retry_test: SkipByDesign # Uses eval to simulate failed loading.
internal/object_members_test: SkipByDesign # Uses eval for interop
+regress/issue/49129_test: SkipByDesign # Uses eval for interop
[ $compiler == dart2js && !$host_checked ]
dummy_compiler_test: Slow, Pass # Issue 32439. self-hosting doesn't work with CFE yet.
diff --git a/tools/VERSION b/tools/VERSION
index 93cc43c..47b9487 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
MAJOR 2
MINOR 18
PATCH 0
-PRERELEASE 169
+PRERELEASE 170
PRERELEASE_PATCH 0
\ No newline at end of file