Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit 73fdf19

Browse files
kallentucommit-bot@chromium.org
authored andcommitted
[dart2js] Add variance table to RTI and updated subtyping wrt variance.
Change-Id: Iac4c28d295e883997acdc2e6c7084739e60be9fd Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/126403 Commit-Queue: Kallen Tu <kallentu@google.com> Reviewed-by: Mayank Patke <fishythefish@google.com>
1 parent 58dde43 commit 73fdf19

File tree

10 files changed

+198
-27
lines changed

10 files changed

+198
-27
lines changed

pkg/compiler/lib/src/js_emitter/startup_emitter/fragment_emitter.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2053,6 +2053,7 @@ class FragmentEmitter {
20532053
initField(RtiUniverseFieldNames.evalCache, 'new Map()');
20542054
initField(RtiUniverseFieldNames.typeRules, '{}');
20552055
initField(RtiUniverseFieldNames.erasedTypes, '{}');
2056+
initField(RtiUniverseFieldNames.typeParameterVariances, '{}');
20562057
initField(RtiUniverseFieldNames.sharedEmptyArray, '[]');
20572058

20582059
return js.ObjectInitializer(universeFields);

pkg/compiler/lib/src/options.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,9 @@ class CompilerOptions implements DiagnosticOptions {
111111
bool get useCFEConstants =>
112112
languageExperiments[fe.ExperimentalFlag.constantUpdate2018];
113113

114+
/// `true` if variance is enabled.
115+
bool get enableVariance => languageExperiments[fe.ExperimentalFlag.variance];
116+
114117
/// A possibly null state object for kernel compilation.
115118
fe.InitializedCompilerState kernelInitializedCompilerState;
116119

pkg/compiler/lib/src/ssa/builder_kernel.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -381,6 +381,8 @@ class KernelSsaGraphBuilder extends ir.Visitor {
381381
return options.useContentSecurityPolicy;
382382
case 'USE_NEW_RTI':
383383
return options.experimentNewRti;
384+
case 'VARIANCE':
385+
return options.enableVariance;
384386
default:
385387
return null;
386388
}

sdk/lib/_internal/js_runtime/lib/rti.dart

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1315,11 +1315,13 @@ class _Universe {
13151315
'#: new Map(),'
13161316
'#: {},'
13171317
'#: {},'
1318+
'#: {},'
13181319
'#: [],' // shared empty array.
13191320
'}',
13201321
RtiUniverseFieldNames.evalCache,
13211322
RtiUniverseFieldNames.typeRules,
13221323
RtiUniverseFieldNames.erasedTypes,
1324+
RtiUniverseFieldNames.typeParameterVariances,
13231325
RtiUniverseFieldNames.sharedEmptyArray);
13241326
}
13251327

@@ -1334,6 +1336,9 @@ class _Universe {
13341336
static Object erasedTypes(universe) =>
13351337
JS('', '#.#', universe, RtiUniverseFieldNames.erasedTypes);
13361338

1339+
static Object typeParameterVariances(universe) =>
1340+
JS('', '#.#', universe, RtiUniverseFieldNames.typeParameterVariances);
1341+
13371342
static Object _findRule(universe, String targetType) =>
13381343
JS('', '#.#', typeRules(universe), targetType);
13391344

@@ -1365,12 +1370,18 @@ class _Universe {
13651370
}
13661371
}
13671372

1373+
static Object findTypeParameterVariances(universe, String cls) =>
1374+
JS('', '#.#', typeParameterVariances(universe), cls);
1375+
13681376
static void addRules(universe, rules) =>
13691377
_Utils.objectAssign(typeRules(universe), rules);
13701378

13711379
static void addErasedTypes(universe, types) =>
13721380
_Utils.objectAssign(erasedTypes(universe), types);
13731381

1382+
static void addTypeParameterVariances(universe, variances) =>
1383+
_Utils.objectAssign(typeParameterVariances(universe), variances);
1384+
13741385
static Object sharedEmptyArray(universe) =>
13751386
JS('JSArray', '#.#', universe, RtiUniverseFieldNames.sharedEmptyArray);
13761387

@@ -2247,6 +2258,14 @@ class TypeRule {
22472258
JS('', '#.#', rule, supertype);
22482259
}
22492260

2261+
class Variance {
2262+
// TODO(fishythefish): Try bitmask representation.
2263+
static const legacyCovariant = 0;
2264+
static const covariant = 1;
2265+
static const contravariant = 2;
2266+
static const invariant = 3;
2267+
}
2268+
22502269
// -------- Subtype tests ------------------------------------------------------
22512270

22522271
// Future entry point from compiled code.
@@ -2431,10 +2450,41 @@ bool _isSubtypeOfInterface(
24312450
var sArgs = Rti._getInterfaceTypeArguments(s);
24322451
int length = _Utils.arrayLength(sArgs);
24332452
assert(length == _Utils.arrayLength(tArgs));
2453+
2454+
var sVariances;
2455+
bool hasVariances;
2456+
if (JS_GET_FLAG("VARIANCE")) {
2457+
sVariances = _Universe.findTypeParameterVariances(universe, sName);
2458+
hasVariances = sVariances != null;
2459+
assert(!hasVariances || length == _Utils.arrayLength(sVariances));
2460+
}
2461+
24342462
for (int i = 0; i < length; i++) {
24352463
Rti sArg = _castToRti(_Utils.arrayAt(sArgs, i));
24362464
Rti tArg = _castToRti(_Utils.arrayAt(tArgs, i));
2437-
if (!_isSubtype(universe, sArg, sEnv, tArg, tEnv)) return false;
2465+
if (JS_GET_FLAG("VARIANCE")) {
2466+
int sVariance = hasVariances
2467+
? _Utils.asInt(_Utils.arrayAt(sVariances, i))
2468+
: Variance.legacyCovariant;
2469+
switch (sVariance) {
2470+
case Variance.legacyCovariant:
2471+
case Variance.covariant:
2472+
if (!_isSubtype(universe, sArg, sEnv, tArg, tEnv)) return false;
2473+
break;
2474+
case Variance.contravariant:
2475+
if (!_isSubtype(universe, tArg, tEnv, sArg, sEnv)) return false;
2476+
break;
2477+
case Variance.invariant:
2478+
if (!_isSubtype(universe, sArg, sEnv, tArg, tEnv) ||
2479+
!_isSubtype(universe, tArg, tEnv, sArg, sEnv)) return false;
2480+
break;
2481+
default:
2482+
throw StateError(
2483+
"Unknown variance given for subtype check: $sVariance");
2484+
}
2485+
} else {
2486+
if (!_isSubtype(universe, sArg, sEnv, tArg, tEnv)) return false;
2487+
}
24382488
}
24392489
return true;
24402490
}
@@ -2665,6 +2715,10 @@ void testingAddRules(universe, rules) {
26652715
_Universe.addRules(universe, rules);
26662716
}
26672717

2718+
void testingAddTypeParameterVariances(universe, variances) {
2719+
_Universe.addTypeParameterVariances(universe, variances);
2720+
}
2721+
26682722
bool testingIsSubtype(universe, rti1, rti2) {
26692723
return isSubtype(universe, _castToRti(rti1), _castToRti(rti2));
26702724
}

sdk/lib/_internal/js_runtime/lib/shared/embedded_names.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -461,5 +461,6 @@ class RtiUniverseFieldNames {
461461
static String evalCache = 'eC';
462462
static String typeRules = 'tR';
463463
static String erasedTypes = 'eT';
464+
static String typeParameterVariances = 'tPV';
464465
static String sharedEmptyArray = 'sEA';
465466
}

sdk_nnbd/lib/_internal/js_runtime/lib/rti.dart

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1315,11 +1315,13 @@ class _Universe {
13151315
'#: new Map(),'
13161316
'#: {},'
13171317
'#: {},'
1318+
'#: {},'
13181319
'#: [],' // shared empty array.
13191320
'}',
13201321
RtiUniverseFieldNames.evalCache,
13211322
RtiUniverseFieldNames.typeRules,
13221323
RtiUniverseFieldNames.erasedTypes,
1324+
RtiUniverseFieldNames.typeParameterVariances,
13231325
RtiUniverseFieldNames.sharedEmptyArray);
13241326
}
13251327

