Skip to content

Commit

Permalink
[stable] [dart2js] Erase static interop type in static invocation
Browse files Browse the repository at this point in the history
Static invocations of external factories are casted so that the
result, which is a @staticInterop type, can be treated as the erased
type instead. This CL fixes the issue where the type that it was
casted to was never replaced with the erased type.

Change-Id: Ic6eb529349ea2b5c42f91c2740d501d4f81bc38e
Cherry-pick: https://dart-review.googlesource.com/c/sdk/+/323505
Cherry-pick-request: #53579
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/327120
Reviewed-by: Sigmund Cherem <sigmund@google.com>
  • Loading branch information
srujzs committed Sep 22, 2023
1 parent 9bf2f88 commit 8f28795
Show file tree
Hide file tree
Showing 3 changed files with 64 additions and 6 deletions.
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
## 3.1.3

This is a patch release that:

- Fixes a bug in dart2js which would cause the compiler to crash when using
`@staticInterop` `@anonymous` factory constructors with type parameters (see
issue [#53579] for more details).

[#53579]: https://github.com/dart-lang/sdk/issues/53579

## 3.1.2 - 2023-09-13

This is a patch release that:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,8 +128,7 @@ class _TypeSubstitutor extends ReplacementVisitor {
}
}

/// Erases usage of `@JS` classes that are annotated with `@staticInterop` in
/// favor of `JavaScriptObject`.
/// Erases usage of `@JS` classes that are annotated with `@staticInterop`.
class StaticInteropClassEraser extends Transformer {
final CloneVisitorNotMembers _cloner = CloneVisitorNotMembers();
late final _StaticInteropConstantReplacer _constantReplacer;
Expand Down Expand Up @@ -252,7 +251,7 @@ class StaticInteropClassEraser extends Transformer {
//
// In order to circumvent this, we introduce a new static method that
// clones the factory body and has a return type of
// `JavaScriptObject`. Invocations of the factory are turned into
// the erased type. Invocations of the factory are turned into
// invocations of the static method. The original factory is still kept
// in order to make modular compilations work.
_findOrCreateFactoryStub(node);
Expand Down Expand Up @@ -283,7 +282,7 @@ class StaticInteropClassEraser extends Transformer {
@override
TreeNode visitConstructorInvocation(ConstructorInvocation node) {
if (hasStaticInteropAnnotation(node.target.enclosingClass)) {
// Add a cast so that the result gets typed as `JavaScriptObject`.
// Add a cast so that the result gets typed as the erased type.
var newInvocation = super.visitConstructorInvocation(node) as Expression;
return AsExpression(
newInvocation,
Expand Down Expand Up @@ -314,10 +313,12 @@ class StaticInteropClassEraser extends Transformer {
return StaticInvocation(stub, args, isConst: node.isConst)
..fileOffset = node.fileOffset;
} else {
// Add a cast so that the result gets typed as `JavaScriptObject`.
// Add a cast so that the result gets typed as the erased type.
var newInvocation = super.visitStaticInvocation(node) as Expression;
return AsExpression(
newInvocation, node.target.function.returnType as InterfaceType)
newInvocation,
_eraseStaticInteropType(
node.target.function.returnType as InterfaceType))
..fileOffset = newInvocation.fileOffset;
}
}
Expand Down
47 changes: 47 additions & 0 deletions tests/lib/js/static_interop_test/generic_factory_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// Copyright (c) 2023, 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.

// Test type parameters on @staticInterop factories.

import 'package:js/js.dart';
import 'package:expect/minitest.dart';

@JS()
@staticInterop
class Array<T, U extends String> {
external factory Array(T t, U u);
factory Array.nonExternal(T t, U u) => Array(t, u);
}

extension on Array {
external Object operator [](int index);
}

@JS()
@staticInterop
@anonymous
class Anonymous<T, U extends String> {
external factory Anonymous({T? t, U? u});
factory Anonymous.nonExternal({T? t, U? u}) => Anonymous(t: t, u: u);
}

extension AnonymousExtension on Anonymous {
external Object? get t;
external Object? get u;
}

void main() {
final arr1 = Array(true, '');
expect(arr1[0], true);
expect(arr1[1], '');
final arr2 = Array<bool, String>(false, '');
expect(arr2[0], false);
expect(arr2[1], '');
final anon1 = Anonymous(t: true, u: '');
expect(anon1.t, true);
expect(anon1.u, '');
final anon2 = Anonymous<bool, String>(t: false, u: '');
expect(anon2.t, false);
expect(anon2.u, '');
}

0 comments on commit 8f28795

Please sign in to comment.