Skip to content

Commit

Permalink
[dart2js] Added variance support for static subtype checking.
Browse files Browse the repository at this point in the history
Change-Id: I0731884aad68e3dd7f84658d75e946ca5db62bbe
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/127063
Reviewed-by: Sigmund Cherem <sigmund@google.com>
Commit-Queue: Kallen Tu <kallentu@google.com>
  • Loading branch information
kallentu authored and commit-bot@chromium.org committed Dec 4, 2019
1 parent f56b0f6 commit 53bbe6c
Show file tree
Hide file tree
Showing 12 changed files with 212 additions and 2 deletions.
3 changes: 3 additions & 0 deletions pkg/compiler/lib/src/elements/entities.dart
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,9 @@ class AsyncMarker {
int get index => values.indexOf(this);
}

/// Values for variance annotations.
enum Variance { legacyCovariant, covariant, contravariant, invariant }

/// Stripped down super interface for constructor like entities.
///
/// Currently only [ConstructorElement] but later also kernel based Dart
Expand Down
22 changes: 20 additions & 2 deletions pkg/compiler/lib/src/elements/types.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1501,6 +1501,9 @@ abstract class AbstractTypeRelation<T extends DartType>
/// Returns the declared bound of [element].
DartType getTypeVariableBound(TypeVariableEntity element);

/// Returns the variances for each type parameter in [cls].
List<Variance> getTypeVariableVariances(ClassEntity cls);