@@ -1334,6 +1336,9 @@ class _Universe {
13341336
static Object erasedTypes(universe) =>
13351337
JS('', '#.#', universe, RtiUniverseFieldNames.erasedTypes);
13361338

1339+
static Object typeParameterVariances(universe) =>
1340+
JS('', '#.#', universe, RtiUniverseFieldNames.typeParameterVariances);
1341+
13371342
static Object _findRule(universe, String targetType) =>
13381343
JS('', '#.#', typeRules(universe), targetType);
13391344

@@ -1365,12 +1370,18 @@ class _Universe {
13651370
}
13661371
}
13671372

1373+
static Object findTypeParameterVariances(universe, String cls) =>
1374+
JS('', '#.#', typeParameterVariances(universe), cls);
1375+
13681376
static void addRules(universe, rules) =>
13691377
_Utils.objectAssign(typeRules(universe), rules);
13701378

13711379
static void addErasedTypes(universe, types) =>
13721380
_Utils.objectAssign(erasedTypes(universe), types);
13731381

1382+
static void addTypeParameterVariances(universe, variances) =>
1383+
_Utils.objectAssign(typeParameterVariances(universe), variances);
1384+
13741385
static Object sharedEmptyArray(universe) =>
13751386
JS('JSArray', '#.#', universe, RtiUniverseFieldNames.sharedEmptyArray);
13761387

