Merge CL for js-interop crash fix.
BUG=
patch from issue 1485823004 at patchset 20001 (http://crrev.com/1485823004#ps20001)
diff --git a/pkg/compiler/lib/src/apiimpl.dart b/pkg/compiler/lib/src/apiimpl.dart
index fdcbd37..5d6d2b89 100644
--- a/pkg/compiler/lib/src/apiimpl.dart
+++ b/pkg/compiler/lib/src/apiimpl.dart
@@ -69,6 +69,8 @@
hasOption(options, Flags.trustTypeAnnotations),
trustPrimitives:
hasOption(options, Flags.trustPrimitives),
+ trustJSInteropTypeAnnotations:
+ hasOption(options, Flags.trustJSInteropTypeAnnotations),
enableMinification: hasOption(options, Flags.minify),
useFrequencyNamer:
!hasOption(options, Flags.noFrequencyBasedMinification),
diff --git a/pkg/compiler/lib/src/commandline_options.dart b/pkg/compiler/lib/src/commandline_options.dart
index 668cb28..03f274e 100644
--- a/pkg/compiler/lib/src/commandline_options.dart
+++ b/pkg/compiler/lib/src/commandline_options.dart
@@ -40,6 +40,8 @@
static const String testMode = '--test-mode';
static const String trustPrimitives = '--trust-primitives';
static const String trustTypeAnnotations = '--trust-type-annotations';
+ static const String trustJSInteropTypeAnnotations =
+ '--experimental-trust-js-interop-type-annotations';
static const String useContentSecurityPolicy = '--csp';
static const String useCpsIr = '--use-cps-ir';
static const String verbose = '--verbose';
diff --git a/pkg/compiler/lib/src/compiler.dart b/pkg/compiler/lib/src/compiler.dart
index 8fab748..2f980bb 100644
--- a/pkg/compiler/lib/src/compiler.dart
+++ b/pkg/compiler/lib/src/compiler.dart
@@ -181,6 +181,7 @@
final bool enableUserAssertions;
final bool trustTypeAnnotations;
final bool trustPrimitives;
+ final bool trustJSInteropTypeAnnotations;
final bool disableTypeInferenceFlag;
final Uri deferredMapUri;
final bool dumpInfo;
@@ -432,6 +433,7 @@
this.enableUserAssertions: false,
this.trustTypeAnnotations: false,
this.trustPrimitives: false,
+ this.trustJSInteropTypeAnnotations: false,
bool disableTypeInferenceFlag: false,
this.maxConcreteTypeSize: 5,
this.enableMinification: false,
diff --git a/pkg/compiler/lib/src/dart2js.dart b/pkg/compiler/lib/src/dart2js.dart
index fa7aeb7..662316b 100644
--- a/pkg/compiler/lib/src/dart2js.dart
+++ b/pkg/compiler/lib/src/dart2js.dart
@@ -119,6 +119,7 @@
bool dumpInfo = false;
bool allowNativeExtensions = false;
bool trustTypeAnnotations = false;
+ bool trustJSInteropTypeAnnotations = false;
bool checkedMode = false;
// List of provided options that imply that output is expected.
List<String> optionsImplyCompilation = <String>[];
@@ -227,6 +228,11 @@
implyCompilation(argument);
}
+ setTrustJSInteropTypeAnnotations(String argument) {
+ trustJSInteropTypeAnnotations = true;
+ implyCompilation(argument);
+ }
+
setTrustPrimitives(String argument) {
implyCompilation(argument);
}
@@ -326,6 +332,9 @@
new OptionHandler(Flags.trustPrimitives,
(_) => setTrustPrimitives(
Flags.trustPrimitives)),
+ new OptionHandler(Flags.trustJSInteropTypeAnnotations,
+ (_) => setTrustJSInteropTypeAnnotations(
+ Flags.trustJSInteropTypeAnnotations)),
new OptionHandler(r'--help|/\?|/h', (_) => wantHelp = true),
new OptionHandler('--packages=.+', setPackageConfig),
new OptionHandler('--package-root=.+|-p.+', setPackageRoot),
diff --git a/pkg/compiler/lib/src/js_backend/js_interop_analysis.dart b/pkg/compiler/lib/src/js_backend/js_interop_analysis.dart
index e6cce5b..4199c69 100644
--- a/pkg/compiler/lib/src/js_backend/js_interop_analysis.dart
+++ b/pkg/compiler/lib/src/js_backend/js_interop_analysis.dart
@@ -116,6 +116,10 @@
ClassElement classElement = element;
+ // Skip classes that are completely unreachable. This should only happen
+ // when all of jsinterop types are unreachable from main.
+ if (!backend.compiler.world.isImplemented(classElement)) return;
+
if (!classElement
.implementsInterface(backend.jsJavaScriptObjectClass)) {
backend.reporter.reportErrorMessage(classElement,
diff --git a/pkg/compiler/lib/src/native/behavior.dart b/pkg/compiler/lib/src/native/behavior.dart
index 7dbab30..63d5bd6 100644
--- a/pkg/compiler/lib/src/native/behavior.dart
+++ b/pkg/compiler/lib/src/native/behavior.dart
@@ -645,12 +645,26 @@
static NativeBehavior ofMethod(FunctionElement method, Compiler compiler) {
FunctionType type = method.computeType(compiler.resolution);
var behavior = new NativeBehavior();
- behavior.typesReturned.add(type.returnType);
+ var returnType = type.returnType;
+ bool isInterop = method.isJsInterop;
+ // Note: For dart:html and other internal libraries we maintain, we can
+ // trust the return type and use it to limit what we enqueue. We have to
+ // be more conservative about JS interop types and assume they can return
+ // anything (unless the user provides the experimental flag to trust the
+ // type of js-interop APIs). We do restrict the allocation effects and say
+ // that interop calls create only interop types (which may be unsound if
+ // an interop call returns a DOM type and declares a dynamic return type,
+ // but otherwise we would include a lot of code by default).
+ // TODO(sigmund,sra): consider doing something better for numeric types.
+ behavior.typesReturned.add(
+ !isInterop || compiler.trustJSInteropTypeAnnotations ? returnType
+ : const DynamicType());
if (!type.returnType.isVoid) {
// Declared types are nullable.
behavior.typesReturned.add(compiler.coreTypes.nullType);
}
- behavior._capture(type, compiler.resolution);
+ behavior._capture(type, compiler.resolution,
+ isInterop: isInterop, compiler: compiler);
// TODO(sra): Optional arguments are currently missing from the
// DartType. This should be fixed so the following work-around can be
@@ -668,10 +682,15 @@
Resolution resolution = compiler.resolution;
DartType type = field.computeType(resolution);
var behavior = new NativeBehavior();
- behavior.typesReturned.add(type);
+ bool isInterop = field.isJsInterop;
+ // TODO(sigmund,sra): consider doing something better for numeric types.
+ behavior.typesReturned.add(
+ !isInterop || compiler.trustJSInteropTypeAnnotations ? type
+ : const DynamicType());
// Declared types are nullable.
behavior.typesReturned.add(resolution.coreTypes.nullType);
- behavior._capture(type, resolution);
+ behavior._capture(type, resolution,
+ isInterop: isInterop, compiler: compiler);
behavior._overrideWithAnnotations(field, compiler);
return behavior;
}
@@ -765,17 +784,50 @@
/// Models the behavior of Dart code receiving instances and methods of [type]
/// from native code. We usually start the analysis by capturing a native
/// method that has been used.
- void _capture(DartType type, Resolution resolution) {
+ ///
+ /// We assume that JS-interop APIs cannot instantiate Dart types or
+ /// non-JSInterop native types.
+ void _capture(DartType type, Resolution resolution,
+ {bool isInterop: false, Compiler compiler}) {
type.computeUnaliased(resolution);
type = type.unaliased;
if (type is FunctionType) {
FunctionType functionType = type;
- _capture(functionType.returnType, resolution);
+ _capture(functionType.returnType, resolution,
+ isInterop: isInterop, compiler: compiler);
for (DartType parameter in functionType.parameterTypes) {
_escape(parameter, resolution);
}
} else {
- typesInstantiated.add(type);
+ DartType instantiated = null;
+ JavaScriptBackend backend = compiler?.backend;
+ if (!isInterop) {
+ typesInstantiated.add(type);
+ } else {
+ if (type.element != null && type.element.isNative) {
+ // Any declared native or interop type (isNative implies isJsInterop)
+ // is assumed to be allocated.
+ typesInstantiated.add(type);
+ }
+
+ if (!compiler.trustJSInteropTypeAnnotations ||
+ type.isDynamic || type.isObject) {
+ // By saying that only JS-interop types can be created, we prevent
+ // pulling in every other native type (e.g. all of dart:html) when a
+ // JS interop API returns dynamic or when we don't trust the type
+ // annotations. This means that to some degree we still use the return
+ // type to decide whether to include native types, even if we don't
+ // trust the type annotation.
+ ClassElement cls = backend.jsJavaScriptObjectClass;
+ cls.ensureResolved(resolution);
+ typesInstantiated.add(cls.thisType);
+ } else {
+ // Otherwise, when the declared type is a Dart type, we do not
+ // register an allocation because we assume it cannot be instantiated
+ // from within the JS-interop code. It must have escaped from another
+ // API.
+ }
+ }
}
}
diff --git a/pkg/compiler/lib/src/ssa/builder.dart b/pkg/compiler/lib/src/ssa/builder.dart
index 0258d7d..a913c77 100644
--- a/pkg/compiler/lib/src/ssa/builder.dart
+++ b/pkg/compiler/lib/src/ssa/builder.dart
@@ -5852,7 +5852,7 @@
&& params.optionalParametersAreNamed;
}
- HForeignCode invokeJsInteropFunction(Element element,
+ HForeignCode invokeJsInteropFunction(FunctionElement element,
List<HInstruction> arguments,
SourceInformation sourceInformation) {
assert(element.isJsInterop);
@@ -5886,6 +5886,9 @@
var nativeBehavior = new native.NativeBehavior()
..codeTemplate = codeTemplate;
+ if (compiler.trustJSInteropTypeAnnotations) {
+ nativeBehavior.typesReturned.add(constructor.enclosingClass.thisType);
+ }
return new HForeignCode(
codeTemplate,
backend.dynamicType, filteredArguments,
@@ -5905,34 +5908,43 @@
arguments = arguments.where((arg) => arg != null).toList();
var inputs = <HInstruction>[target]..addAll(arguments);
- js.Template codeTemplate;
- if (element.isGetter) {
- codeTemplate = js.js.parseForeignJS("#");
- } else if (element.isSetter) {
- codeTemplate = js.js.parseForeignJS("# = #");
- } else {
- FunctionElement function = element;
- FunctionSignature params = function.functionSignature;
+ var nativeBehavior = new native.NativeBehavior()
+ ..sideEffects.setAllSideEffects();
- var argsStub = <String>[];
- for (int i = 0; i < arguments.length; i++) {
- argsStub.add('#');
- }
+ DartType type = element.isConstructor ?
+ element.enclosingClass.thisType : element.type.returnType;
+ // Native behavior effects here are similar to native/behavior.dart.
+ // The return type is dynamic if we don't trust js-interop type
+ // declarations.
+ nativeBehavior.typesReturned.add(
+ compiler.trustJSInteropTypeAnnotations ? type : const DynamicType());
- if (element.isConstructor) {
- codeTemplate = js.js.parseForeignJS("new #(${argsStub.join(",")})");
- } else {
- codeTemplate = js.js.parseForeignJS("#(${argsStub.join(",")})");
- }
+ // The allocation effects include the declared type if it is native (which
+ // includes js interop types).
+ if (type.element != null && type.element.isNative) {
+ nativeBehavior.typesInstantiated.add(type);
}
- var nativeBehavior = new native.NativeBehavior()
- ..codeTemplate = codeTemplate
- ..typesReturned.add(
- backend.jsJavaScriptObjectClass.thisType)
- ..typesInstantiated.add(
- backend.jsJavaScriptObjectClass.thisType)
- ..sideEffects.setAllSideEffects();
+ // It also includes any other JS interop type if we don't trust the
+ // annotation or if is declared too broad.
+ if (!compiler.trustJSInteropTypeAnnotations || type.isObject ||
+ type.isDynamic) {
+ nativeBehavior.typesInstantiated.add(
+ backend.jsJavaScriptObjectClass.thisType);
+ }
+
+ String code;
+ if (element.isGetter) {
+ code = "#";
+ } else if (element.isSetter) {
+ code = "# = #";
+ } else {
+ var args = new List.filled(arguments.length, '#').join(',');
+ code = element.isConstructor ? "new #($args)" : "#($args)";
+ }
+ js.Template codeTemplate = js.js.parseForeignJS(code);
+ nativeBehavior.codeTemplate = codeTemplate;
+
return new HForeignCode(
codeTemplate,
backend.dynamicType, inputs,
diff --git a/tests/compiler/dart2js/compiler_helper.dart b/tests/compiler/dart2js/compiler_helper.dart
index d3ac2ac..2604401 100644
--- a/tests/compiler/dart2js/compiler_helper.dart
+++ b/tests/compiler/dart2js/compiler_helper.dart
@@ -43,7 +43,8 @@
import 'output_collector.dart';
export 'output_collector.dart';
-/// Compile [code] and returns the code for [entry].
+/// Compile [code] and returns either the code for [entry] or, if [returnAll] is
+/// true, the code for the entire program.
///
/// If [check] is provided, it is executed on the code for [entry] before
/// returning. If [useMock] is `true` the [MockCompiler] is used for
@@ -54,8 +55,11 @@
bool minify: false,
bool analyzeAll: false,
bool disableInlining: true,
+ bool trustJSInteropTypeAnnotations: false,
bool useMock: false,
- void check(String generated)}) async {
+ void check(String generatedEntry),
+ bool returnAll: false}) async {
+ OutputCollector outputCollector = returnAll ? new OutputCollector() : null;
if (useMock) {
// TODO(johnniwinther): Remove this when no longer needed by
// `arithmetic_simplication_test.dart`.
@@ -65,7 +69,9 @@
// compiling a method.
disableTypeInference: true,
enableMinification: minify,
- disableInlining: disableInlining);
+ disableInlining: disableInlining,
+ trustJSInteropTypeAnnotations: trustJSInteropTypeAnnotations,
+ outputProvider: outputCollector);
await compiler.init();
compiler.parseScript(code);
lego.Element element = compiler.mainApp.find(entry);
@@ -89,7 +95,7 @@
if (check != null) {
check(generated);
}
- return generated;
+ return returnAll ? outputCollector.getOutput('', 'js') : generated;
} else {
List<String> options = <String>[
Flags.disableTypeInference];
@@ -102,6 +108,9 @@
if (analyzeAll) {
options.add(Flags.analyzeAll);
}
+ if (trustJSInteropTypeAnnotations) {
+ options.add(Flags.trustJSInteropTypeAnnotations);
+ }
Map<String, String> source;
if (entry != 'main') {
@@ -113,6 +122,7 @@
CompilationResult result = await runCompiler(
memorySourceFiles: source,
options: options,
+ outputProvider: outputCollector,
beforeRun: (compiler) {
if (disableInlining) {
compiler.disableInlining = true;
@@ -126,7 +136,7 @@
if (check != null) {
check(generated);
}
- return generated;
+ return returnAll ? outputCollector.getOutput('', 'js') : generated;
}
}
diff --git a/tests/compiler/dart2js/interop_anonymous_unreachable_test.dart b/tests/compiler/dart2js/interop_anonymous_unreachable_test.dart
new file mode 100644
index 0000000..3f38205
--- /dev/null
+++ b/tests/compiler/dart2js/interop_anonymous_unreachable_test.dart
@@ -0,0 +1,177 @@
+// Copyright (c) 2015, 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.
+
+library tests.dart2js.interop_anonymous_unreachable_test;
+
+import 'dart:async';
+
+import 'package:test/test.dart';
+import 'compiler_helper.dart';
+
+
+main() {
+ test("unreachable code doesn't crash the compiler", () async {
+ // This test is a regression for Issue #24974
+ String generated = await compile("""
+ import 'package:js/js.dart';
+
+ @JS() @anonymous
+ class UniqueLongNameForTesting_A {
+ external factory UniqueLongNameForTesting_A();
+ }
+ main() {}
+ """, returnAll: true);
+
+ // the code should not be included in the output either.
+ expect(generated, isNot(contains("UniqueLongNameForTesting_A")));
+ });
+
+ group('tree-shaking interop types', () {
+ String program = """
+ import 'package:js/js.dart';
+
+ // reachable and allocated
+ @JS() @anonymous
+ class UniqueLongNameForTesting_A {
+ external bool get x;
+ external UniqueLongNameForTesting_D get d;
+ external UniqueLongNameForTesting_E get e;
+ external factory UniqueLongNameForTesting_A(
+ {UniqueLongNameForTesting_B arg0});
+ }
+
+ // visible through the parameter above, but not used.
+ @JS() @anonymous
+ class UniqueLongNameForTesting_B {
+ external factory UniqueLongNameForTesting_B();
+ }
+
+ // unreachable
+ @JS() @anonymous
+ class UniqueLongNameForTesting_C {
+ external factory UniqueLongNameForTesting_C();
+ }
+
+ // visible and reached through `d`.
+ @JS() @anonymous
+ class UniqueLongNameForTesting_D {
+ external factory UniqueLongNameForTesting_D();
+ }
+
+ // visible through `e`, but not reached.
+ @JS() @anonymous
+ class UniqueLongNameForTesting_E {
+ external factory UniqueLongNameForTesting_E();
+ }
+
+ main() {
+ print(new UniqueLongNameForTesting_A().x);
+ print(new UniqueLongNameForTesting_A().d);
+ }
+ """;
+
+ test('no tree-shaking by default', () async {
+ String generated = await compile(program, returnAll: true);
+ expect(generated.contains("UniqueLongNameForTesting_A"), isTrue);
+ expect(generated.contains("UniqueLongNameForTesting_D"), isTrue);
+
+ expect(generated.contains("UniqueLongNameForTesting_B"), isTrue);
+ expect(generated.contains("UniqueLongNameForTesting_C"), isTrue);
+ expect(generated.contains("UniqueLongNameForTesting_E"), isTrue);
+ });
+
+ test('tree-shake when using flag', () async {
+ String generated = await compile(program,
+ trustJSInteropTypeAnnotations: true,
+ returnAll: true);
+ expect(generated.contains("UniqueLongNameForTesting_A"), isTrue);
+ expect(generated.contains("UniqueLongNameForTesting_D"), isTrue);
+
+ expect(generated.contains("UniqueLongNameForTesting_B"), isFalse);
+ expect(generated.contains("UniqueLongNameForTesting_C"), isFalse);
+ expect(generated.contains("UniqueLongNameForTesting_E"), isFalse);
+ });
+ });
+
+ group('tree-shaking other native types', () {
+ String program = """
+ import 'dart:html';
+ import 'package:js/js.dart';
+
+ @JS() @anonymous
+ class UniqueLongNameForTesting_A {
+ external dynamic get x;
+ }
+
+ @JS() @anonymous
+ class UniqueLongNameForTesting_B {
+ external dynamic get y;
+ }
+
+ main() {
+ print(new UniqueLongNameForTesting_A().x);
+ }
+ """;
+
+ test('allocation effect of dynamic excludes native types', () async {
+ String generated = await compile(program, returnAll: true);
+ expect(generated.contains("UniqueLongNameForTesting_A"), isTrue);
+ // any js-interop type could be allocated by `get x`
+ expect(generated.contains("UniqueLongNameForTesting_B"), isTrue);
+ // but we exclude other native types like HTMLAudioElement
+ expect(generated.contains("HTMLAudioElement"), isFalse);
+ });
+
+ test('allocation effect of dynamic excludes native types [flag]', () async {
+ // Trusting types doesn't make a difference.
+ String generated = await compile(program,
+ trustJSInteropTypeAnnotations: true,
+ returnAll: true);
+ expect(generated.contains("UniqueLongNameForTesting_A"), isTrue);
+ expect(generated.contains("UniqueLongNameForTesting_B"), isTrue);
+ expect(generated.contains("HTMLAudioElement"), isFalse);
+ });
+
+ test('declared native types are included in allocation effect', () async {
+ String program2 = """
+ import 'dart:html';
+ import 'package:js/js.dart';
+
+ @JS() @anonymous
+ class UniqueLongNameForTesting_A {
+ external AudioElement get x;
+ }
+
+ main() {
+ print(new UniqueLongNameForTesting_A().x is AudioElement);
+ }
+ """;
+
+ String generated = await compile(program2, returnAll: true);
+ expect(generated.contains("UniqueLongNameForTesting_A"), isTrue);
+ expect(generated.contains("HTMLAudioElement"), isTrue);
+
+ program2 = """
+ import 'dart:html';
+ import 'package:js/js.dart';
+
+ @JS() @anonymous
+ class UniqueLongNameForTesting_A {
+ external dynamic get x;
+ }
+
+ main() {
+ print(new UniqueLongNameForTesting_A().x is AudioElement);
+ }
+ """;
+
+ generated = await compile(program2, returnAll: true);
+ expect(generated.contains("UniqueLongNameForTesting_A"), isTrue);
+ // This extra check is to make sure that we don't include HTMLAudioElement
+ // just because of the is-check. It is optimized away in this case because
+ // we believe it was never instantiated.
+ expect(generated.contains("HTMLAudioElement"), isFalse);
+ });
+ });
+}
diff --git a/tests/compiler/dart2js/mock_compiler.dart b/tests/compiler/dart2js/mock_compiler.dart
index 7456c29..2195fe6 100644
--- a/tests/compiler/dart2js/mock_compiler.dart
+++ b/tests/compiler/dart2js/mock_compiler.dart
@@ -87,6 +87,7 @@
// affected by inlining support.
bool disableInlining: true,
bool trustTypeAnnotations: false,
+ bool trustJSInteropTypeAnnotations: false,
bool enableAsyncAwait: false,
int this.expectedWarnings,
int this.expectedErrors,
@@ -106,6 +107,7 @@
emitJavaScript: emitJavaScript,
preserveComments: preserveComments,
trustTypeAnnotations: trustTypeAnnotations,
+ trustJSInteropTypeAnnotations: trustJSInteropTypeAnnotations,
diagnosticOptions:
new DiagnosticOptions(showPackageWarnings: true),
outputProvider: new LegacyCompilerOutput(outputProvider)) {
diff --git a/tests/html/html.status b/tests/html/html.status
index 56276ae..bba61dd 100644
--- a/tests/html/html.status
+++ b/tests/html/html.status
@@ -9,8 +9,8 @@
[ $compiler == none && ($runtime == dartium || $runtime == drt) ]
js_array_test: Skip # Dartium JSInterop failure
-js_typed_interop_test: Skip # Dartium JSInterop failure
-mirrors_js_typed_interop_test: Skip # Dartium JSInterop failure
+mirrors_js_typed_interop_test: Fail # Missing expected failure (Issue 25044)
+js_typed_interop_side_cast_exp_test: Fail, OK # tests dart2js-specific behavior.
cross_domain_iframe_test: RuntimeError # Dartium JSInterop failure
indexeddb_2_test: Fail # Dartium JSInterop failure. Identity preservation on array deferred copy.
@@ -19,7 +19,6 @@
native_gc_test: Skip # Dartium JSInterop failure
transferables_test: RuntimeError # Dartium JSInterop failure
-
[ $compiler == none && ($runtime == drt || $runtime == dartium ) ]
worker_api_test: Fail # Issue 10223
resource_http_test: Fail # Issue 24203
diff --git a/tests/html/js_dart_to_string_test.dart b/tests/html/js_dart_to_string_test.dart
index 5293053..d2c23f4 100644
--- a/tests/html/js_dart_to_string_test.dart
+++ b/tests/html/js_dart_to_string_test.dart
@@ -3,7 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
@JS()
-library js_typed_interop_test;
+library js_dart_to_string_test;
import 'dart:html';
diff --git a/tests/html/js_typed_interop_anonymous2_exp_test.dart b/tests/html/js_typed_interop_anonymous2_exp_test.dart
new file mode 100644
index 0000000..baebd03
--- /dev/null
+++ b/tests/html/js_typed_interop_anonymous2_exp_test.dart
@@ -0,0 +1,52 @@
+// Copyright (c) 2015, 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.
+//
+// SharedOptions=--experimental-trust-js-interop-type-annotations
+
+// Same test as js_typed_interop_anonymous2, but using the
+// --experimental-trust-js-interop-type-annotations flag.
+library js_typed_interop_anonymous2_exp_test;
+
+import 'dart:html';
+import 'dart:js' as js;
+
+import 'package:js/js.dart';
+import 'package:unittest/unittest.dart';
+import 'package:unittest/html_config.dart';
+
+@JS() @anonymous
+class A {
+ external factory A({B b});
+
+ external B get b;
+}
+
+@JS() @anonymous
+class B {
+ external factory B({C c});
+
+ external C get c;
+}
+
+@JS() @anonymous
+class C {
+ external factory C();
+}
+
+// D is unreachable, and that is OK
+@JS() @anonymous
+class D {
+ external factory D();
+}
+
+main() {
+ useHtmlConfiguration();
+
+ test('simple', () {
+ var b = new B();
+ var a = new A(b: b);
+ expect(a.b, equals(b));
+ expect(b.c, isNull);
+ });
+}
diff --git a/tests/html/js_typed_interop_anonymous2_test.dart b/tests/html/js_typed_interop_anonymous2_test.dart
new file mode 100644
index 0000000..ed0e732
--- /dev/null
+++ b/tests/html/js_typed_interop_anonymous2_test.dart
@@ -0,0 +1,48 @@
+// Copyright (c) 2015, 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.
+
+library js_typed_interop_anonymous2_test;
+
+import 'dart:html';
+import 'dart:js' as js;
+
+import 'package:js/js.dart';
+import 'package:unittest/unittest.dart';
+import 'package:unittest/html_config.dart';
+
+@JS() @anonymous
+class A {
+ external factory A({B b});
+
+ external B get b;
+}
+
+@JS() @anonymous
+class B {
+ external factory B({C c});
+
+ external C get c;
+}
+
+@JS() @anonymous
+class C {
+ external factory C();
+}
+
+// D is unreachable, and that is OK
+@JS() @anonymous
+class D {
+ external factory D();
+}
+
+main() {
+ useHtmlConfiguration();
+
+ test('simple', () {
+ var b = new B();
+ var a = new A(b: b);
+ expect(a.b, equals(b));
+ expect(b.c, isNull);
+ });
+}
diff --git a/tests/html/js_typed_interop_anonymous_exp_test.dart b/tests/html/js_typed_interop_anonymous_exp_test.dart
new file mode 100644
index 0000000..40a04aa
--- /dev/null
+++ b/tests/html/js_typed_interop_anonymous_exp_test.dart
@@ -0,0 +1,36 @@
+// Copyright (c) 2015, 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.
+//
+// SharedOptions=--experimental-trust-js-interop-type-annotations
+
+// Same test as js_typed_interop_anonymous, but using the
+// --experimental-trust-js-interop-type-annotations flag.
+library js_typed_interop_anonymous_exp_test;
+
+import 'dart:html';
+import 'dart:js' as js;
+
+import 'package:js/js.dart';
+import 'package:unittest/unittest.dart';
+import 'package:unittest/html_config.dart';
+
+@JS() @anonymous
+class Literal {
+ external factory Literal({int x, String y, num z});
+
+ external int get x;
+ external String get y;
+ external num get z;
+}
+
+main() {
+ useHtmlConfiguration();
+
+ test('simple', () {
+ var l = new Literal(x: 3, y: "foo");
+ expect(l.x, equals(3));
+ expect(l.y, equals("foo"));
+ expect(l.z, isNull);
+ });
+}
diff --git a/tests/html/js_typed_interop_anonymous_test.dart b/tests/html/js_typed_interop_anonymous_test.dart
new file mode 100644
index 0000000..b2d1593
--- /dev/null
+++ b/tests/html/js_typed_interop_anonymous_test.dart
@@ -0,0 +1,32 @@
+// Copyright (c) 2015, 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.
+
+library js_typed_interop_anonymous_test;
+
+import 'dart:html';
+import 'dart:js' as js;
+
+import 'package:js/js.dart';
+import 'package:unittest/unittest.dart';
+import 'package:unittest/html_config.dart';
+
+@JS() @anonymous
+class Literal {
+ external factory Literal({int x, String y, num z});
+
+ external int get x;
+ external String get y;
+ external num get z;
+}
+
+main() {
+ useHtmlConfiguration();
+
+ test('simple', () {
+ var l = new Literal(x: 3, y: "foo");
+ expect(l.x, equals(3));
+ expect(l.y, equals("foo"));
+ expect(l.z, isNull);
+ });
+}
diff --git a/tests/html/js_typed_interop_anonymous_unreachable_exp_test.dart b/tests/html/js_typed_interop_anonymous_unreachable_exp_test.dart
new file mode 100644
index 0000000..a4e2c96
--- /dev/null
+++ b/tests/html/js_typed_interop_anonymous_unreachable_exp_test.dart
@@ -0,0 +1,34 @@
+// Copyright (c) 2015, 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.
+//
+// SharedOptions=--experimental-trust-js-interop-type-annotations
+
+// Same test as js_typed_interop_anonymous_unreachable, but using the
+// --experimental-trust-js-interop-type-annotations flag.
+library js_typed_interop_anonymous_unreachable_exp_test;
+
+import 'dart:html';
+import 'dart:js' as js;
+
+import 'package:js/js.dart';
+import 'package:unittest/unittest.dart';
+import 'package:unittest/html_config.dart';
+
+@JS() @anonymous
+class Literal {
+ external factory Literal({int x, String y, num z});
+
+ external int get x;
+ external String get y;
+ external num get z;
+}
+
+main() {
+ useHtmlConfiguration();
+ test('nothing to do', () {
+ // This test is empty, but it is a regression for Issue# 24974: dartjs
+ // would crash trying to compile code that used @anonymous and that was
+ // not reachable from main.
+ });
+}
diff --git a/tests/html/js_typed_interop_anonymous_unreachable_test.dart b/tests/html/js_typed_interop_anonymous_unreachable_test.dart
new file mode 100644
index 0000000..ba8758e
--- /dev/null
+++ b/tests/html/js_typed_interop_anonymous_unreachable_test.dart
@@ -0,0 +1,30 @@
+// Copyright (c) 2015, 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.
+
+library js_typed_interop_anonymous_unreachable_test;
+
+import 'dart:html';
+import 'dart:js' as js;
+
+import 'package:js/js.dart';
+import 'package:unittest/unittest.dart';
+import 'package:unittest/html_config.dart';
+
+@JS() @anonymous
+class Literal {
+ external factory Literal({int x, String y, num z});
+
+ external int get x;
+ external String get y;
+ external num get z;
+}
+
+main() {
+ useHtmlConfiguration();
+ test('nothing to do', () {
+ // This test is empty, but it is a regression for Issue# 24974: dartjs
+ // would crash trying to compile code that used @anonymous and that was
+ // not reachable from main.
+ });
+}
diff --git a/tests/html/js_typed_interop_side_cast_exp_test.dart b/tests/html/js_typed_interop_side_cast_exp_test.dart
new file mode 100644
index 0000000..6fd929a
--- /dev/null
+++ b/tests/html/js_typed_interop_side_cast_exp_test.dart
@@ -0,0 +1,53 @@
+// Copyright (c) 2015, 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.
+//
+// SharedOptions=--experimental-trust-js-interop-type-annotations
+
+// Similar test to js_typed_interop_side_cast, but because we are using the
+// --experimental-trust-js-interop-type-annotations flag, we test a slighly
+// different behavior.
+library js_typed_interop_side_cast_exp_test;
+
+import 'dart:html';
+import 'dart:js' as js;
+
+import 'package:js/js.dart';
+import 'package:unittest/unittest.dart';
+import 'package:unittest/html_config.dart';
+
+@JS() @anonymous
+class A {
+ external int get x;
+ external factory A({int x});
+}
+
+@JS() @anonymous
+class B {
+ external int get x;
+ external factory B({int x});
+}
+
+@JS() @anonymous
+class C {
+ external int get x;
+ external factory C({int x});
+}
+
+main() {
+ useHtmlConfiguration();
+
+ test('side-casts work for reachable types', () {
+ new C(x: 3); // make C reachable
+ var a = new A(x: 3);
+ expect(a is C, isTrue);
+ C c = a;
+ expect(c.x, equals(3));
+ });
+
+ // Note: this test would fail without the experimental flag.
+ test('side-casts do not work for unreachable types', () {
+ var a = new A(x: 3);
+ expect(a is B, isFalse);
+ });
+}
diff --git a/tests/html/js_typed_interop_side_cast_test.dart b/tests/html/js_typed_interop_side_cast_test.dart
new file mode 100644
index 0000000..d50e1ac
--- /dev/null
+++ b/tests/html/js_typed_interop_side_cast_test.dart
@@ -0,0 +1,47 @@
+// Copyright (c) 2015, 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.
+
+library js_typed_interop_anonymous2_test;
+
+import 'dart:html';
+import 'dart:js' as js;
+
+import 'package:js/js.dart';
+import 'package:unittest/unittest.dart';
+import 'package:unittest/html_config.dart';
+
+@JS() @anonymous
+class A {
+ external int get x;
+ external factory A({int x});
+}
+
+@JS() @anonymous
+class C {
+ external int get x;
+ external factory C({int x});
+}
+
+@JS() @anonymous
+class B {
+ external int get x;
+ external factory B({int x});
+}
+
+main() {
+ useHtmlConfiguration();
+
+ test('side-casts work for reachable types', () {
+ new C(x: 3); // make C reachable
+ var a = new A(x: 3);
+ expect(a is C, isTrue);
+ C c = a;
+ expect(c.x, equals(3));
+ });
+
+ test('side-casts work for otherwise unreachable types', () {
+ var a = new A(x: 3);
+ expect(a is B, isTrue);
+ });
+}