[ddc] Move native null-checks to definitions

Bug: https://github.com/dart-lang/sdk/issues/42535

Native null-checks are moved to member definitions, and native
member accesses are changed to use this indirection in the case
where the member type can be checked.

Change-Id: I499bb3a4f6a66021dd0ab7930a55e7233c1ce020
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/166785
Reviewed-by: Nicholas Shahan <nshahan@google.com>
Reviewed-by: Sigmund Cherem <sigmund@google.com>
Commit-Queue: Srujan Gaddam <srujzs@google.com>
diff --git a/pkg/dev_compiler/lib/src/kernel/compiler.dart b/pkg/dev_compiler/lib/src/kernel/compiler.dart
index d54ecef..d12313b 100644
--- a/pkg/dev_compiler/lib/src/kernel/compiler.dart
+++ b/pkg/dev_compiler/lib/src/kernel/compiler.dart
@@ -1776,14 +1776,23 @@
   js_ast.Fun _emitNativeFunctionBody(Procedure node) {
     var name = _annotationName(node, isJSAnnotation) ?? node.name.text;
     if (node.isGetter) {
-      return js_ast.Fun([], js.block('{ return this.#; }', [name]));
+      var returnValue = js('this.#', [name]);
+      if (_isNullCheckableNative(node)) {
+        // Add a potential null-check on native getter if type is non-nullable.
+        returnValue = runtimeCall('checkNativeNonNull(#)', [returnValue]);
+      }
+      return js_ast.Fun([], js.block('{ return #; }', [returnValue]));
     } else if (node.isSetter) {
       var params = _emitParameters(node.function);
       return js_ast.Fun(
           params, js.block('{ this.# = #; }', [name, params.last]));
     } else {
-      return js.fun(
-          'function (...args) { return this.#.apply(this, args); }', name);
+      var returnValue = js('this.#.apply(this, args)', [name]);
+      if (_isNullCheckableNative(node)) {
+        // Add a potential null-check on return value if type is non-nullable.
+        returnValue = runtimeCall('checkNativeNonNull(#)', [returnValue]);
+      }
+      return js.fun('function (...args) { return #; }', [returnValue]);
     }
   }
 
@@ -2270,6 +2279,9 @@
       // Fields on a native class are implicitly native.
       // Methods/getters/setters are marked external/native.
       if (member is Field || _isExternal(member)) {
+        // If the native member needs to be null-checked, we require symbolizing
+        // it in order to access the null-check at the member definition.
+        if (_isNullCheckableNative(member)) return true;
         var jsName = _annotationName(member, isJSName);
         return jsName != null && jsName != name;
       } else {
@@ -4215,14 +4227,8 @@
 
   @override
   js_ast.Expression visitPropertyGet(PropertyGet node) {
-    var propertyGet =
-        _emitPropertyGet(node.receiver, node.interfaceTarget, node.name.text);
-    if (_isCheckableNative(node.interfaceTarget)) {
-      // If target is a native getter with a non-nullable type, add a null check
-      // for soundness.
-      return runtimeCall('checkNativeNonNull(#)', [propertyGet]);
-    }
-    return propertyGet;
+    return _emitPropertyGet(
+        node.receiver, node.interfaceTarget, node.name.text);
   }
 
   @override
@@ -4276,8 +4282,8 @@
   }
 
   /// Return whether [member] returns a native object whose type needs to be
-  /// checked.
-  bool _isCheckableNative(Member member) =>
+  /// null-checked. This is true for non-nullable native return types.
+  bool _isNullCheckableNative(Member member) =>
       member != null &&
       member.isExternal &&
       _extensionTypes.isNativeClass(member.enclosingClass) &&
@@ -4355,14 +4361,8 @@
 
   @override
   js_ast.Expression visitMethodInvocation(MethodInvocation node) {
-    var methodCall = _emitMethodCall(
+    return _emitMethodCall(
         node.receiver, node.interfaceTarget, node.arguments, node);
-    if (_isCheckableNative(node.interfaceTarget)) {
-      // If target is a native method with a non-nullable type, add a null check
-      // for soundness.
-      return runtimeCall('checkNativeNonNull(#)', [methodCall]);
-    }
-    return methodCall;
   }
 
   js_ast.Expression _emitMethodCall(Expression receiver, Member target,