@@ -2247,6 +2258,14 @@ class TypeRule {
22472258
JS('', '#.#', rule, supertype);
22482259
}
22492260

2261+
class Variance {
2262+
// TODO(fishythefish): Try bitmask representation.
2263+
static const legacyCovariant = 0;
2264+
static const covariant = 1;
2265+
static const contravariant = 2;
2266+
static const invariant = 3;
2267+
}
2268+
22502269
// -------- Subtype tests ------------------------------------------------------
22512270

22522271
// Future entry point from compiled code.
@@ -2431,10 +2450,41 @@ bool _isSubtypeOfInterface(
24312450
var sArgs = Rti._getInterfaceTypeArguments(s);
24322451
int length = _Utils.arrayLength(sArgs);
24332452
assert(length == _Utils.arrayLength(tArgs));
2453+
2454+
var sVariances;
2455+
bool hasVariances;
2456+
if (JS_GET_FLAG("VARIANCE")) {
2457+
sVariances = _Universe.findTypeParameterVariances(universe, sName);
2458+
hasVariances = sVariances != null;
2459+
assert(!hasVariances || length == _Utils.arrayLength(sVariances));
2460+
}
2461+
24342462
for (int i = 0; i < length; i++) {
24352463
Rti sArg = _castToRti(_Utils.arrayAt(sArgs, i));
24362464
Rti tArg = _castToRti(_Utils.arrayAt(tArgs, i));
2437-
if (!_isSubtype(universe, sArg, sEnv, tArg, tEnv)) return false;
2465+
if (JS_GET_FLAG("VARIANCE")) {
2466+
int sVariance = hasVariances
2467+
? _Utils.asInt(_Utils.arrayAt(sVariances, i))
2468+
: Variance.legacyCovariant;
2469+
switch (sVariance) {
2470+
case Variance.legacyCovariant:
2471+
case Variance.covariant:
2472+
if (!_isSubtype(universe, sArg, sEnv, tArg, tEnv)) return false;
2473+
break;
2474+
case Variance.contravariant:
2475+
if (!_isSubtype(universe, tArg, tEnv, sArg, sEnv)) return false;
2476+
break;
2477+
case Variance.invariant:
2478+
if (!_isSubtype(universe, sArg, sEnv, tArg, tEnv) ||
2479+
!_isSubtype(universe, tArg, tEnv, sArg, sEnv)) return false;
2480+
break;
2481+
default:
2482+
throw StateError(
2483+
"Unknown variance given for subtype check: $sVariance");
2484+
}
2485+
} else {
2486+
if (!_isSubtype(universe, sArg, sEnv, tArg, tEnv)) return false;
2487+
}
24382488
}
24392489
return true;
24402490
}
@@ -2665,6 +2715,10 @@ void testingAddRules(universe, rules) {
26652715
_Universe.addRules(universe, rules);
26662716
}
26672717

2718+
void testingAddTypeParameterVariances(universe, variances) {
2719+
_Universe.addTypeParameterVariances(universe, variances);
2720+
}
2721+
26682722
bool testingIsSubtype(universe, rti1, rti2) {
26692723
return isSubtype(universe, _castToRti(rti1), _castToRti(rti2));
26702724
}

sdk_nnbd/lib/_internal/js_runtime/lib/shared/embedded_names.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -461,5 +461,6 @@ class RtiUniverseFieldNames {
461461
static String evalCache = 'eC';
462462
static String typeRules = 'tR';
463463
static String erasedTypes = 'eT';
464+
static String typeParameterVariances = 'tPV';
464465
static String sharedEmptyArray = 'sEA';
465466
}

tests/compiler/dart2js_extra/rti/subtype_test.dart

Lines changed: 2 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55
import 'dart:_foreign_helper' show JS, JS_GET_NAME;
66
import 'dart:_js_embedded_names' show JsGetName;
77
import 'dart:_rti' as rti;
8-
import "package:expect/expect.dart";
8+
9+
import 'subtype_utils.dart';
910

1011
final String objectName = JS_GET_NAME(JsGetName.OBJECT_CLASS_TYPE_NAME);
1112
final String futureName = JS_GET_NAME(JsGetName.FUTURE_CLASS_TYPE_NAME);
@@ -23,7 +24,6 @@ const typeRulesJson = r'''
2324
}
2425
''';
2526
final typeRules = JS('=Object', 'JSON.parse(#)', typeRulesJson);
26-
final universe = rti.testingCreateUniverse();
2727

