[ddc, dartj2s] Exclude null checks on non-web native members
Modifies ddc and dart2js logic to only include null-checks on native
members inside the web libraries. Modifies tests to account for this
change.
Change-Id: If9c164fb90b761d3c4611d87ffeb02c2fa884457
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/168585
Reviewed-by: Nicholas Shahan <nshahan@google.com>
Reviewed-by: Sigmund Cherem <sigmund@google.com>
diff --git a/pkg/compiler/lib/src/ir/util.dart b/pkg/compiler/lib/src/ir/util.dart
index 15d7550..437a842 100644
--- a/pkg/compiler/lib/src/ir/util.dart
+++ b/pkg/compiler/lib/src/ir/util.dart
@@ -242,3 +242,29 @@
/// and function type variables) are considered.
bool containsFreeVariables(ir.DartType type) =>
type.accept(const _FreeVariableVisitor());
+
+/// Returns true if [importUri] corresponds to dart:html and related libraries.
+bool _isWebLibrary(Uri importUri) =>
+ importUri.scheme == 'dart' &&
+ (importUri.path == 'html' ||
+ importUri.path == 'svg' ||
+ importUri.path == 'indexed_db' ||
+ importUri.path == 'web_audio' ||
+ importUri.path == 'web_gl' ||
+ importUri.path == 'web_sql' ||
+ importUri.path == 'html_common') ||
+ // Mock web library path for testing.
+ importUri.path
+ .contains('native_null_assertions/web_library_interfaces.dart');
+
+bool nodeIsInWebLibrary(ir.TreeNode node) {
+ if (node == null) return false;
+ if (node is ir.Library) return _isWebLibrary(node.importUri);
+ return nodeIsInWebLibrary(node.parent);
+}
+
+bool memberEntityIsInWebLibrary(MemberEntity entity) {
+ var importUri = entity?.library?.canonicalUri;
+ if (importUri == null) return false;
+ return _isWebLibrary(importUri);
+}
diff --git a/pkg/compiler/lib/src/ssa/builder_kernel.dart b/pkg/compiler/lib/src/ssa/builder_kernel.dart
index 619683d..aa1ea95 100644
--- a/pkg/compiler/lib/src/ssa/builder_kernel.dart
+++ b/pkg/compiler/lib/src/ssa/builder_kernel.dart
@@ -1548,7 +1548,8 @@
if (options.enableNativeNullAssertions) {
if (_isNonNullableByDefault(functionNode)) {
DartType type = _getDartTypeIfValid(functionNode.returnType);
- if (dartTypes.isNonNullableIfSound(type)) {
+ if (dartTypes.isNonNullableIfSound(type) &&
+ nodeIsInWebLibrary(functionNode)) {
push(HNullCheck(value, _abstractValueDomain.excludeNull(returnType),
sticky: true));
value = pop();
@@ -4800,7 +4801,7 @@
/// type is non-nullable, add a check to make sure it isn't null.
void _maybeAddNullCheckOnJS(ir.StaticInvocation invocation) {
if (options.enableNativeNullAssertions &&
- _isInWebLibrary(invocation) &&
+ nodeIsInWebLibrary(invocation) &&
closedWorld.dartTypes
.isNonNullableIfSound(_getStaticType(invocation).type)) {
HInstruction code = pop();
@@ -4810,25 +4811,6 @@
}
}
- /// Returns true if [node] belongs to dart:html and related libraries.
- bool _isInWebLibrary(ir.TreeNode node) {
- if (node == null) return false;
- bool isWebLibrary(Uri importUri) =>
- importUri.scheme == 'dart' &&
- (importUri.path == 'html' ||
- importUri.path == 'svg' ||
- importUri.path == 'indexed_db' ||
- importUri.path == 'web_audio' ||
- importUri.path == 'web_gl' ||
- importUri.path == 'web_sql' ||
- importUri.path == 'html_common') ||
- // Mock web library path for testing.
- importUri.path
- .contains('native_null_assertions/js_invocations_in_web_library');
- if (node is ir.Library) return isWebLibrary(node.importUri);
- return _isInWebLibrary(node.parent);
- }
-
void _handleJsStringConcat(ir.StaticInvocation invocation) {
if (_unexpectedForeignArguments(invocation,
minPositional: 2, maxPositional: 2)) {
diff --git a/pkg/compiler/lib/src/ssa/optimize.dart b/pkg/compiler/lib/src/ssa/optimize.dart
index 3386187..6011731 100644
--- a/pkg/compiler/lib/src/ssa/optimize.dart
+++ b/pkg/compiler/lib/src/ssa/optimize.dart
@@ -13,6 +13,7 @@
import '../elements/types.dart';
import '../inferrer/abstract_value_domain.dart';
import '../inferrer/types.dart';
+import '../ir/util.dart';
import '../js_backend/field_analysis.dart'
show FieldAnalysisData, JFieldAnalysis;
import '../js_backend/backend.dart' show CodegenInputs;
@@ -929,7 +930,8 @@
if (method.library.isNonNullableByDefault) {
FunctionType type =
_closedWorld.elementEnvironment.getFunctionType(method);
- if (_closedWorld.dartTypes.isNonNullableIfSound(type.returnType)) {
+ if (_closedWorld.dartTypes.isNonNullableIfSound(type.returnType) &&
+ memberEntityIsInWebLibrary(method)) {
node.block.addBefore(node, replacement);
replacement = HNullCheck(replacement,
_abstractValueDomain.excludeNull(replacement.instructionType),
diff --git a/pkg/dev_compiler/lib/src/kernel/compiler.dart b/pkg/dev_compiler/lib/src/kernel/compiler.dart
index 31eb2d6..464ff44 100644
--- a/pkg/dev_compiler/lib/src/kernel/compiler.dart
+++ b/pkg/dev_compiler/lib/src/kernel/compiler.dart
@@ -4283,7 +4283,8 @@
_extensionTypes.isNativeClass(member.enclosingClass) &&
member is Procedure &&
member.function != null &&
- member.function.returnType.isPotentiallyNonNullable;
+ member.function.returnType.isPotentiallyNonNullable &&
+ _isWebLibrary(member.enclosingLibrary?.importUri);
// TODO(jmesserly): can we encapsulate REPL name lookups and remove this?
// _emitMemberName would be a nice place to handle it, but we don't have
@@ -5105,6 +5106,7 @@
}
bool _isWebLibrary(Uri importUri) =>
+ importUri != null &&
importUri.scheme == 'dart' &&
(importUri.path == 'html' ||
importUri.path == 'svg' ||
diff --git a/tests/dart2js/native/native_null_assertions/flag_disabled_test.dart b/tests/dart2js/native/native_null_assertions/flag_disabled_non_web_test.dart
similarity index 90%
copy from tests/dart2js/native/native_null_assertions/flag_disabled_test.dart
copy to tests/dart2js/native/native_null_assertions/flag_disabled_non_web_test.dart
index e307833..85521eb 100644
--- a/tests/dart2js/native/native_null_assertions/flag_disabled_test.dart
+++ b/tests/dart2js/native/native_null_assertions/flag_disabled_non_web_test.dart
@@ -2,6 +2,7 @@
// 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 'non_web_library_interfaces.dart';
import 'null_assertions_test_lib.dart';
void main() {
diff --git a/tests/dart2js/native/native_null_assertions/flag_disabled_test.dart b/tests/dart2js/native/native_null_assertions/flag_disabled_web_test.dart
similarity index 90%
rename from tests/dart2js/native/native_null_assertions/flag_disabled_test.dart
rename to tests/dart2js/native/native_null_assertions/flag_disabled_web_test.dart
index e307833..7bfe293 100644
--- a/tests/dart2js/native/native_null_assertions/flag_disabled_test.dart
+++ b/tests/dart2js/native/native_null_assertions/flag_disabled_web_test.dart
@@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
import 'null_assertions_test_lib.dart';
+import 'web_library_interfaces.dart';
void main() {
var flagEnabled = false;
diff --git a/tests/dart2js/native/native_null_assertions/flag_enabled_test.dart b/tests/dart2js/native/native_null_assertions/flag_enabled_non_web_test.dart
similarity index 90%
copy from tests/dart2js/native/native_null_assertions/flag_enabled_test.dart
copy to tests/dart2js/native/native_null_assertions/flag_enabled_non_web_test.dart
index 0eb3965..61c018b 100644
--- a/tests/dart2js/native/native_null_assertions/flag_enabled_test.dart
+++ b/tests/dart2js/native/native_null_assertions/flag_enabled_non_web_test.dart
@@ -4,6 +4,7 @@
// dart2jsOptions=--native-null-assertions
+import 'non_web_library_interfaces.dart';
import 'null_assertions_test_lib.dart';
void main() {
diff --git a/tests/dart2js/native/native_null_assertions/flag_enabled_test.dart b/tests/dart2js/native/native_null_assertions/flag_enabled_web_test.dart
similarity index 91%
rename from tests/dart2js/native/native_null_assertions/flag_enabled_test.dart
rename to tests/dart2js/native/native_null_assertions/flag_enabled_web_test.dart
index 0eb3965..71baddf 100644
--- a/tests/dart2js/native/native_null_assertions/flag_enabled_test.dart
+++ b/tests/dart2js/native/native_null_assertions/flag_enabled_web_test.dart
@@ -5,6 +5,7 @@
// dart2jsOptions=--native-null-assertions
import 'null_assertions_test_lib.dart';
+import 'web_library_interfaces.dart';
void main() {
var flagEnabled = true;
diff --git a/tests/dart2js/native/native_null_assertions/js_invocations_in_non_web_library.dart b/tests/dart2js/native/native_null_assertions/js_invocations_in_non_web_library.dart
deleted file mode 100644
index 9dd2be1..0000000
--- a/tests/dart2js/native/native_null_assertions/js_invocations_in_non_web_library.dart
+++ /dev/null
@@ -1,16 +0,0 @@
-// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-import '../native_testing.dart';
-import 'null_assertions_lib.dart';
-
-// Implementation of `JSInterface` except in a folder that is not part of the
-// allowlist for the `--native-null-assertions` flag. This file is not treated
-// as a web library, and therefore the `JS()` invocations should not be checked.
-
-@Native('CCCInNonWebLibrary')
-class CCCInNonWebLibrary implements JSInterface {
- String get name => JS('String', '#.name', this);
- String? get optName => JS('String|Null', '#.optName', this);
-}
diff --git a/tests/dart2js/native/native_null_assertions/js_invocations_in_web_library.dart b/tests/dart2js/native/native_null_assertions/js_invocations_in_web_library.dart
deleted file mode 100644
index 0554ab4..0000000
--- a/tests/dart2js/native/native_null_assertions/js_invocations_in_web_library.dart
+++ /dev/null
@@ -1,16 +0,0 @@
-// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-import '../native_testing.dart';
-import 'null_assertions_lib.dart';
-
-// Implementation of `JSInterface` in a folder that is explicitly part of the
-// allowlist for the `--native-null-assertions` flag. This file is treated as a
-// web library, and therefore the `JS()` invocations should be checked.
-
-@Native('CCCInWebLibrary')
-class CCCInWebLibrary implements JSInterface {
- String get name => JS('String', '#.name', this);
- String? get optName => JS('String|Null', '#.optName', this);
-}
diff --git a/tests/dart2js/native/native_null_assertions/non_web_library_interfaces.dart b/tests/dart2js/native/native_null_assertions/non_web_library_interfaces.dart
new file mode 100644
index 0000000..f0b6f95
--- /dev/null
+++ b/tests/dart2js/native/native_null_assertions/non_web_library_interfaces.dart
@@ -0,0 +1,105 @@
+// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import '../native_testing.dart';
+import 'null_assertions_test_lib.dart';
+
+// Implementations of `NativeInterface` and `JSInterface` in a folder that is
+// not part of the allowlist for the `--native-null-assertions` flag. This file
+// is not treated as a web library, and therefore native members and `JS()`
+// invocations should not be checked.
+
+@Native("AAA")
+class AAA implements NativeInterface {
+ int get size native;
+ String get name native;
+ String? get optName native;
+ int method1() native;
+ String method2() native;
+ String? optMethod() native;
+}
+
+@Native('CCC')
+class CCC implements JSInterface {
+ String get name => JS('String', '#.name', this);
+ String? get optName => JS('String|Null', '#.optName', this);
+}
+
+/// Returns an 'AAA' object that satisfies the interface.
+AAA makeA() native;
+
+/// Returns an 'AAA' object where each method breaks the interface's contract.
+AAA makeAX() native;
+
+/// Returns a 'CCC' object that satisfies the interface using `JS()`
+/// invocations.
+CCC makeC() native;
+
+/// Returns a 'CCC' object where each method breaks the interface's contract.
+CCC makeCX() native;
+
+// The 'AAA' version of the code is passed only objects of a single native
+// class, so the native method can be inlined (which happens in the optimizer).
+// This tests that the null-check exists in the 'inlined' code.
+
+@pragma('dart2js:noInline')
+String describeAAA(AAA o) {
+ return '${o.name} ${o.method2()} ${o.size} ${o.method1()}';
+}
+
+@pragma('dart2js:noInline')
+String describeOptAAA(AAA o) {
+ return '${o.optName} ${o.optMethod()}';
+}
+
+void testNativeNullAssertions(bool flagEnabled) {
+ nativeTesting();
+ setup();
+ AAA a = makeA();
+ BBB b = BBB();
+
+ Expect.equals(expectedA, describeNativeInterface(a));
+ Expect.equals(expectedB, describeNativeInterface(b));
+
+ Expect.equals(expectedA, describeAAA(a));
+
+ AAA x = makeAX(); // This object returns `null`!
+ // Since native members are not in a web library, there should be no checks,
+ // regardless of if the flag is enabled.
+ var checkExpectation = (f) => f();
+ checkExpectation(() => describeNativeInterface(x));
+ checkExpectation(() => describeAAA(x));
+
+ checkExpectation(() => x.name);
+ checkExpectation(() => x.size);
+ checkExpectation(() => x.method1());
+ checkExpectation(() => x.method2());
+
+ // Now test that a nullable return type does not have a check.
+ Expect.equals(expectedOptA, describeOptNativeInterface(a));
+ Expect.equals(expectedOptB, describeOptNativeInterface(b));
+ Expect.equals(expectedOptX, describeOptNativeInterface(x));
+
+ Expect.equals(expectedOptA, describeOptAAA(a));
+ Expect.equals(expectedOptX, describeOptAAA(x));
+}
+
+void testJSInvocationNullAssertions(bool flagEnabled) {
+ nativeTesting();
+ setup();
+
+ CCC c = makeC();
+ CCC cx = makeCX();
+
+ Expect.equals(expectedC, describeJSInterface(c));
+
+ // Since invocations are not in a web library, there should be no checks,
+ // regardless of if the flag is enabled.
+ var checkExpectation = (f) => f();
+ checkExpectation(() => describeJSInterface(cx));
+
+ // Test that invocations with a nullable static type do not have checks.
+ Expect.equals(expectedOptC, describeOptJSInterface(c));
+ Expect.equals(expectedOptCX, describeOptJSInterface(cx));
+}
diff --git a/tests/dart2js/native/native_null_assertions/null_assertions_lib.dart b/tests/dart2js/native/native_null_assertions/null_assertions_lib.dart
deleted file mode 100644
index 894fdf0..0000000
--- a/tests/dart2js/native/native_null_assertions/null_assertions_lib.dart
+++ /dev/null
@@ -1,38 +0,0 @@
-// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-import '../native_testing.dart';
-
-abstract class NativeInterface {
- int get size;
- String get name;
- String? get optName;
- int method1();
- String method2();
- String? optMethod();
-}
-
-@Native("AAA")
-class AAA implements NativeInterface {
- int get size native;
- String get name native;
- String? get optName native;
- int method1() native;
- String method2() native;
- String? optMethod() native;
-}
-
-abstract class JSInterface {
- String get name;
- String? get optName;
-}
-
-class BBB implements NativeInterface {
- int get size => 300;
- String get name => 'Brenda';
- String? get optName => name;
- int method1() => 400;
- String method2() => 'brilliant!';
- String? optMethod() => method2();
-}
diff --git a/tests/dart2js/native/native_null_assertions/null_assertions_test_lib.dart b/tests/dart2js/native/native_null_assertions/null_assertions_test_lib.dart
index 3f1f540..049cd36 100644
--- a/tests/dart2js/native/native_null_assertions/null_assertions_test_lib.dart
+++ b/tests/dart2js/native/native_null_assertions/null_assertions_test_lib.dart
@@ -3,31 +3,29 @@
// BSD-style license that can be found in the LICENSE file.
import '../native_testing.dart';
-import 'js_invocations_in_non_web_library.dart';
-import 'js_invocations_in_web_library.dart';
-import 'null_assertions_lib.dart';
-/// Returns an 'AAA' object that satisfies the interface.
-AAA makeA() native;
+abstract class NativeInterface {
+ int get size;
+ String get name;
+ String? get optName;
+ int method1();
+ String method2();
+ String? optMethod();
+}
-/// Returns an 'AAA' object where each method breaks the interface's contract.
-AAA makeAX() native;
+abstract class JSInterface {
+ String get name;
+ String? get optName;
+}
-/// Returns a 'JSInterface' object whose `JS()` invocations exist in a library
-/// that is part of the allowlist.
-CCCInWebLibrary makeWebC() native;
-
-/// Returns the same as above but where each method breaks the interface's
-/// contract.
-CCCInWebLibrary makeWebCX() native;
-
-/// Returns a 'JSInterface' object whose `JS()` invocations exist in a library
-/// that is not part of the allowlist.
-CCCInNonWebLibrary makeNonWebC() native;
-
-/// Returns the same as above but where each method breaks the interface's
-/// contract.
-CCCInNonWebLibrary makeNonWebCX() native;
+class BBB implements NativeInterface {
+ int get size => 300;
+ String get name => 'Brenda';
+ String? get optName => name;
+ int method1() => 400;
+ String method2() => 'brilliant!';
+ String? optMethod() => method2();
+}
void setup() {
JS('', r"""
@@ -52,40 +50,25 @@
self.nativeConstructor(AAA);
- function CCCInWebLibrary(n) {
- this.name = n;
- this.optName = n;
- }
- function CCCInNonWebLibrary(n) {
+ function CCC(n) {
this.name = n;
this.optName = n;
}
- makeWebC = function() {
- return new CCCInWebLibrary('Carol');
+ makeC = function() {
+ return new CCC('Carol');
};
- makeWebCX = function() {
- return new CCCInWebLibrary(void 0);
- };
- makeNonWebC = function() {
- return new CCCInNonWebLibrary('Carol');
- };
- makeNonWebCX = function() {
- return new CCCInNonWebLibrary(void 0);
+ makeCX = function() {
+ return new CCC(void 0);
};
- self.nativeConstructor(CCCInWebLibrary);
- self.nativeConstructor(CCCInNonWebLibrary);
+ self.nativeConstructor(CCC);
})()""");
}
// The 'NativeInterface' version of the code is passed both native and Dart
// objects, so there will be an interceptor dispatch to the method. This tests
// that the null-check exists in the forwarding method.
-//
-// The 'AAA' version of the code is passed only objects of a single native
-// class, so the native method can be inlined (which happens in the optimizer).
-// This tests that the null-check exists in the 'inlined' code.
@pragma('dart2js:noInline')
String describeNativeInterface(NativeInterface o) {
@@ -93,21 +76,11 @@
}
@pragma('dart2js:noInline')
-String describeAAA(AAA o) {
- return '${o.name} ${o.method2()} ${o.size} ${o.method1()}';
-}
-
-@pragma('dart2js:noInline')
String describeOptNativeInterface(NativeInterface o) {
return '${o.optName} ${o.optMethod()}';
}
@pragma('dart2js:noInline')
-String describeOptAAA(AAA o) {
- return '${o.optName} ${o.optMethod()}';
-}
-
-@pragma('dart2js:noInline')
String describeJSInterface(JSInterface o) {
return '${o.name}';
}
@@ -126,68 +99,3 @@
const expectedC = 'Carol';
const expectedOptC = 'Carol';
const expectedOptCX = 'null';
-
-// Test that `--native-null-assertions` injects null-checks on the returned
-// value of native methods with a non-nullable return type in an opt-in library.
-void testNativeNullAssertions(bool flagEnabled) {
- nativeTesting();
- setup();
- AAA a = makeA();
- BBB b = BBB();
-
- Expect.equals(expectedA, describeNativeInterface(a));
- Expect.equals(expectedB, describeNativeInterface(b));
-
- Expect.equals(expectedA, describeAAA(a));
-
- AAA x = makeAX(); // This object returns `null`!
- var checkExpectation = flagEnabled ? Expect.throws : (f) => f();
- checkExpectation(() => describeNativeInterface(x));
- checkExpectation(() => describeAAA(x));
-
- checkExpectation(() => x.name);
- checkExpectation(() => x.size);
- checkExpectation(() => x.method1());
- checkExpectation(() => x.method2());
-
- // Now test that a nullable return type does not have a check.
- Expect.equals(expectedOptA, describeOptNativeInterface(a));
- Expect.equals(expectedOptB, describeOptNativeInterface(b));
- Expect.equals(expectedOptX, describeOptNativeInterface(x));
-
- Expect.equals(expectedOptA, describeOptAAA(a));
- Expect.equals(expectedOptX, describeOptAAA(x));
-}
-
-// Test that `--native-null-assertions` injects null-checks on the returned
-// value of `JS()` invocations with a non-nullable static type in an opt-in
-// library.
-void testJSInvocationNullAssertions(bool flagEnabled) {
- nativeTesting();
- setup();
-
- CCCInWebLibrary webC = makeWebC();
- CCCInWebLibrary webCX = makeWebCX();
-
- CCCInNonWebLibrary nonWebC = makeNonWebC();
- CCCInNonWebLibrary nonWebCX = makeNonWebCX();
-
- Expect.equals(expectedC, describeJSInterface(webC));
- Expect.equals(expectedC, describeJSInterface(nonWebC));
-
- // If invocations are in a web library, this should throw if null checks are
- // enabled.
- var checkExpectationWeb = flagEnabled ? Expect.throws : (f) => f();
- checkExpectationWeb(() => describeJSInterface(webCX));
-
- // If invocations are not in a web library, there should not be a null check
- // regardless if the flag is enabled or not.
- var checkExpectationNonWeb = (f) => f();
- checkExpectationNonWeb(() => describeJSInterface(nonWebCX));
-
- // Test that invocations with a nullable static type do not have checks.
- Expect.equals(expectedOptC, describeOptJSInterface(webC));
- Expect.equals(expectedOptC, describeOptJSInterface(nonWebC));
- Expect.equals(expectedOptCX, describeOptJSInterface(webCX));
- Expect.equals(expectedOptCX, describeOptJSInterface(nonWebCX));
-}
diff --git a/tests/dart2js/native/native_null_assertions/web_library_interfaces.dart b/tests/dart2js/native/native_null_assertions/web_library_interfaces.dart
new file mode 100644
index 0000000..48bb86b
--- /dev/null
+++ b/tests/dart2js/native/native_null_assertions/web_library_interfaces.dart
@@ -0,0 +1,105 @@
+// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import '../native_testing.dart';
+import 'null_assertions_test_lib.dart';
+
+// Implementations of `NativeInterface` and `JSInterface` in a folder that is
+// part of the allowlist for the `--native-null-assertions` flag. This file is
+// treated as a web library, and therefore native members and `JS()` invocations
+// should be checked.
+
+@Native("AAA")
+class AAA implements NativeInterface {
+ int get size native;
+ String get name native;
+ String? get optName native;
+ int method1() native;
+ String method2() native;
+ String? optMethod() native;
+}
+
+@Native('CCC')
+class CCC implements JSInterface {
+ String get name => JS('String', '#.name', this);
+ String? get optName => JS('String|Null', '#.optName', this);
+}
+
+/// Returns an 'AAA' object that satisfies the interface.
+AAA makeA() native;
+
+/// Returns an 'AAA' object where each method breaks the interface's contract.
+AAA makeAX() native;
+
+/// Returns a 'CCC' object that satisfies the interface using `JS()`
+/// invocations.
+CCC makeC() native;
+
+/// Returns a 'CCC' object where each method breaks the interface's contract.
+CCC makeCX() native;
+
+// The 'AAA' version of the code is passed only objects of a single native
+// class, so the native method can be inlined (which happens in the optimizer).
+// This tests that the null-check exists in the 'inlined' code.
+
+@pragma('dart2js:noInline')
+String describeAAA(AAA o) {
+ return '${o.name} ${o.method2()} ${o.size} ${o.method1()}';
+}
+
+@pragma('dart2js:noInline')
+String describeOptAAA(AAA o) {
+ return '${o.optName} ${o.optMethod()}';
+}
+
+void testNativeNullAssertions(bool flagEnabled) {
+ nativeTesting();
+ setup();
+ AAA a = makeA();
+ BBB b = BBB();
+
+ Expect.equals(expectedA, describeNativeInterface(a));
+ Expect.equals(expectedB, describeNativeInterface(b));
+
+ Expect.equals(expectedA, describeAAA(a));
+
+ AAA x = makeAX(); // This object returns `null`!
+ // Since native members are in a web library, this should throw if null checks
+ // are enabled.
+ var checkExpectation = flagEnabled ? Expect.throws : (f) => f();
+ checkExpectation(() => describeNativeInterface(x));
+ checkExpectation(() => describeAAA(x));
+
+ checkExpectation(() => x.name);
+ checkExpectation(() => x.size);
+ checkExpectation(() => x.method1());
+ checkExpectation(() => x.method2());
+
+ // Now test that a nullable return type does not have a check.
+ Expect.equals(expectedOptA, describeOptNativeInterface(a));
+ Expect.equals(expectedOptB, describeOptNativeInterface(b));
+ Expect.equals(expectedOptX, describeOptNativeInterface(x));
+
+ Expect.equals(expectedOptA, describeOptAAA(a));
+ Expect.equals(expectedOptX, describeOptAAA(x));
+}
+
+void testJSInvocationNullAssertions(bool flagEnabled) {
+ nativeTesting();
+ setup();
+
+ CCC c = makeC();
+ CCC cx = makeCX();
+
+ Expect.equals(expectedC, describeJSInterface(c));
+
+ // Since invocations are in a web library, this should throw if null checks
+ // are enabled.
+ var checkExpectation = flagEnabled ? Expect.throws : (f) => f();
+ checkExpectation(() => describeJSInterface(cx));
+
+ // Test that invocations with a nullable static type do not have checks.
+ Expect.equals(expectedOptC, describeOptJSInterface(c));
+ Expect.equals(expectedOptCX, describeOptJSInterface(cx));
+}