blob: adc08b70c18638bc16f4d2d02cdd45c92ff4296e [file] [log] [blame]
// Copyright (c) 2018, 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:collection';
import '../compiler/js_metalet.dart' as JS;
import '../compiler/js_names.dart' as JS;
import '../compiler/js_utils.dart' as JS;
import '../js_ast/js_ast.dart' as JS;
import '../js_ast/js_ast.dart' show js;
/// Shared code between Analyzer and Kernel backends.
///
/// This class should only implement functionality that depends purely on JS
/// classes, rather than on Analyzer/Kernel types.
abstract class SharedCompiler<Library> {
/// When inside a `[]=` operator, this will be a non-null value that should be
/// returned by any `return;` statement.
///
/// This lets DDC use the setter method's return value directly.
final List<JS.Identifier> _operatorSetResultStack = [];
JS.Identifier runtimeModule;
final namedArgumentTemp = JS.TemporaryId('opts');
final _privateNames = HashMap<Library, HashMap<String, JS.TemporaryId>>();
/// The list of output module items, in the order they need to be emitted in.
final moduleItems = <JS.ModuleItem>[];
/// When compiling the body of a `operator []=` method, this will be non-null
/// and will indicate the the value that should be returned from any `return;`
/// statements.
JS.Identifier get _operatorSetResult {
var stack = _operatorSetResultStack;
return stack.isEmpty ? null : stack.last;
}
/// The import URI of current library.
Uri get currentLibraryUri;
void enterFunction(String name, List<JS.Parameter> formals,
bool Function() isLastParamMutated) {
if (name == '[]=') {
_operatorSetResultStack.add(isLastParamMutated()
? JS.TemporaryId((formals.last as JS.Identifier).name)
: formals.last);
} else {
_operatorSetResultStack.add(null);
}
}
JS.Block exitFunction(
String name, List<JS.Parameter> formals, JS.Block code) {
if (name == "==" &&
formals.isNotEmpty &&
currentLibraryUri.scheme != 'dart') {
// In Dart `operator ==` methods are not called with a null argument.
// This is handled before calling them. For performance reasons, we push
// this check inside the method, to simplify our `equals` helper.
//
// TODO(jmesserly): in most cases this check is not necessary, because
// the Dart code already handles it (typically by an `is` check).
// Eliminate it when possible.
code = js
.block('{ if (# == null) return false; #; }', [formals.first, code]);
}
var setOperatorResult = _operatorSetResultStack.removeLast();
if (setOperatorResult != null) {
// []= methods need to return the value. We could also address this at
// call sites, but it's less code size to handle inside the operator.
var valueParam = formals.last;
var statements = code.statements;
if (statements.isEmpty || !statements.last.alwaysReturns) {
statements.add(JS.Return(setOperatorResult));
}
if (!identical(setOperatorResult, valueParam)) {
// If the value parameter was mutated, then we use a temporary
// variable to track the initial value
formals.last = setOperatorResult;
code = js
.block('{ let # = #; #; }', [valueParam, setOperatorResult, code]);
}
}
return code;
}
/// Emits a return statement `return <value>;`, handling special rules for
/// the `operator []=` method.
JS.Statement emitReturnStatement(JS.Expression value) {
if (_operatorSetResult != null) {
var result = JS.Return(_operatorSetResult);
return value != null ? JS.Block([value.toStatement(), result]) : result;
}
return value != null ? value.toReturn() : JS.Return();
}
/// Prepends the `dart.` and then uses [js.call] to parse the specified JS
/// [code] template, passing [args].
///
/// For example:
///
/// runtimeCall('asInt(#)', expr)
///
/// Generates a JS AST representing:
///
/// dart.asInt(<expr>)
///
JS.Expression runtimeCall(String code, [args]) {
if (args != null) {
var newArgs = <Object>[runtimeModule];
if (args is Iterable) {
newArgs.addAll(args);
} else {
newArgs.add(args);
}
args = newArgs;
} else {
args = runtimeModule;
}
return js.call('#.$code', args);
}
/// Calls [runtimeCall] and uses `toStatement()` to convert the resulting
/// expression into a statement.
JS.Statement runtimeStatement(String code, [args]) {
return runtimeCall(code, args).toStatement();
}
JS.TemporaryId emitPrivateNameSymbol(Library library, String name) {
return _privateNames.putIfAbsent(library, () => HashMap()).putIfAbsent(name,
() {
var idName = name;
if (idName.endsWith('=')) {
idName = idName.replaceAll('=', '_');
}
var id = JS.TemporaryId(idName);
moduleItems.add(
js.statement('const # = Symbol(#);', [id, js.string(name, "'")]));
return id;
});
}
/// Emits an expression to set the property [name] on the class [className],
/// with [value].
///
/// This will use `className.name = value` if possible, otherwise it will use
/// `dart.defineValue(className, name, value)`. This is required when
/// `Function.prototype` already defins a getters with the same name.
JS.Expression defineValueOnClass(
JS.Expression className, JS.Expression name, JS.Expression value) {
var args = [className, name, value];
if (name is JS.LiteralString &&
JS.isFunctionPrototypeGetter(name.valueWithoutQuotes)) {
return runtimeCall('defineValue(#, #, #)', args);
}
return js.call('#.# = #', args);
}
}