Skip to content

Commit a0c438a

Browse files
committed
cpsir: implementation of jsinterop in cps ir
R=jacobr@google.com Review URL: https://codereview.chromium.org/1688433006 .
1 parent f2fd68c commit a0c438a

File tree

6 files changed

+193
-18
lines changed

6 files changed

+193
-18
lines changed

pkg/compiler/lib/src/cps_ir/cps_ir_builder.dart

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,10 @@ import '../elements/elements.dart';
2020
import '../io/source_information.dart';
2121
import '../js/js.dart' as js show
2222
js,
23+
objectLiteral,
2324
LiteralStatement,
2425
Template,
26+
InterpolatedExpression,
2527
isIdentityTemplate;
2628
import '../native/native.dart' show
2729
NativeBehavior;
@@ -691,6 +693,9 @@ class IrBuilder {
691693
assert(!element.isLocal);
692694
assert(!element.isInstanceMember);
693695
assert(isOpen);
696+
if (program.isJsInterop(element)) {
697+
return buildInvokeJsInteropMember(element, arguments);
698+
}
694699
return addPrimitive(
695700
new ir.InvokeStatic(element, selector, arguments, sourceInformation));
696701
}
@@ -2066,6 +2071,86 @@ class IrBuilder {
20662071
buildReturn(value: value, sourceInformation: source);
20672072
}
20682073

