blob: 6c0624ef5cbe352ae4e1f5c3934a297d56249608 [file] [log] [blame]
// Copyright (c) 2021, 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:kernel/ast.dart';
import 'package:kernel/core_types.dart';
import 'package:kernel/kernel.dart';
/// Replaces js_util methods with inline calls to foreign_helper JS which
/// emits the code as a JavaScript code fragment.
class JsUtilOptimizer extends Transformer {
final Procedure _jsTarget;
final Procedure _getPropertyTarget;
final Procedure _setPropertyTarget;
final Procedure _setPropertyUncheckedTarget;
/// Dynamic members in js_util that interop allowed.
static final Iterable<String> _allowedInteropJsUtilMembers = <String>[
'callConstructor',
'callMethod',
'getProperty',
'jsify',
'newObject',
'setProperty'
];
final Iterable<Procedure> _allowedInteropJsUtilTargets;
final Procedure _allowInteropTarget;
JsUtilOptimizer(CoreTypes coreTypes)
: _jsTarget =
coreTypes.index.getTopLevelMember('dart:_foreign_helper', 'JS'),
_getPropertyTarget =
coreTypes.index.getTopLevelMember('dart:js_util', 'getProperty'),
_setPropertyTarget =
coreTypes.index.getTopLevelMember('dart:js_util', 'setProperty'),
_setPropertyUncheckedTarget = coreTypes.index
.getTopLevelMember('dart:js_util', '_setPropertyUnchecked'),
_allowInteropTarget =
coreTypes.index.getTopLevelMember('dart:js', 'allowInterop'),
_allowedInteropJsUtilTargets = _allowedInteropJsUtilMembers.map(
(member) =>
coreTypes.index.getTopLevelMember('dart:js_util', member)) {}
/// Replaces js_util method calls with optimization when possible.
///
/// Lowers `getProperty` for any argument type straight to JS fragment call.
/// Lowers `setProperty` to `_setPropertyUnchecked` for values that are
/// not Function type and guaranteed to be interop allowed.
@override
visitStaticInvocation(StaticInvocation node) {
if (node.target == _getPropertyTarget) {
node = _lowerGetProperty(node);
} else if (node.target == _setPropertyTarget) {
node = _lowerSetProperty(node);
}
node.transformChildren(this);
return node;
}
/// Lowers the given js_util `getProperty` call to the foreign_helper JS call
/// for any argument type. Lowers `getProperty(o, name)` to
/// `JS('Object|Null', '#.#', o, name)`.
StaticInvocation _lowerGetProperty(StaticInvocation node) {
Arguments arguments = node.arguments;
assert(arguments.types.isEmpty);
assert(arguments.positional.length == 2);
assert(arguments.named.isEmpty);
return StaticInvocation(
_jsTarget,
Arguments(
[
StringLiteral("Object|Null"),
StringLiteral("#.#"),
...arguments.positional
],
// TODO(rileyporter): Copy type from getProperty when it's generic.
types: [DynamicType()],
)..fileOffset = arguments.fileOffset)
..fileOffset = node.fileOffset;
}
/// Lowers the given js_util `setProperty` call to `_setPropertyUnchecked`
/// when the additional validation checks in `setProperty` can be elided.
/// Removing the checks allows further inlining by the compilers.
StaticInvocation _lowerSetProperty(StaticInvocation node) {
Arguments arguments = node.arguments;
assert(arguments.types.isEmpty);
assert(arguments.positional.length == 3);
assert(arguments.named.isEmpty);
if (!_allowedInterop(arguments.positional.last)) {
return node;
}
return StaticInvocation(_setPropertyUncheckedTarget, arguments)
..fileOffset = node.fileOffset;
}
/// Returns whether the given TreeNode is guaranteed to be allowed to interop
/// with JS.
///
/// Returns true when the node is guaranteed to be not a function:
/// - has a DartType that is NullType or an InterfaceType that is not
/// Function or Object
/// Also returns true for allowed method calls within the JavaScript domain:
/// - dart:_foreign_helper JS
/// - dart:js `allowInterop`
/// - dart:js_util and any of the `_allowedInteropJsUtilMembers`
bool _allowedInterop(TreeNode node) {
// TODO(rileyporter): Detect functions that have been wrapped at some point
// with `allowInterop`
// TODO(rileyporter): Use staticTypeContext to generalize type checking and
// allow more non-function types. Currently, we skip all literal types.
var checkType;
if (node is VariableGet) {
checkType = node.variable.type;
}
if (node is StaticInvocation) {
if (node.target == _allowInteropTarget) return true;
if (node.target == _jsTarget) return true;
if (_allowedInteropJsUtilTargets.contains(node.target)) return true;
checkType = node.target.function.returnType;
}
if (checkType is InterfaceType) {
return checkType.classNode.name != 'Function' &&
checkType.classNode.name != 'Object';
} else {
// Only other DartType guaranteed to not be a function.
return checkType is NullType;
}
}
}