@override
bool visitType(T t, T s) {
throw 'internal error: unknown type ${t}';
Expand Down Expand Up @@ -1529,10 +1532,25 @@ abstract class AbstractTypeRelation<T extends DartType>
bool checkTypeArguments(InterfaceType instance, InterfaceType other) {
List<T> tTypeArgs = instance.typeArguments;
List<T> sTypeArgs = other.typeArguments;
List<Variance> tVariances = getTypeVariableVariances(instance.element);
assert(tTypeArgs.length == sTypeArgs.length);
assert(tTypeArgs.length == tVariances.length);
for (int i = 0; i < tTypeArgs.length; i++) {
if (invalidTypeArguments(tTypeArgs[i], sTypeArgs[i])) {
return false;
switch (tVariances[i]) {
case Variance.legacyCovariant:
case Variance.covariant:
if (invalidTypeArguments(tTypeArgs[i], sTypeArgs[i])) return false;
break;
case Variance.contravariant:
if (invalidTypeArguments(sTypeArgs[i], tTypeArgs[i])) return false;
break;
case Variance.invariant:
if (invalidTypeArguments(tTypeArgs[i], sTypeArgs[i]) ||
invalidTypeArguments(sTypeArgs[i], tTypeArgs[i])) return false;
break;
default:
throw StateError(
"Invalid variance ${tVariances[i]} used for subtype check.");
}
}
return true;
Expand Down
1 change: 1 addition & 0 deletions pkg/compiler/lib/src/ir/element_map.dart
Original file line number Diff line number Diff line change
Expand Up @@ -69,4 +69,5 @@ abstract class IrToElementMap {
DartType getCallType(InterfaceType type);
int getHierarchyDepth(IndexedClass cls);
DartType getTypeVariableBound(IndexedTypeVariable typeVariable);
List<Variance> getTypeVariableVariances(IndexedClass cls);
}
5 changes: 5 additions & 0 deletions pkg/compiler/lib/src/ir/types.dart
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,11 @@ abstract class AbstractTypeRelationMixin
return elementMap.getTypeVariableBound(element);
}

@override
List<Variance> getTypeVariableVariances(ClassEntity cls) {
return elementMap.getTypeVariableVariances(cls);
}

@override
FunctionType getCallType(InterfaceType type) {
return elementMap.getCallType(type);
Expand Down
15 changes: 15 additions & 0 deletions pkg/compiler/lib/src/ir/util.dart
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,21 @@ AsyncMarker getAsyncMarker(ir.FunctionNode node) {
}
}

/// Returns the `Variance` corresponding to `node.variance`.
Variance convertVariance(ir.TypeParameter node) {
if (node.isLegacyCovariant) return Variance.legacyCovariant;
switch (node.variance) {
case ir.Variance.covariant:
return Variance.covariant;
case ir.Variance.contravariant:
return Variance.contravariant;
case ir.Variance.invariant:
return Variance.invariant;
default:
throw new UnsupportedError("Variance ${node.variance} is not supported.");
}
}

/// Kernel encodes a null-aware expression `a?.b` as
///
/// let final #1 = a in #1 == null ? null : #1.b
Expand Down
3 changes: 3 additions & 0 deletions pkg/compiler/lib/src/js_model/closure.dart
Original file line number Diff line number Diff line change
Expand Up @@ -895,6 +895,9 @@ class RecordClassData implements JClassData {

@override
InterfaceType get instantiationToBounds => thisType;

@override
List<Variance> getVariances() => null;
}

/// A container for variables declared in a particular scope that are accessed
Expand Down
7 changes: 7 additions & 0 deletions pkg/compiler/lib/src/js_model/element_map_impl.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1070,6 +1070,13 @@ class JsKernelToElementMap implements JsToElementMap, IrToElementMap {
return data.getBound(this);
}

@override
List<Variance> getTypeVariableVariances(IndexedClass cls) {
assert(checkFamily(cls));
JClassData data = classes.getData(cls);
return data.getVariances();
}

DartType _getTypeVariableDefaultType(IndexedTypeVariable typeVariable) {
assert(checkFamily(typeVariable));
JTypeVariableData data = typeVariables.getData(typeVariable);
Expand Down
9 changes: 9 additions & 0 deletions pkg/compiler/lib/src/js_model/env.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import '../elements/types.dart';
import '../ir/element_map.dart';
import '../ir/static_type_cache.dart';
import '../ir/visitors.dart';
import '../ir/util.dart';
import '../js_model/element_map.dart';
import '../ordered_typeset.dart';
import '../serialization/serialization.dart';
Expand Down Expand Up @@ -451,6 +452,8 @@ abstract class JClassData {

bool get isEnumClass;
bool get isMixinApplication;

List<Variance> getVariances();
}

class JClassDataImpl implements JClassData {
Expand Down Expand Up @@ -482,6 +485,8 @@ class JClassDataImpl implements JClassData {
@override
OrderedTypeSet orderedTypeSet;

List<Variance> _variances;

JClassDataImpl(this.cls, this.definition);

factory JClassDataImpl.readFromDataSource(DataSource source) {
Expand All @@ -506,6 +511,10 @@ class JClassDataImpl implements JClassData {

@override
DartType get callType => null;

@override
List<Variance> getVariances() =>
_variances ??= cls.typeParameters.map(convertVariance).toList();
}

/// Enum used for identifying [JMemberData] subclasses in serialization.
Expand Down
7 changes: 7 additions & 0 deletions pkg/compiler/lib/src/kernel/element_map_impl.dart
Original file line number Diff line number Diff line change
Expand Up @@ -643,6 +643,13 @@ class KernelToElementMapImpl implements KernelToElementMap, IrToElementMap {
return data.getBound(this);
}

@override
List<Variance> getTypeVariableVariances(IndexedClass cls) {
assert(checkFamily(cls));
KClassData data = classes.getData(cls);
return data.getVariances();
}

ClassEntity getAppliedMixin(IndexedClass cls) {
assert(checkFamily(cls));
KClassData data = classes.getData(cls);
Expand Down
6 changes: 6 additions & 0 deletions pkg/compiler/lib/src/kernel/env.dart
Original file line number Diff line number Diff line change
Expand Up @@ -629,6 +629,7 @@ abstract class KClassData {
bool get isMixinApplication;

Iterable<ConstantValue> getMetadata(IrToElementMap elementMap);
List<Variance> getVariances();

/// Convert this [KClassData] to the corresponding [JClassData].
JClassData convert();
Expand Down Expand Up @@ -659,6 +660,7 @@ class KClassDataImpl implements KClassData {
OrderedTypeSet orderedTypeSet;

Iterable<ConstantValue> _metadata;
List<Variance> _variances;

KClassDataImpl(this.node);

Expand All @@ -677,6 +679,10 @@ class KClassDataImpl implements KClassData {
node.annotations);
}

@override
List<Variance> getVariances() =>
_variances ??= node.typeParameters.map(convertVariance).toList();

@override
JClassData convert() {
return new JClassDataImpl(node, new RegularClassDefinition(node));
Expand Down
132 changes: 132 additions & 0 deletions tests/compiler/dart2js/codegen/variance_subtype_cast_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
// Copyright (c) 2019, 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.

import 'package:async_helper/async_helper.dart';
import '../helpers/compiler_helper.dart';

const String LEGACY_COV_CAST = r"""
class LegacyCovariant<T> {
void method() {}
}
foo(param) {
LegacyCovariant<num> c = LegacyCovariant<num>();
(c as LegacyCovariant<int>).method();
// present: 'LegacyCovariant_int._as'
}
""";

const String LEGACY_COV_NO_CAST = r"""
class LegacyCovariant<T> {
void method() {}
}
foo(param) {
LegacyCovariant<num> c = LegacyCovariant<num>();
(c as LegacyCovariant<Object>).method();
// absent: 'LegacyCovariant_Object._as'
}
""";

const String COV_CAST = r"""
class Covariant<out T> {
void method() {}
}
foo(param) {
Covariant<num> c = Covariant<num>();
(c as Covariant<int>).method();
// present: 'Covariant_int._as'
}
""";

const String COV_NO_CAST = r"""
class Covariant<out T> {
void method() {}
}
foo(param) {
Covariant<num> c = Covariant<num>();
(c as Covariant<Object>).method();
// absent: 'Covariant_Object._as'
}
""";

const String CONTRA_CAST = r"""
class Contravariant<in T> {
void method() {}
}
foo(param) {
Contravariant<num> c = Contravariant<num>();
(c as Contravariant<Object>).method();
// present: 'Contravariant_Object._as'
}
""";

const String CONTRA_NO_CAST = r"""
class Contravariant<in T> {
void method() {}
}
foo(param) {
Contravariant<num> c = Contravariant<num>();
(c as Contravariant<int>).method();
// absent: 'Contravariant_int._as'
}
""";

const String INV_CAST1 = r"""
class Invariant<inout T> {
void method() {}
}
foo(param) {
Invariant<num> i = Invariant<num>();
(i as Invariant<Object>).method();
// present: 'Invariant_Object._as'
}
""";

const String INV_CAST2 = r"""
class Invariant<inout T> {
void method() {}
}
foo(param) {
Invariant<num> i = Invariant<num>();
(i as Invariant<int>).method();
// present: 'Invariant_int._as'
}
""";

const String INV_NO_CAST = r"""
class Invariant<inout T> {
void method() {}
}
foo(param) {
Invariant<num> i = Invariant<num>();
(i as Invariant<num>).method();
// absent: 'Invariant_num._as'
}
""";

main() {
runTests() async {
Future check(String test) {
return compile(test,
entry: 'foo',
check: checkerForAbsentPresent(test),
newRti: true,
enableVariance: true);
}

await check(LEGACY_COV_CAST);
await check(LEGACY_COV_NO_CAST);
await check(COV_CAST);
await check(COV_NO_CAST);
await check(CONTRA_CAST);
await check(CONTRA_NO_CAST);
await check(INV_CAST1);
await check(INV_CAST2);
await check(INV_NO_CAST);
}

asyncTest(() async {
print('--test from kernel------------------------------------------------');
await runTests();
});
}
4 changes: 4 additions & 0 deletions tests/compiler/dart2js/helpers/compiler_helper.dart
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ Future<String> compile(String code,
bool disableTypeInference: true,
bool omitImplicitChecks: true,
bool newRti: false,
bool enableVariance: false,
void check(String generatedEntry),
bool returnAll: false}) async {
OutputCollector outputCollector = returnAll ? new OutputCollector() : null;
Expand All @@ -63,6 +64,9 @@ Future<String> compile(String code,
if (newRti) {
options.add(Flags.experimentNewRti);
}
if (enableVariance) {
options.add('${Flags.enableLanguageExperiments}=variance');
}

// Pretend this is a dart2js_native test to allow use of 'native' keyword
// and import of private libraries.
Expand Down

0 comments on commit 53bbe6c

Please sign in to comment.