2074+
static _isNotNull(ir.Primitive value) =>
2075+
value is! ir.Constant || !value.value.isNull;
2076+
2077+
/// Builds a call to a resolved js-interop element.
2078+
ir.Primitive buildInvokeJsInteropMember(FunctionElement element,
2079+
List<ir.Primitive> arguments) {
2080+
program.addNativeMethod(element);
2081+
String target = program.getJsInteropTargetPath(element);
2082+
// Strip off trailing arguments that were not specified.
2083+
// TODO(jacobr,sigmund): assert that the trailing arguments are all null.
2084+
// TODO(jacobr): rewrite named arguments to an object literal matching
2085+
// the factory constructor case.
2086+
var inputs = arguments.where(_isNotNull).toList();
2087+
2088+
var behavior = new NativeBehavior()..sideEffects.setAllSideEffects();
2089+
DartType type = element.isConstructor ?
2090+
element.enclosingClass.thisType : element.type.returnType;
2091+
// Native behavior effects here are similar to native/behavior.dart.
2092+
// The return type is dynamic if we don't trust js-interop type
2093+
// declarations.
2094+
behavior.typesReturned.add(
2095+
program.trustJSInteropTypeAnnotations ? type : const DynamicType());
2096+
2097+
// The allocation effects include the declared type if it is native (which
2098+
// includes js interop types).
2099+
if (type.element != null && program.isNative(type.element)) {
2100+
behavior.typesInstantiated.add(type);
2101+
}
2102+
2103+
// It also includes any other JS interop type if we don't trust the
2104+
// annotation or if is declared too broad.
2105+
if (!program.trustJSInteropTypeAnnotations || type.isObject ||
2106+
type.isDynamic) {
2107+
behavior.typesInstantiated.add(program.jsJavascriptObjectType);
2108+
}
2109+
2110+
String code;
2111+
if (element.isGetter) {
2112+
code = target;
2113+
} else if (element.isSetter) {
2114+
code = "$target = #";
2115+
} else {
2116+
var args = new List.filled(inputs.length, '#').join(',');
2117+
code = element.isConstructor ? "new $target($args)" : "$target($args)";
2118+
}
2119+
return buildForeignCode(js.js.parseForeignJS(code), inputs, behavior);
2120+
// TODO(sigmund): should we record the source-information here?
2121+
}
2122+
2123+
/// Builds an object literal that results from invoking a factory constructor
2124+
/// of a js-interop anonymous type.
2125+
ir.Primitive buildJsInteropObjectLiteral(ConstructorElement constructor,
2126+
List<ir.Primitive> arguments, {SourceInformation source}) {
2127+
assert(program.isJsInteropAnonymous(constructor));
2128+
program.addNativeMethod(constructor);
2129+
FunctionSignature params = constructor.functionSignature;
2130+
int i = 0;
2131+
var filteredArguments = <ir.Primitive>[];
2132+
var entries = new Map<String, js.Expression>();
2133+
params.orderedForEachParameter((ParameterElement parameter) {
2134+
// TODO(jacobr): throw if parameter names do not match names of property
2135+
// names in the class.
2136+
assert (parameter.isNamed);
2137+
ir.Primitive argument = arguments[i++];
2138+
if (_isNotNull(argument)) {
2139+
filteredArguments.add(argument);
2140+
entries[parameter.name] =
2141+
new js.InterpolatedExpression(filteredArguments.length - 1);
2142+
}
2143+
});
2144+
var code = new js.Template(null, js.objectLiteral(entries));
2145+
var behavior = new NativeBehavior();
2146+
if (program.trustJSInteropTypeAnnotations) {
2147+
behavior.typesReturned.add(constructor.enclosingClass.thisType);
2148+
}
2149+
2150+
// TODO(sigmund): should we record the source-information here?
2151+
return buildForeignCode(code, filteredArguments, behavior);
2152+
}
2153+
20692154
/// Create a blocks of [statements] by applying [build] to all reachable
20702155
/// statements. The first statement is assumed to be reachable.
20712156
// TODO(johnniwinther): Type [statements] as `Iterable` when `NodeList` uses
@@ -2551,6 +2636,13 @@ class IrBuilder {
25512636
Selector selector =
25522637
new Selector(SelectorKind.CALL, element.memberName, callStructure);
25532638
ClassElement cls = element.enclosingClass;
2639+
if (program.isJsInterop(element)) {
2640+
if (program.isJsInteropAnonymous(element)) {
2641+
return buildJsInteropObjectLiteral(element, arguments,
2642+
source: sourceInformation);
2643+
}
2644+
return buildInvokeJsInteropMember(element, arguments);
2645+
}
25542646
if (program.requiresRuntimeTypesFor(cls)) {
25552647
InterfaceType interface = type;
25562648
Iterable<ir.Primitive> typeArguments =

pkg/compiler/lib/src/cps_ir/cps_ir_builder_task.dart

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -796,7 +796,10 @@ class IrBuilderVisitor extends ast.Visitor<ir.Primitive>
796796

797797
/// Creates a primitive for the default value of [parameter].
798798
ir.Primitive translateDefaultValue(ParameterElement parameter) {
799-
if (parameter.initializer == null) {
799+
if (parameter.initializer == null ||
800+
// TODO(sigmund): JS doesn't support default values, so this should be
801+
// reported as an error earlier (Issue #25759).
802+
backend.isJsInterop(parameter.functionDeclaration)) {
800803
return irBuilder.buildNullConstant();
801804
} else {
802805
return inlineConstant(parameter.executableContext, parameter.initializer);
@@ -4018,4 +4021,22 @@ class GlobalProgramInformation {
40184021
void addNativeMethod(FunctionElement function) {
40194022
_backend.emitter.nativeEmitter.nativeMethods.add(function);
40204023
}
4024+
4025+
bool get trustJSInteropTypeAnnotations =>
4026+
_compiler.trustJSInteropTypeAnnotations;
4027+
4028+
bool isNative(ClassElement element) => _backend.isNative(element);
4029+
4030+
bool isJsInterop(FunctionElement element) => _backend.isJsInterop(element);
4031+
4032+
bool isJsInteropAnonymous(FunctionElement element) =>
4033+
_backend.jsInteropAnalysis.hasAnonymousAnnotation(element.contextClass);
4034+
4035+
String getJsInteropTargetPath(FunctionElement element) {
4036+
return '${_backend.namer.fixedBackendPath(element)}.'
4037+
'${_backend.getFixedBackendName(element)}';
4038+
}
4039+
4040+
DartType get jsJavascriptObjectType =>
4041+
_backend.helpers.jsJavaScriptObjectClass.thisType;
40214042
}

pkg/compiler/lib/src/cps_ir/inline.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -395,6 +395,7 @@ class InliningVisitor extends TrampolineRecursiveVisitor {
395395
// AST node, targets that are asynchronous or generator functions, or
396396
// targets containing a try statement.
397397
if (!target.hasNode) return null;
398+
if (backend.isJsInterop(target)) return null;
398399
if (target.asyncMarker != AsyncMarker.SYNC) return null;
399400
// V8 does not optimize functions containing a try statement. Inlining
400401
// code containing a try statement will make the optimizable calling code

tests/html/html.status

Lines changed: 4 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ custom/element_upgrade_test: Fail # Issue 17298
5151
[ $compiler == dart2js && $browser ]
5252
custom/created_callback_test: Fail # Support for created constructor. Issue 14835
5353
fontface_loaded_test: Fail # Support for promises.
54+
js_typed_interop_default_arg_test/default_value: MissingCompileTimeError # Issue #25759
5455

5556
[ $compiler == dart2js && ($runtime == safari || $runtime == safarimobilesim || $runtime == ff || $ie) ]
5657
custom/entered_left_view_test/viewless_document: Fail # Polyfill does not handle this
@@ -441,20 +442,9 @@ custom_elements_23127_test/c2t: RuntimeError # Need custom element support #2548
441442
custom_elements_test/innerHtml: RuntimeError # Need custom element support #25484
442443
custom_elements_test/register: RuntimeError # Need custom element support #25484
443444

444-
# New js-interop support via package:js is only implemented in SSA:
445-
js_array_test: RuntimeError # Need package:js support #24978
446-
js_dart_to_string_test: RuntimeError # Need package:js support #24978
447-
js_function_getter_test/call: RuntimeError # Need package:js support #24978
448-
js_function_getter_test: RuntimeError # Need package:js support #24978
449-
js_function_getter_trust_types_test: RuntimeError # Need package:js support #24978
450-
js_typed_interop_anonymous2_exp_test: RuntimeError # Need package:js support #24978
451-
js_typed_interop_anonymous2_test: RuntimeError # Need package:js support #24978
452-
js_typed_interop_anonymous_exp_test: RuntimeError # Need package:js support #24978
453-
js_typed_interop_anonymous_test: RuntimeError # Need package:js support #24978
454-
js_typed_interop_side_cast_exp_test: RuntimeError # Need package:js support #24978
455-
js_typed_interop_side_cast_test: RuntimeError # Need package:js support #24978
456-
js_typed_interop_test: RuntimeError # Need package:js support #24978
457-
mirrors_js_typed_interop_test: RuntimeError # Need package:js support #24978
445+
js_typed_interop_side_cast_exp_test: RuntimeError # Corner case in package:js that we might want to remove (See comment in #24978).
446+
js_typed_interop_test/static_method_tearoff_1: RuntimeError # Tree-shaking a used tear-off (#24978, #25720).
447+
js_typed_interop_default_arg_test/explicit_argument: RuntimeError # Tree-shaking a used tear-off (#24978, #25720).
458448

459449
# These are raw dart:js tests that fail due to bugs in the CPS IR:
460450
js_test/Dart_functions: RuntimeError # Tree-shaking an escaping closure #25720
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
@JS()
6+
library js_typed_interop_test;
7+
8+
import 'dart:html';
9+
10+
import 'package:expect/expect.dart' show NoInline;
11+
import 'package:js/js.dart';
12+
import 'package:unittest/unittest.dart';
13+
import 'package:unittest/html_config.dart';
14+
15+
_injectJs() {
16+
document.body.append(new ScriptElement()
17+
..type = 'text/javascript'
18+
..innerHtml = r"""
19+
var Foo = {
20+
get42: function(b) { return arguments.length >= 1 ? b : 42; },
21+
get43: function(b) { return arguments.length >= 1 ? b : 43; }
22+
};
23+
""");
24+
}
25+
26+
@JS()
27+
class Foo {
28+
// Note: it's invalid to provide a default value.
29+
external static num get42([num b
30+
= 3 /// default_value: compile-time error
31+
]);
32+
external static num get43([num b]);
33+
}
34+
35+
main() {
36+
_injectJs();
37+
useHtmlConfiguration();
38+
39+
test('call directly from dart', () {
40+
expect(Foo.get42(2), 2);
41+
expect(Foo.get42(), 42);
42+
});
43+
44+
test('call tearoff from dart with arg', () {
45+
var f = Foo.get42;
46+
expect(f(2), 2); /// explicit_argument: ok
47+
});
48+
49+
test('call tearoff from dart with default', () {
50+
var f = Foo.get42;
51+
// Note: today both SSA and CPS remove the extra argument on static calls,
52+
// but they fail to do so on tearoffs.
53+
expect(f(), 3); /// default_value: continued
54+
55+
f = Foo.get43;
56+
expect(f(), 43);
57+
});
58+
}

tests/html/js_typed_interop_test.dart

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ class Foo {
124124
external callClosureWithArg1(Function closure, arg1);
125125
external callClosureWithArg2(Function closure, arg1, arg2);
126126
external Bar getBar();
127+
127128
external static num multiplyDefault2(num a, [num b]);
128129
}
129130

@@ -277,14 +278,26 @@ main() {
277278
});
278279
});
279280

280-
group('static method', () {
281-
test('call from dart', () {
281+
group('static_method_call', () {
282+
test('call directly from dart', () {
282283
expect(Foo.multiplyDefault2(6, 7), equals(42));
283284
expect(Foo.multiplyDefault2(6), equals(12));
285+
});
286+
});
287+
288+
// Note: these extra groups are added to be able to mark each test
289+
// individually in status files. This should be split as separate test files.
290+
group('static_method_tearoff_1', () {
291+
test('call tearoff from dart', () {
284292
MultiplyWithDefault tearOffMethod = Foo.multiplyDefault2;
285293
expect(tearOffMethod(6, 6), equals(36));
294+
});
295+
});
296+
297+
group('static_method_tearoff_2', () {
298+
test('call tearoff from dart', () {
299+
MultiplyWithDefault tearOffMethod = Foo.multiplyDefault2;
286300
expect(tearOffMethod(6), equals(12));
287-
Function untypedTearOff = Foo.multiplyDefault2;
288301
});
289302
});
290303

0 commit comments

Comments
 (0)