Files
sdk/pkg/dev_compiler/lib/runtime/dart_runtime.dart
T

325 lines
9.7 KiB
Dart

// Copyright (c) 2015, 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 dev_compiler.runtime.dart_runtime;
import 'dart:mirrors';
import 'package:dev_compiler/config.dart';
dynamic dload(dynamic obj, String field) {
var symbol = new Symbol(field);
var mirror = reflect(obj);
// TODO(vsm): Does this create an NSM?
var fieldMirror = mirror.getField(symbol);
return fieldMirror.reflectee;
}
dynamic dinvokef(dynamic f, List args) {
// TODO(vsm): Support named arguments.
assert(f is Function);
return Function.apply(f, args);
}
// A workaround to manufacture a generic Type object inline.
// We use mirrors to extract type T given a TypeFunction<T>.
// E.g., Map<String, String> is not a valid literal in Dart.
// Instead, use: type((Map<String, String> _) {});
// See bug: https://code.google.com/p/dart/issues/detail?id=11923
typedef TypeFunction<T>(T x);
Type type(TypeFunction f) {
ClosureMirror cm = reflect(f);
MethodMirror mm = cm.function;
ParameterMirror pm = mm.parameters[0];
TypeMirror tm = pm.type;
return tm.reflectedType;
}
dynamic cast(dynamic obj, Type staticType) {
// This is our 'as' equivalent.
if (obj == null) {
// A null can be cast only to non-primitive types.
if (!isPrimitiveType(staticType)) return null;
} else {
// For non-null values, val is T => val as T succeeds.
if (instanceOf(obj, staticType)) return obj;
}
// TODO(vsm): Add message.
throw new CastError();
}
bool instanceOf(dynamic obj, Type staticType) {
// This is our 'is' equivalent.
if (obj == null) {
// Only true for the Object type.
return staticType == Object || staticType == dynamic || staticType == Null;
}
Type runtimeType = obj.runtimeType;
return _isSubType(reflectType(runtimeType), reflectType(staticType));
}
bool isGroundType(Type type) {
// These are types allow in is / as expressions.
final mirror = reflectType(type);
// Disallow functions.
if (mirror is FunctionTypeMirror) return false;
if (mirror is TypedefMirror) return false;
// Disallow generic type parameters.
if (mirror is TypeVariableMirror) return false;
if (mirror is ClassMirror) {
return _isRawClass(mirror);
}
// Only dynamic should be left. Should this be allowed?
// It's not particularly useful.
assert(mirror.reflectedType == dynamic);
return true;
}
final _primitiveMap = {
'int': int,
'double': double,
'num': num,
'bool': bool,
'String': String,
};
// TODO(vsm): Make this configurable? Using default settings for now.
final _typeOptions = new TypeOptions();
Set<Type> _primitives = () {
var types = _typeOptions.nonnullableTypes;
var set = new Set<Type>.from(types.map((t) => _primitiveMap[t]));
return set;
}();
bool isPrimitiveType(Type t) {
return _primitives.contains(t);
}
class Arity {
final int normal;
final int optionalPositional;
Arity._internal(this.normal, this.optionalPositional);
int get min => normal;
int get max => normal + optionalPositional;
}
Arity getArity(Function f) {
final FunctionTypeMirror mirror = reflectType(f.runtimeType);
final parameters = mirror.parameters;
int normal = 0;
int optionalPositional = 0;
for (var parameter in parameters) {
if (parameter.isNamed) {
// Ignore named parameters - these cannot be passed positionally.
} else if (parameter.isOptional) {
optionalPositional++;
} else {
normal++;
}
}
return new Arity._internal(normal, optionalPositional);
}
bool _isFunctionSubType(TypeMirror ret1, List<ParameterMirror> params1,
TypeMirror ret2, List<ParameterMirror> params2) {
if (!_isSubType(ret1, ret2)) {
// Covariant return types
// Note, void (which can only appear as a return type) is effectively
// treated as dynamic. If the base return type is void, we allow any
// subtype return type.
// E.g., we allow:
// () -> int <: () -> void
if (ret2.simpleName != const Symbol('void')) {
return false;
}
}
if (params1.length < params2.length) {
return false;
}
for (int i = 0; i < params2.length; ++i) {
ParameterMirror p1 = params1[i];
ParameterMirror p2 = params2[i];
// Contravariant parameter types.
if (!_isSubType(p2.type, p1.type)) {
return false;
}
// Optional parameters.
if (p2.isOptional) {
// If the base param is optional, the sub param must be optional:
if (!p1.isOptional) return false;
if (!p2.isNamed) {
// either neither are named or
if (p1.isNamed) return false;
} else {
// both are named with the same name
if (!p1.isNamed || p1.simpleName != p2.simpleName) return false;
}
} else {
// If the base param is required, the sub may be optional, but not named.
if (p1.isNamed) return false;
}
}
for (int i = params2.length; i < params1.length; ++i) {
ParameterMirror p1 = params1[i];
// Any additional sub params must be optional.
if (!p1.isOptional) return false;
}
return true;
}
bool _isClassSubType(ClassMirror m1, ClassMirror m2) {
// TODO(vsm): Consider some caching for efficiency here.
// We support Dart's covariant generics with the caveat that we do not
// substitute bottom for dynamic in subtyping rules.
// I.e., given T1, ..., Tn where at least one Ti != dynamic we disallow:
// - S !<: S<T1, ..., Tn>
// - S<dynamic, ..., dynamic> !<: S<T1, ..., Tn>
if (m1 == m2) return true;
if (m1.hasReflectedType && m1.reflectedType == Object) return false;
// Check if m1 and m2 have the same raw type. If so, check covariance on
// type parameters.
if (m1.originalDeclaration == m2.originalDeclaration) {
final typeArguments1 = m1.typeArguments;
final typeArguments2 = m2.typeArguments;
final length = typeArguments1.length;
if (typeArguments2.length == 0) {
// m2 is the raw form of m1
return true;
} else if (typeArguments1.length == 0) {
// m1 is raw, but m2 is not
return false;
}
assert(typeArguments2.length == length);
for (var i = 0; i < length; ++i) {
var typeArgument1 = typeArguments1[i];
var typeArgument2 = typeArguments2[i];
if (!_isSubType(typeArgument1, typeArgument2)) {
return false;
}
}
return true;
}
// Check superclass.
if (_isClassSubType(m1.superclass, m2)) return true;
// Check interfaces.
for (final parent in m1.superinterfaces) {
if (_isClassSubType(parent, m2)) return true;
}
return false;
}
bool _isRawClass(ClassMirror mirror) {
// Allow only raw types.
if (mirror == mirror.originalDeclaration) return true;
final dynamicMirror = reflectType(dynamic);
for (var typeArgument in mirror.typeArguments) {
if (typeArgument != dynamicMirror) return false;
}
return true;
}
TypeMirror _canonicalizeTypeMirror(TypeMirror t) {
if (t is TypedefMirror) {
// We canonicalize Typedefs to their underlying function types.
t = (t as TypedefMirror).referent;
}
if (t is ClassMirror && _isRawClass(t)) {
// We canonicalize T<dynamic> to T.
t = t.originalDeclaration;
}
return t;
}
bool _reflects(TypeMirror mirror, Type t) {
return mirror.hasReflectedType && mirror.reflectedType == t;
}
bool _isSubType(TypeMirror t1, TypeMirror t2) {
t1 = _canonicalizeTypeMirror(t1);
t2 = _canonicalizeTypeMirror(t2);
if (t1 == t2) return true;
// In Dart, dynamic is effectively both top and bottom.
// Here, we treat dynamic as top - the base type of everything.
if (_reflects(t1, dynamic)) return false;
if (_reflects(t2, dynamic)) return true;
// Object only subtypes dynamic and Object.
if (_reflects(t2, Object)) return true;
if (_reflects(t1, Object)) return false;
// "Traditional" name-based subtype check.
final c1 = t1 as ClassMirror;
final c2 = t2 as ClassMirror;
if (_isClassSubType(c1, c2)) {
return true;
}
// Function subtyping.
// Note: it appears under the hood all Dart functions map to a class / hidden type
// that:
// (a) subtypes Object (an internal _FunctionImpl in the VM)
// (b) implements Function
// (c) provides standard Object members (hashCode, toString)
// (d) contains private members (corresponding to _FunctionImpl?)
// (e) provides a call method to handle the actual function invocation
//
// The standard Dart subtyping rules are structural in nature. I.e.,
// bivariant on arguments and return type.
//
// The below tries for a more traditional subtyping rule:
// - covariant on return type
// - contravariant on parameters
// - 'sensible' (?) rules on optional and/or named params
// but doesn't properly mix with class subtyping yet.
//
// Note, a class type that implements a call method implicitly subtypes
// the function type of the call method. However, the converse is not true:
// a function type does not subtype a class type with a call method.
// If c1 is not a proper function or a class type with call method,
// return false.
TypeMirror ret1;
List<ParameterMirror> params1;
// Note, a proper function has a call method, but it's not a regular method,
// so we break out the two cases.
if (c1 is FunctionTypeMirror) {
// Regular function
ret1 = c1.returnType;
params1 = c1.parameters;
} else {
var call1 = c1.instanceMembers[#call];
if (call1 == null || !call1.isRegularMethod) return false;
// Class that emulate a function
ret1 = call1.returnType;
params1 = call1.parameters;
}
// Any type that implements a call method implicitly subtypes Function.
if (_reflects(c2, Function)) return true;
// Check structural function subtyping
return _isFunctionSubType(ret1, params1, c2.returnType, c2.parameters);
}