[vm/tfa] Use pragma to pass type arguments to exact return types.

Change-Id: Ie2a1de07653c53e8b9506c54a0a66a5fe6503b9a
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/129160
Commit-Queue: Samir Jindel <sjindel@google.com>
Reviewed-by: Alexander Markov <alexmarkov@google.com>
This commit is contained in:
Samir Jindel 2020-01-03 14:08:19 +00:00 committed by commit-bot@chromium.org
parent e3a20bf361
commit 445ae73b32
13 changed files with 95 additions and 45 deletions

View file

@ -10,6 +10,8 @@ import 'package:kernel/core_types.dart' show CoreTypes;
const kEntryPointPragmaName = "vm:entry-point";
const kExactResultTypePragmaName = "vm:exact-result-type";
const kNonNullableResultType = "vm:non-nullable-result-type";
const kResultTypeUsesPassedTypeArguments =
"result-type-uses-passed-type-arguments";
abstract class ParsedPragma {}
@ -22,7 +24,9 @@ class ParsedEntryPointPragma extends ParsedPragma {
class ParsedResultTypeByTypePragma extends ParsedPragma {
final DartType type;
ParsedResultTypeByTypePragma(this.type);
final bool resultTypeUsesPassedTypeArguments;
ParsedResultTypeByTypePragma(
this.type, this.resultTypeUsesPassedTypeArguments);
}
class ParsedResultTypeByPathPragma extends ParsedPragma {
@ -97,9 +101,17 @@ class ConstantPragmaAnnotationParser extends PragmaAnnotationParser {
case kExactResultTypePragmaName:
if (options == null) return null;
if (options is TypeLiteralConstant) {
return new ParsedResultTypeByTypePragma(options.type);
return new ParsedResultTypeByTypePragma(options.type, false);
} else if (options is StringConstant) {
return new ParsedResultTypeByPathPragma(options.value);
} else if (options is ListConstant &&
options.entries.length == 2 &&
options.entries[0] is TypeLiteralConstant &&
options.entries[1] is StringConstant &&
(options.entries[1] as StringConstant).value ==
kResultTypeUsesPassedTypeArguments) {
return new ParsedResultTypeByTypePragma(
(options.entries[0] as TypeLiteralConstant).type, true);
}
throw "ERROR: Unsupported option to '$kExactResultTypePragmaName' "
"pragma: $options";

View file

@ -928,10 +928,10 @@ class GenericInterfacesInfoImpl implements GenericInterfacesInfo {
final cachedFlattenedTypeArgs = <Class, List<DartType>>{};
final cachedFlattenedTypeArgsForNonGeneric = <Class, List<Type>>{};
RuntimeTypeTranslator closedTypeTranslator;
RuntimeTypeTranslatorImpl closedTypeTranslator;
GenericInterfacesInfoImpl(this.hierarchy) {
closedTypeTranslator = RuntimeTypeTranslator.forClosedTypes(this);
closedTypeTranslator = RuntimeTypeTranslatorImpl.forClosedTypes(this);
}
List<DartType> flattenedTypeArgumentsFor(Class klass, {bool useCache: true}) {

View file

@ -191,9 +191,12 @@ class NativeCodeOracle {
/// Simulate the execution of a native method by adding its entry points
/// using [entryPointsListener]. Returns result type of the native method.
Type handleNativeProcedure(Member member,
EntryPointsListener entryPointsListener, TypesBuilder typesBuilder) {
Type returnType = null;
TypeExpr handleNativeProcedure(
Member member,
EntryPointsListener entryPointsListener,
TypesBuilder typesBuilder,
RuntimeTypeTranslator translator) {
TypeExpr returnType = null;
bool nullable = null;
for (var annotation in member.annotations) {
@ -214,6 +217,13 @@ class NativeCodeOracle {
var type = pragma.type;
if (type is InterfaceType) {
returnType = entryPointsListener.addAllocatedClass(type.classNode);
if (pragma.resultTypeUsesPassedTypeArguments) {
returnType = translator.instantiateConcreteType(
returnType,
member.function.typeParameters
.map((t) => TypeParameterType(t, Nullability.legacy))
.toList());
}
continue;
}
throw "ERROR: Invalid return type for native method: ${pragma.type}";

View file

@ -312,14 +312,12 @@ class SummaryCollector extends RecursiveVisitor<TypeExpr> {
Join _returnValue;
Parameter _receiver;
ConstantAllocationCollector constantAllocationCollector;
RuntimeTypeTranslator _translator;
RuntimeTypeTranslatorImpl _translator;
StaticTypeContext _staticTypeContext;
// Currently only used for factory constructors.
Map<TypeParameter, TypeExpr> _fnTypeVariables;
Member _privateListConstructor;
SummaryCollector(
this.target,
this._environment,
@ -330,8 +328,6 @@ class SummaryCollector extends RecursiveVisitor<TypeExpr> {
this._genericInterfacesInfo) {
assertx(_genericInterfacesInfo != null);
constantAllocationCollector = new ConstantAllocationCollector(this);
_privateListConstructor =
_environment.coreTypes.index.getMember('dart:core', '_List', '');
}
Summary createSummary(Member member,
@ -361,7 +357,7 @@ class SummaryCollector extends RecursiveVisitor<TypeExpr> {
_summary = new Summary();
}
_translator = new RuntimeTypeTranslator(
_translator = new RuntimeTypeTranslatorImpl(
_summary, _receiver, null, _genericInterfacesInfo);
if (fieldSummaryType == FieldSummaryType.kInitializer) {
@ -405,7 +401,7 @@ class SummaryCollector extends RecursiveVisitor<TypeExpr> {
isReceiver: true);
}
_translator = new RuntimeTypeTranslator(
_translator = new RuntimeTypeTranslatorImpl(
_summary, _receiver, _fnTypeVariables, _genericInterfacesInfo);
// Handle forwarding stubs. We need to check types against the types of
@ -475,30 +471,17 @@ class SummaryCollector extends RecursiveVisitor<TypeExpr> {
}
if (function.body == null) {
if (member == _privateListConstructor) {
// We have an intrinsic summary for the _List factory to ensure the
// correct type arguments are used.
// TODO(39769): Find a way to generalize this to other classes.
_returnValue.values.add(_translator.instantiateConcreteType(
_nativeCodeOracle.handleNativeProcedure(
member, _entryPointsListener, _typesBuilder),
[
TypeParameterType(
function.typeParameters.first, Nullability.legacy)
]));
TypeExpr type = _nativeCodeOracle.handleNativeProcedure(
member, _entryPointsListener, _typesBuilder, _translator);
if (type is! ConcreteType && type is! Statement) {
// Runtime type could be more precise than static type, so
// calculate intersection.
final runtimeType = _translator.translate(function.returnType);
final typeCheck = new TypeCheck(type, runtimeType, function, type);
_summary.add(typeCheck);
_returnValue.values.add(typeCheck);
} else {
Type type = _nativeCodeOracle.handleNativeProcedure(
member, _entryPointsListener, _typesBuilder);
if (type is! ConcreteType) {
// Runtime type could be more precise than static type, so
// calculate intersection.
final runtimeType = _translator.translate(function.returnType);
final typeCheck = new TypeCheck(type, runtimeType, function, type);
_summary.add(typeCheck);
_returnValue.values.add(typeCheck);
} else {
_returnValue.values.add(type);
}
_returnValue.values.add(type);
}
} else {
_visit(function.body);
@ -1520,19 +1503,20 @@ class SummaryCollector extends RecursiveVisitor<TypeExpr> {
}
}
class RuntimeTypeTranslator extends DartTypeVisitor<TypeExpr> {
class RuntimeTypeTranslatorImpl extends DartTypeVisitor<TypeExpr>
implements RuntimeTypeTranslator {
final Summary summary;
final Map<TypeParameter, TypeExpr> functionTypeVariables;
final Map<DartType, TypeExpr> typesCache = <DartType, TypeExpr>{};
final TypeExpr receiver;
final GenericInterfacesInfo genericInterfacesInfo;
RuntimeTypeTranslator(this.summary, this.receiver, this.functionTypeVariables,
this.genericInterfacesInfo) {}
RuntimeTypeTranslatorImpl(this.summary, this.receiver,
this.functionTypeVariables, this.genericInterfacesInfo) {}
// Create a type translator which can be used only for types with no free type
// variables.
RuntimeTypeTranslator.forClosedTypes(this.genericInterfacesInfo)
RuntimeTypeTranslatorImpl.forClosedTypes(this.genericInterfacesInfo)
: summary = null,
functionTypeVariables = null,
receiver = null {}

View file

@ -102,6 +102,10 @@ abstract class TypesBuilder {
}
}
abstract class RuntimeTypeTranslator {
TypeExpr instantiateConcreteType(ConcreteType type, List<DartType> typeArgs);
}
/// Abstract interface to type hierarchy information used by types.
abstract class TypeHierarchy extends TypesBuilder
implements GenericInterfacesInfo {

View file

@ -0,0 +1,4 @@
main() {
dynamic x = List<int>(10);
x[0] + 10;
}

View file

@ -0,0 +1,8 @@
library #lib;
import self as self;
import "dart:core" as core;
static method main() → dynamic {
dynamic x = [@vm.inferred-type.metadata=dart.core::_List<dart.core::int*>] core::_List::•<core::int*>(10);
[@vm.direct-call.metadata=dart.core::_IntegerImplementation::+??] [@vm.direct-call.metadata=dart.core::_List::[]] [@vm.inferred-type.metadata=int?] x.[](0).+(10);
}

View file

@ -45,6 +45,8 @@ Since those annotations side-step the normal type system, they are unsafe and we
therefore restrict those annotations to only have an affect inside dart:
libraries.
See also https://github.com/dart-lang/sdk/issues/35244.
### Providing an exact result type
```dart
@ -69,6 +71,14 @@ Note that since `null` is an instance of the `Null` type, which is a subtype of
any other, exactness of the annotated result type implies that the result must
be non-null.
It is also possible to specify the type arguments of the result type if they are
the same as the type arguments passed to the method itself. This is primarily
useful for factory constructors:
```dart
@pragma("vm:exact-result-type", [<type>, "result-type-uses-passed-type-arguments"])
```
#### Examples for exact result types
```dart
@ -92,6 +102,12 @@ class C {
@pragma('vm:exact-result-type', "dart:core#_Smi")
final int intValue;
}
class D<T> {
@pragma("vm:exact-result-type",
[D, "result-type-uses-passed-type-arguments"])
factory D(); // returns an instance of D<T>
}
```
### Declaring a result type non-nullable

View file

@ -61,6 +61,14 @@ intptr_t MethodRecognizer::ResultCidFromPragma(
}
}
}
} else if (option.IsArray()) {
const Array& array = Array::Cast(option);
if (array.Length() > 0) {
const Object& type = Object::Handle(Array::Cast(option).At(0));
if (type.IsType()) {
return Type::Cast(type).type_class_id();
}
}
}
}

View file

@ -8,7 +8,8 @@
@pragma("vm:entry-point")
class _List<E> extends FixedLengthListBase<E> {
@pragma("vm:exact-result-type", _List) // <- cannot use _List<T> here.
@pragma(
"vm:exact-result-type", [_List, "result-type-uses-passed-type-arguments"])
@pragma("vm:prefer-inline")
factory _List(length) native "List_allocate";

View file

@ -106,7 +106,8 @@ class _GrowableList<T> extends ListBase<T> {
return new _GrowableList<T>._withData(data);
}
@pragma("vm:exact-result-type", _GrowableList)
@pragma("vm:exact-result-type",
[_GrowableList, "result-type-uses-passed-type-arguments"])
factory _GrowableList._withData(_List data) native "GrowableList_allocate";
@pragma("vm:exact-result-type", "dart:core#_Smi")

View file

@ -8,7 +8,8 @@
@pragma("vm:entry-point")
class _List<E> extends FixedLengthListBase<E> {
@pragma("vm:exact-result-type", _List) // <- cannot use _List<T> here.
@pragma(
"vm:exact-result-type", [_List, "result-type-uses-passed-type-arguments"])
@pragma("vm:prefer-inline")
factory _List(length) native "List_allocate";

View file

@ -106,7 +106,8 @@ class _GrowableList<T> extends ListBase<T> {
return new _GrowableList<T>._withData(data);
}
@pragma("vm:exact-result-type", _GrowableList)
@pragma("vm:exact-result-type",
[_GrowableList, "result-type-uses-passed-type-arguments"])
factory _GrowableList._withData(_List data) native "GrowableList_allocate";
@pragma("vm:exact-result-type", "dart:core#_Smi")