blob: d82e130bc2eadc0ed2ba85bb548abe40890670e4 [file] [log] [blame]
// Copyright (c) 2014, 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.
library dart2js.js_emitter.runtime_type_generator;
import '../common_elements.dart' show CommonElements;
import '../deferred_load.dart' show OutputUnit, OutputUnitData;
import '../elements/entities.dart';
import '../elements/types.dart';
import '../js/js.dart' as jsAst;
import '../js/js.dart' show js;
import '../js_backend/js_interop_analysis.dart' as jsInteropAnalysis;
import '../js_backend/namer.dart' show Namer;
import '../js_backend/runtime_types.dart'
show
ClassChecks,
ClassFunctionType,
OnVariableCallback,
RuntimeTypesChecks,
RuntimeTypesEncoder,
Substitution,
TypeCheck;
import '../js_emitter/sorter.dart';
import '../util/util.dart' show Setlet;
import 'code_emitter_task.dart' show CodeEmitterTask, Emitter;
// Function signatures used in the generation of runtime type information.
typedef void FunctionTypeSignatureEmitter(ClassFunctionType classFunctionType);
class TypeTest {
final jsAst.Name name;
final jsAst.Node expression;
TypeTest(this.name, this.expression);
}
class TypeTests {
TypeTest isTest;
TypeTest substitution;
TypeTest signature;
}
class TypeTestProperties {
/// The index of the function type into the metadata.
///
/// If the class doesn't have a function type this field is `null`.
///
/// If the is tests were generated with `storeFunctionTypeInMetadata` set to
/// `false`, this field is `null`, and the [properties] contain a property
/// that encodes the function type.
jsAst.Expression functionTypeIndex;
/// The properties that must be installed on the prototype of the
/// JS constructor of the [ClassEntity] for which the is checks were
/// generated.
final Map<ClassEntity, TypeTests> _properties = <ClassEntity, TypeTests>{};
void addIsTest(ClassEntity cls, jsAst.Name name, jsAst.Node expression) {
TypeTests typeTests = _properties.putIfAbsent(cls, () => new TypeTests());
typeTests.isTest = new TypeTest(name, expression);
}
void addSubstitution(
ClassEntity cls, jsAst.Name name, jsAst.Node expression) {
TypeTests typeTests = _properties.putIfAbsent(cls, () => new TypeTests());
typeTests.substitution = new TypeTest(name, expression);
}
void addSignature(ClassEntity cls, jsAst.Name name, jsAst.Node expression) {
TypeTests typeTests = _properties.putIfAbsent(cls, () => new TypeTests());
typeTests.signature = new TypeTest(name, expression);
}
void forEachProperty(
Sorter sorter, void f(jsAst.Name name, jsAst.Node expression)) {
void handleTypeTest(TypeTest typeTest) {
if (typeTest == null) return;
f(typeTest.name, typeTest.expression);
}
for (ClassEntity cls in sorter.sortClasses(_properties.keys)) {
TypeTests typeTests = _properties[cls];
handleTypeTest(typeTests.isTest);
handleTypeTest(typeTests.substitution);
handleTypeTest(typeTests.signature);
}
}
}
class RuntimeTypeGenerator {
final CommonElements _commonElements;
final OutputUnitData _outputUnitData;
final CodeEmitterTask emitterTask;
final Namer _namer;
final RuntimeTypesChecks _rtiChecks;
final RuntimeTypesEncoder _rtiEncoder;
final _TypeContainedInOutputUnitVisitor _outputUnitVisitor;
RuntimeTypeGenerator(this._commonElements, this._outputUnitData,
this.emitterTask, this._namer, this._rtiChecks, this._rtiEncoder)
: _outputUnitVisitor = new _TypeContainedInOutputUnitVisitor(
_commonElements, _outputUnitData);
/// Generate "is tests" for [cls] itself, and the "is tests" for the
/// classes it implements and type argument substitution functions for these
/// tests. We don't need to add the "is tests" of the super class because
/// they will be inherited at runtime, but we may need to generate the
/// substitutions, because they may have changed.
/// Generates all properties necessary for is-checks on the [classElement].
///
/// Returns an instance of [TypeTestProperties] that contains the properties
/// that must be installed on the prototype of the JS constructor of the
/// [classElement].
///
/// If [storeFunctionTypeInMetadata] is `true`, stores the reified function
/// type (if class has one) in the metadata object and stores its index in
/// the result. This is only possible for function types that do not contain
/// type variables.
TypeTestProperties generateIsTests(ClassEntity classElement,
Map<MemberEntity, jsAst.Expression> generatedCode,
{bool storeFunctionTypeInMetadata: true}) {
TypeTestProperties result = new TypeTestProperties();
// TODO(johnniwinther): Include function signatures in [ClassChecks].
void generateFunctionTypeSignature(ClassFunctionType classFunctionType) {
FunctionEntity method = classFunctionType.callFunction;
FunctionType type = classFunctionType.callType;
// TODO(johnniwinther): Avoid unneeded function type indices or
// signatures. We either need them for mirrors or because [type] is
// potentially a subtype of a checked function. Currently we eagerly
// generate a function type index or signature for all callable classes.
jsAst.Expression functionTypeIndex;
bool isDeferred = false;
if (!type.containsTypeVariables) {
// TODO(sigmund): use output unit of [method] when the classes mentioned
// in [type] aren't in the main output unit. (Issue #31032)
OutputUnit mainOutputUnit = _outputUnitData.mainOutputUnit;
if (_outputUnitVisitor.isTypeContainedIn(type, mainOutputUnit)) {
functionTypeIndex =
emitterTask.metadataCollector.reifyType(type, mainOutputUnit);
} else if (!storeFunctionTypeInMetadata) {
// TODO(johnniwinther): Support sharing deferred signatures with the
// full emitter.
isDeferred = true;
functionTypeIndex = emitterTask.metadataCollector
.reifyType(type, _outputUnitData.outputUnitForMember(method));
}
}
if (storeFunctionTypeInMetadata && functionTypeIndex != null) {
result.functionTypeIndex = functionTypeIndex;
} else {
jsAst.Expression encoding =
generatedCode[classFunctionType.signatureFunction];
if (classFunctionType.signatureFunction == null) {
// The signature function isn't live.
return;
}
if (functionTypeIndex != null) {
if (isDeferred) {
// The function type index must be offset by the number of types
// already loaded.
encoding = new jsAst.Binary(
'+',
new jsAst.VariableUse(_namer.typesOffsetName),
functionTypeIndex);
} else {
encoding = functionTypeIndex;
}
}
if (encoding != null) {
jsAst.Name operatorSignature =
_namer.asName(_namer.fixedNames.operatorSignature);
result.addSignature(classElement, operatorSignature, encoding);
}
}
}
void generateTypeCheck(TypeCheck check) {
ClassEntity checkedClass = check.cls;
if (check.needsIs) {
result.addIsTest(
checkedClass, _namer.operatorIs(checkedClass), js('1'));
}
Substitution substitution = check.substitution;
if (substitution != null) {
jsAst.Expression body =
_getSubstitutionCode(emitterTask.emitter, substitution);
result.addSubstitution(
checkedClass, _namer.substitutionName(checkedClass), body);
}
}
_generateIsTestsOn(
classElement, generateFunctionTypeSignature, generateTypeCheck);
if (classElement == _commonElements.jsJavaScriptFunctionClass) {
var type = jsInteropAnalysis.buildJsFunctionType();
if (type != null) {
jsAst.Expression thisAccess = new jsAst.This();
jsAst.Expression encoding = _rtiEncoder.getSignatureEncoding(
_namer, emitterTask.emitter, type, thisAccess);
jsAst.Name operatorSignature =
_namer.asName(_namer.fixedNames.operatorSignature);
result.addSignature(classElement, operatorSignature, encoding);
}
}
return result;
}
/// Compute a JavaScript expression that describes the necessary substitution
/// for type arguments in a subtype test.
///
/// The result can be:
/// 1) `null`, if no substituted check is necessary, because the type
/// variables are the same or there are no type variables in the class
/// that is checked for.
/// 2) A list expression describing the type arguments to be used in the
/// subtype check, if the type arguments to be used in the check do not
/// depend on the type arguments of the object.
/// 3) A function mapping the type variables of the object to be checked to
/// a list expression.
jsAst.Expression _getSubstitutionCode(
Emitter emitter, Substitution substitution) {
if (substitution.isTrivial) {
return new jsAst.LiteralNull();
}
if (substitution.isJsInterop) {
return js('function() { return # }',
_rtiEncoder.getJsInteropTypeArguments(substitution.length));
}
jsAst.Expression declaration(TypeVariableType variable) {
return new jsAst.Parameter(_getVariableName(variable.element.name));
}
jsAst.Expression use(TypeVariableType variable) {
return new jsAst.VariableUse(_getVariableName(variable.element.name));
}
if (substitution.arguments.every((DartType type) => type.isDynamic)) {
return emitter.generateFunctionThatReturnsNull();
} else {
jsAst.Expression value =
_getSubstitutionRepresentation(emitter, substitution.arguments, use);
if (substitution.isFunction) {
Iterable<jsAst.Expression> formals =
// TODO(johnniwinther): Pass [declaration] directly to `map` when
// `substitution.parameters` can no longer be a
// `List<ResolutionDartType>`.
substitution.parameters.map((type) => declaration(type));
return js('function(#) { return # }', [formals, value]);
} else {
return js('function() { return # }', value);
}
}
}
jsAst.Expression _getSubstitutionRepresentation(
Emitter emitter, List<DartType> types, OnVariableCallback onVariable) {
List<jsAst.Expression> elements = types
.map((DartType type) =>
_rtiEncoder.getTypeRepresentation(emitter, type, onVariable))
.toList(growable: false);
return new jsAst.ArrayInitializer(elements);
}
String _getVariableName(String name) {
// Kernel type variable names for anonymous mixin applications have names
// canonicalized to a non-identified, e.g. '#U0'.
name = name.replaceAll('#', '_');
return _namer.safeVariableName(name);
}
void _generateIsTestsOn(
ClassEntity cls,
FunctionTypeSignatureEmitter generateFunctionTypeSignature,
void emitTypeCheck(TypeCheck check)) {
Setlet<ClassEntity> generated = new Setlet<ClassEntity>();
// Precomputed is checks.
ClassChecks classChecks = _rtiChecks.requiredChecks[cls];
Iterable<TypeCheck> typeChecks = classChecks.checks;
if (typeChecks != null) {
for (TypeCheck typeCheck in typeChecks) {
if (!generated.contains(typeCheck.cls)) {
emitTypeCheck(typeCheck);
generated.add(typeCheck.cls);
}
}
}
if (classChecks.functionType != null) {
generateFunctionTypeSignature(classChecks.functionType);
}
}
}
/// Visitor that checks whether a type is contained within one output unit.
class _TypeContainedInOutputUnitVisitor
implements DartTypeVisitor<bool, OutputUnit> {
final CommonElements _commonElements;
final OutputUnitData _outputUnitData;
_TypeContainedInOutputUnitVisitor(this._commonElements, this._outputUnitData);
/// Returns `true` if all classes mentioned in [type] are in [outputUnit].
bool isTypeContainedIn(DartType type, OutputUnit outputUnit) =>
visit(type, outputUnit);
@override
bool visit(DartType type, OutputUnit argument) => type.accept(this, argument);
bool visitList(List<DartType> types, OutputUnit argument) {
for (DartType type in types) {
if (!visit(type, argument)) {
return false;
}
}
return true;
}
@override
bool visitFutureOrType(FutureOrType type, OutputUnit argument) {
if (_outputUnitData.outputUnitForClass(_commonElements.functionClass) !=
argument) {
return false;
}
return visit(type.typeArgument, argument);
}
@override
bool visitDynamicType(DynamicType type, OutputUnit argument) => true;
@override
bool visitTypedefType(TypedefType type, OutputUnit argument) {
return visit(type.unaliased, argument);
}
@override
bool visitInterfaceType(InterfaceType type, OutputUnit argument) {
if (_outputUnitData.outputUnitForClass(type.element) != argument) {
return false;
}
return visitList(type.typeArguments, argument);
}
@override
bool visitFunctionType(FunctionType type, OutputUnit argument) {
bool result = visit(type.returnType, argument) &&
visitList(type.parameterTypes, argument) &&
visitList(type.optionalParameterTypes, argument) &&
visitList(type.namedParameterTypes, argument);
if (!result) return false;
for (FunctionTypeVariable typeVariable in type.typeVariables) {
if (!visit(typeVariable.bound, argument)) {
return false;
}
}
return true;
}
@override
bool visitFunctionTypeVariable(
FunctionTypeVariable type, OutputUnit argument) {
return true;
}
@override
bool visitTypeVariableType(TypeVariableType type, OutputUnit argument) {
return false;
}
@override
bool visitVoidType(VoidType type, OutputUnit argument) {
return true;
}
}