[dart2js] Erase static interop type in static invocation

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
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/323505
Reviewed-by: Sigmund Cherem <sigmund@google.com>
Commit-Queue: Srujan Gaddam <srujzs@google.com>
This commit is contained in:
Srujan Gaddam 2023-08-31 09:10:57 +00:00 committed by Commit Queue
parent 54faa31964
commit b25873f11c
2 changed files with 54 additions and 6 deletions

View file

@ -135,8 +135,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;
@ -259,7 +258,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);
@ -290,7 +289,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,
@ -321,10 +320,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;
}
}

View file

@ -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<T, U extends String> on Anonymous<T, U> {
external T get t;
external U 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, '');
}