Index: lib/runtime/_types.js |
diff --git a/lib/runtime/_types.js b/lib/runtime/_types.js |
new file mode 100644 |
index 0000000000000000000000000000000000000000..e8dc2d0c38c034a33867abc9f5b2719181fa7fb5 |
--- /dev/null |
+++ b/lib/runtime/_types.js |
@@ -0,0 +1,439 @@ |
+// 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. |
+ |
+/* This library defines the representation of runtime types. |
+*/ |
+ |
+dart_library.library('dart_runtime/_types', null, /* Imports */[ |
+], /* Lazy Imports */[ |
+ 'dart/core', |
+ 'dart_runtime/_classes', |
+ 'dart_runtime/_rtti' |
+], function(exports, core, classes, rtti) { |
+ 'use strict'; |
+ |
+ const getOwnPropertyNames = Object.getOwnPropertyNames; |
+ |
+ const assert = dart_utils.assert; |
+ const copyProperties = dart_utils.copyProperties; |
+ const safeGetOwnProperty = dart_utils.safeGetOwnProperty; |
+ |
+ class TypeRep extends rtti.LazyTagged(() => core.Type) { |
+ get name() {return this.toString();} |
+ } |
+ |
+ class Dynamic extends TypeRep { |
+ toString() { return "dynamic"; } |
+ } |
+ let dynamicR = new Dynamic(); |
+ exports.dynamic = dynamicR; |
+ |
+ class Void extends TypeRep { |
+ toString() { return "void"; } |
+ } |
+ |
+ let voidR = new Void(); |
+ exports.void = voidR; |
+ |
+ class Bottom extends TypeRep { |
+ toString() { return "bottom"; } |
+ }; |
+ let bottomR = new Bottom(); |
+ exports.bottom = bottomR; |
+ |
+ class AbstractFunctionType extends TypeRep { |
+ constructor() { |
+ super(); |
+ this._stringValue = null; |
+ } |
+ |
+ toString() { return this.name; } |
+ |
+ get name() { |
+ if (this._stringValue) return this._stringValue; |
+ |
+ let buffer = '('; |
+ for (let i = 0; i < this.args.length; ++i) { |
+ if (i > 0) { |
+ buffer += ', '; |
+ } |
+ buffer += typeName(this.args[i]); |
+ } |
+ if (this.optionals.length > 0) { |
+ if (this.args.length > 0) buffer += ', '; |
+ buffer += '['; |
+ for (let i = 0; i < this.optionals.length; ++i) { |
+ if (i > 0) { |
+ buffer += ', '; |
+ } |
+ buffer += typeName(this.optionals[i]); |
+ } |
+ buffer += ']'; |
+ } else if (Object.keys(this.named).length > 0) { |
+ if (this.args.length > 0) buffer += ', '; |
+ buffer += '{'; |
+ let names = getOwnPropertyNames(this.named).sort(); |
+ for (let i = 0; i < names.length; ++i) { |
+ if (i > 0) { |
+ buffer += ', '; |
+ } |
+ buffer += names[i] + ': ' + typeName(this.named[names[i]]); |
+ } |
+ buffer += '}'; |
+ } |
+ |
+ buffer += ') -> ' + typeName(this.returnType); |
+ this._stringValue = buffer; |
+ return buffer; |
+ } |
+ } |
+ |
+ class FunctionType extends AbstractFunctionType { |
+ constructor(returnType, args, optionals, named) { |
+ super(); |
+ this.returnType = returnType; |
+ this.args = args; |
+ this.optionals = optionals; |
+ this.named = named; |
+ } |
+ } |
+ |
+ class Typedef extends AbstractFunctionType { |
+ constructor(name, closure) { |
+ super(); |
+ this._name = name; |
+ this._closure = closure; |
+ this._functionType = null; |
+ } |
+ |
+ get name() { |
+ return this._name; |
+ } |
+ |
+ get functionType() { |
+ if (!this._functionType) { |
+ this._functionType = this._closure(); |
+ } |
+ return this._functionType; |
+ } |
+ |
+ get returnType() { |
+ return this.functionType.returnType; |
+ } |
+ |
+ get args() { |
+ return this.functionType.args; |
+ } |
+ |
+ get optionals() { |
+ return this.functionType.optionals; |
+ } |
+ |
+ get named() { |
+ return this.functionType.named; |
+ } |
+ } |
+ |
+ function functionType(returnType, args, extra) { |
+ // TODO(vsm): Cache / memomize? |
+ let optionals; |
+ let named; |
+ if (extra === void 0) { |
+ optionals = []; |
+ named = {}; |
+ } else if (extra instanceof Array) { |
+ optionals = extra; |
+ named = {}; |
+ } else { |
+ optionals = []; |
+ named = extra; |
+ } |
+ return new FunctionType(returnType, args, optionals, named); |
+ } |
+ exports.functionType = functionType; |
+ |
+ function typedef(name, closure) { |
+ return new Typedef(name, closure); |
+ } |
+ exports.typedef = typedef; |
+ |
+ function isDartType(type) { |
+ return rtti.read(type) === core.Type; |
+ } |
+ exports.isDartType = isDartType; |
+ |
+ function typeName(type) { |
+ // Non-instance types |
+ if (type instanceof TypeRep) return type.toString(); |
+ // Instance types |
+ let tag = rtti.read(type); |
+ if (tag === core.Type) { |
+ let name = type.name; |
+ let args = classes.getGenericArgs(type); |
+ if (args) { |
+ name += '<'; |
+ for (let i = 0; i < args.length; ++i) { |
+ if (i > 0) name += ', '; |
+ name += typeName(args[i]); |
+ } |
+ name += '>'; |
+ } |
+ return name; |
+ } |
+ if (tag) return "Not a type: " + tag.name; |
+ return "JSObject<" + type.name + ">"; |
+ } |
+ exports.typeName = typeName; |
+ |
+ function isFunctionType(type) { |
+ return type instanceof AbstractFunctionType || type == core.Function; |
+ } |
+ |
+ function isFunctionSubType(ft1, ft2) { |
+ if (ft2 == core.Function) { |
+ return true; |
+ } |
+ |
+ let ret1 = ft1.returnType; |
+ let ret2 = ft2.returnType; |
+ |
+ 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 != voidR) { |
+ return false; |
+ } |
+ } |
+ |
+ let args1 = ft1.args; |
+ let args2 = ft2.args; |
+ |
+ if (args1.length > args2.length) { |
+ return false; |
+ } |
+ |
+ for (let i = 0; i < args1.length; ++i) { |
+ if (!isSubtype_(args2[i], args1[i], true)) { |
+ return false; |
+ } |
+ } |
+ |
+ let optionals1 = ft1.optionals; |
+ let optionals2 = ft2.optionals; |
+ |
+ if (args1.length + optionals1.length < args2.length + optionals2.length) { |
+ return false; |
+ } |
+ |
+ let j = 0; |
+ for (let i = args1.length; i < args2.length; ++i, ++j) { |
+ if (!isSubtype_(args2[i], optionals1[j], true)) { |
+ return false; |
+ } |
+ } |
+ |
+ for (let i = 0; i < optionals2.length; ++i, ++j) { |
+ if (!isSubtype_(optionals2[i], optionals1[j], true)) { |
+ return false; |
+ } |
+ } |
+ |
+ let named1 = ft1.named; |
+ let named2 = ft2.named; |
+ |
+ let names = getOwnPropertyNames(named2); |
+ for (let i = 0; i < names.length; ++i) { |
+ let name = names[i]; |
+ let n1 = named1[name]; |
+ let n2 = named2[name]; |
+ if (n1 === void 0) { |
+ return false; |
+ } |
+ if (!isSubtype_(n2, n1, true)) { |
+ return false; |
+ } |
+ } |
+ |
+ return true; |
+ } |
+ |
+ /** |
+ * Computes the canonical type. |
+ * This maps JS types onto their corresponding Dart Type. |
+ */ |
+ // TODO(jmesserly): lots more needs to be done here. |
+ function canonicalType(t) { |
+ if (t === Object) return core.Object; |
+ if (t === Function) return core.Function; |
+ if (t === Array) return core.List; |
+ |
+ // We shouldn't normally get here with these types, unless something strange |
+ // happens like subclassing Number in JS and passing it to Dart. |
+ if (t === String) return core.String; |
+ if (t === Number) return core.double; |
+ if (t === Boolean) return core.bool; |
+ return t; |
+ } |
+ |
+ const subtypeMap = new Map(); |
+ function isSubtype(t1, t2) { |
+ // See if we already know the answer |
+ // TODO(jmesserly): general purpose memoize function? |
+ let map = subtypeMap.get(t1); |
+ let result; |
+ if (map) { |
+ result = map.get(t2); |
+ if (result !== void 0) return result; |
+ } else { |
+ subtypeMap.set(t1, map = new Map()); |
+ } |
+ result = isSubtype_(t1, t2) |
+ map.set(t2, result); |
+ return result; |
+ } |
+ exports.isSubtype = isSubtype; |
+ |
+ function _isBottom(type, dynamicIsBottom) { |
+ return (type == dynamicR && dynamicIsBottom) || type == bottomR; |
+ } |
+ |
+ function _isTop(type, dynamicIsBottom) { |
+ return type == core.Object || (type == dynamicR && !dynamicIsBottom); |
+ } |
+ |
+ function isSubtype_(t1, t2, opt_dynamicIsBottom) { |
+ let dynamicIsBottom = |
+ opt_dynamicIsBottom === void 0 ? false : opt_dynamicIsBottom; |
+ |
+ t1 = canonicalType(t1); |
+ t2 = canonicalType(t2); |
+ if (t1 == t2) return true; |
+ |
+ // In Dart, dynamic is effectively both top and bottom. |
+ // Here, we treat dynamic as one or the other depending on context, |
+ // but not both. |
+ |
+ // Trivially true. |
+ if (_isTop(t2, dynamicIsBottom) || _isBottom(t1, dynamicIsBottom)) { |
+ return true; |
+ } |
+ |
+ // Trivially false. |
+ if (_isTop(t1, dynamicIsBottom) || _isBottom(t2, dynamicIsBottom)) { |
+ return false; |
+ } |
+ |
+ // "Traditional" name-based subtype check. |
+ if (isClassSubType(t1, t2)) { |
+ return true; |
+ } |
+ |
+ // Function subtyping. |
+ // TODO(vsm): Handle Objects with call methods. Those are functions |
+ // even if they do not *nominally* subtype core.Function. |
+ if (isFunctionType(t1) && |
+ isFunctionType(t2)) { |
+ return isFunctionSubType(t1, t2); |
+ } |
+ return false; |
+ } |
+ |
+ function isClassSubType(t1, t2) { |
+ // 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> |
+ t1 = canonicalType(t1); |
+ assert(t2 == canonicalType(t2)); |
+ if (t1 == t2) return true; |
+ |
+ if (t1 == core.Object) return false; |
+ |
+ // If t1 is a JS Object, we may not hit core.Object. |
+ if (t1 == null) return t2 == core.Object || t2 == dynamicR; |
+ |
+ // Check if t1 and t2 have the same raw type. If so, check covariance on |
+ // type parameters. |
+ let raw1 = classes.getGenericClass(t1); |
+ let raw2 = classes.getGenericClass(t2); |
+ if (raw1 != null && raw1 == raw2) { |
+ let typeArguments1 = classes.getGenericArgs(t1); |
+ let typeArguments2 = classes.getGenericArgs(t2); |
+ let length = typeArguments1.length; |
+ if (typeArguments2.length == 0) { |
+ // t2 is the raw form of t1 |
+ return true; |
+ } else if (length == 0) { |
+ // t1 is raw, but t2 is not |
+ return false; |
+ } |
+ assert(length == typeArguments2.length); |
+ for (let i = 0; i < length; ++i) { |
+ if (!isSubtype(typeArguments1[i], typeArguments2[i])) { |
+ return false; |
+ } |
+ } |
+ return true; |
+ } |
+ |
+ // Check superclass. |
+ if (isClassSubType(t1.__proto__, t2)) return true; |
+ |
+ // Check mixins. |
+ let mixins = classes.getMixins(t1); |
+ if (mixins) { |
+ for (let m1 of mixins) { |
+ // TODO(jmesserly): remove the != null check once we can load core libs. |
+ if (m1 != null && isClassSubType(m1, t2)) return true; |
+ } |
+ } |
+ |
+ // Check interfaces. |
+ let getInterfaces = classes.getImplements(t1); |
+ if (getInterfaces) { |
+ for (let i1 of getInterfaces()) { |
+ // TODO(jmesserly): remove the != null check once we can load core libs. |
+ if (i1 != null && isClassSubType(i1, t2)) return true; |
+ } |
+ } |
+ |
+ return false; |
+ } |
+ |
+ // TODO(jmesserly): this isn't currently used, but it could be if we want |
+ // `obj is NonGroundType<T,S>` to be rejected at runtime instead of compile |
+ // time. |
+ function isGroundType(type) { |
+ // TODO(vsm): Cache this if we start using it at runtime. |
+ |
+ if (type instanceof AbstractFunctionType) { |
+ if (!_isTop(type.returnType, false)) return false; |
+ for (let i = 0; i < type.args.length; ++i) { |
+ if (!_isBottom(type.args[i], true)) return false; |
+ } |
+ for (let i = 0; i < type.optionals.length; ++i) { |
+ if (!_isBottom(type.optionals[i], true)) return false; |
+ } |
+ let names = getOwnPropertyNames(type.named); |
+ for (let i = 0; i < names.length; ++i) { |
+ if (!_isBottom(type.named[names[i]], true)) return false; |
+ } |
+ return true; |
+ } |
+ |
+ let typeArgs = classes.getGenericArgs(type); |
+ if (!typeArgs) return true; |
+ for (let t of typeArgs) { |
+ if (t != core.Object && t != dynamicR) return false; |
+ } |
+ return true; |
+ } |
+ exports.isGroundType = isGroundType; |
+ |
+}); |