2828
main() {
2929
rti.testingAddRules(universe, typeRules);
@@ -144,26 +144,3 @@ void testGenericFunctions() {
144144
equivalent('~()<List<@/>>', '~()<List<~/>>');
145145
unrelated('~()<List<int/>>', '~()<List<num/>>');
146146
}
147-
148-
String reason(String s, String t) => "$s <: $t";
149-
150-
void strictSubtype(String s, String t) {
151-
var sRti = rti.testingUniverseEval(universe, s);
152-
var tRti = rti.testingUniverseEval(universe, t);
153-
Expect.isTrue(rti.testingIsSubtype(universe, sRti, tRti), reason(s, t));
154-
Expect.isFalse(rti.testingIsSubtype(universe, tRti, sRti), reason(t, s));
155-
}
156-
157-
void unrelated(String s, String t) {
158-
var sRti = rti.testingUniverseEval(universe, s);
159-
var tRti = rti.testingUniverseEval(universe, t);
160-
Expect.isFalse(rti.testingIsSubtype(universe, sRti, tRti), reason(s, t));
161-
Expect.isFalse(rti.testingIsSubtype(universe, tRti, sRti), reason(t, s));
162-
}
163-
164-
void equivalent(String s, String t) {
165-
var sRti = rti.testingUniverseEval(universe, s);
166-
var tRti = rti.testingUniverseEval(universe, t);
167-
Expect.isTrue(rti.testingIsSubtype(universe, sRti, tRti), reason(s, t));
168-
Expect.isTrue(rti.testingIsSubtype(universe, tRti, sRti), reason(t, s));
169-
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// Copyright (c) 2019, 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+
import 'dart:_rti' as rti;
6+
import "package:expect/expect.dart";
7+
8+
final universe = rti.testingCreateUniverse();
9+
10+
String reason(String s, String t) => "$s <: $t";
11+
12+
void strictSubtype(String s, String t) {
13+
var sRti = rti.testingUniverseEval(universe, s);
14+
var tRti = rti.testingUniverseEval(universe, t);
15+
Expect.isTrue(rti.testingIsSubtype(universe, sRti, tRti), reason(s, t));
16+
Expect.isFalse(rti.testingIsSubtype(universe, tRti, sRti), reason(t, s));
17+
}
18+
19+
void unrelated(String s, String t) {
20+
var sRti = rti.testingUniverseEval(universe, s);
21+
var tRti = rti.testingUniverseEval(universe, t);
22+
Expect.isFalse(rti.testingIsSubtype(universe, sRti, tRti), reason(s, t));
23+
Expect.isFalse(rti.testingIsSubtype(universe, tRti, sRti), reason(t, s));
24+
}
25+
26+
void equivalent(String s, String t) {
27+
var sRti = rti.testingUniverseEval(universe, s);
28+
var tRti = rti.testingUniverseEval(universe, t);
29+
Expect.isTrue(rti.testingIsSubtype(universe, sRti, tRti), reason(s, t));
30+
Expect.isTrue(rti.testingIsSubtype(universe, tRti, sRti), reason(t, s));
31+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
// Copyright (c) 2019, 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+
// dart2jsOptions= --enable-experiment=variance
6+
7+
import 'dart:_foreign_helper' show JS;
8+
import 'dart:_rti' as rti;
9+
10+
import 'subtype_utils.dart';
11+
12+
const typeRulesJson = r'''
13+
{
14+
"int": {"num": []}
15+
}
16+
''';
17+
final typeRules = JS('=Object', 'JSON.parse(#)', typeRulesJson);
18+
19+
const typeParameterVariancesJson = '''
20+
{
21+
"Covariant": [${rti.Variance.covariant}],
22+
"Contravariant": [${rti.Variance.contravariant}],
23+
"Invariant": [${rti.Variance.invariant}],
24+
"MultiVariant":[${rti.Variance.legacyCovariant}, ${rti.Variance.invariant},
25+
${rti.Variance.contravariant}, ${rti.Variance.covariant}]
26+
}
27+
''';
28+
final typeParameterVariances =
29+
JS('=Object', 'JSON.parse(#)', typeParameterVariancesJson);
30+
31+
main() {
32+
rti.testingAddRules(universe, typeRules);
33+
rti.testingAddTypeParameterVariances(universe, typeParameterVariances);
34+
testInterfacesWithVariance();
35+
testInterfacesWithVariance(); // Ensure caching didn't change anything.
36+
}
37+
38+
void testInterfacesWithVariance() {
39+
strictSubtype('LegacyCovariant<int>', 'LegacyCovariant<num>');
40+
strictSubtype('Covariant<int>', 'Covariant<num>');
41+
strictSubtype('Contravariant<num>', 'Contravariant<int>');
42+
equivalent('Invariant<num>', 'Invariant<num>');
43+
unrelated('Invariant<int>', 'Invariant<num>');
44+
unrelated('Invariant<num>', 'Invariant<int>');
45+
strictSubtype(
46+
'MultiVariant<int,num,num,int>', 'MultiVariant<num,num,int,num>');
47+
}

0 commit comments

Comments
 (0)