OLD | NEW |
---|---|
(Empty) | |
1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file | |
2 // for details. All rights reserved. Use of this source code is governed by a | |
3 // BSD-style license that can be found in the LICENSE file. | |
4 | |
5 /* This library defines the representation of runtime types. | |
6 */ | |
7 | |
8 loader.library('dart/types', null, /* Imports */[ | |
9 ], /* Lazy Imports */[ | |
10 'dart/classes', | |
11 'dart/core', | |
12 'dart/rtti' | |
13 ], function(exports, classes, core, rtti) { | |
14 'use strict'; | |
15 | |
16 const assert = js_utils.assert; | |
17 const copyProperties = js_utils.copyProperties; | |
18 const getOwnPropertyNames = js_utils.getOwnPropertyNames; | |
19 const safeGetOwnProperty = js_utils.safeGetOwnProperty; | |
20 const throwRuntimeError = js_utils.throwRuntimeError; | |
21 | |
22 class TypeRep extends rtti.LazyTagged(() => core.Type) { | |
23 get name() {return this.toString();} | |
24 } | |
25 | |
26 class Dynamic extends TypeRep { | |
Jennifer Messerly
2015/06/12 17:32:25
awesome!
| |
27 toString() { return "dynamic"; } | |
28 } | |
29 let dynamicR = new Dynamic(); | |
30 exports.dynamic = dynamicR; | |
31 | |
32 class Void extends TypeRep { | |
33 toString() { return "void"; } | |
34 } | |
35 | |
36 let voidR = new Void(); | |
37 exports.void = voidR; | |
38 | |
39 class Bottom extends TypeRep { | |
40 toString() { return "bottom"; } | |
41 }; | |
42 let bottomR = new Bottom(); | |
43 exports.bottom = bottomR; | |
44 | |
45 class AbstractFunctionType extends TypeRep { | |
46 constructor() { | |
47 super(); | |
48 this._stringValue = null; | |
49 } | |
50 | |
51 toString() { return this.name; } | |
52 | |
53 get name() { | |
54 if (this._stringValue) return this._stringValue; | |
55 | |
56 let buffer = '('; | |
57 for (let i = 0; i < this.args.length; ++i) { | |
58 if (i > 0) { | |
59 buffer += ', '; | |
60 } | |
61 buffer += typeToString(this.args[i]); | |
62 } | |
63 if (this.optionals.length > 0) { | |
64 if (this.args.length > 0) buffer += ', '; | |
65 buffer += '['; | |
66 for (let i = 0; i < this.optionals.length; ++i) { | |
67 if (i > 0) { | |
68 buffer += ', '; | |
69 } | |
70 buffer += typeToString(this.optionals[i]); | |
71 } | |
72 buffer += ']'; | |
73 } else if (Object.keys(this.named).length > 0) { | |
74 if (this.args.length > 0) buffer += ', '; | |
75 buffer += '{'; | |
76 let names = getOwnPropertyNames(this.named).sort(); | |
77 for (let i = 0; i < names.length; ++i) { | |
78 if (i > 0) { | |
79 buffer += ', '; | |
80 } | |
81 buffer += names[i] + ': ' + typeToString(this.named[names[i]]); | |
82 } | |
83 buffer += '}'; | |
84 } | |
85 | |
86 buffer += ') -> ' + typeToString(this.returnType); | |
87 this._stringValue = buffer; | |
88 return buffer; | |
89 } | |
90 } | |
91 | |
92 class FunctionType extends AbstractFunctionType { | |
93 constructor(returnType, args, optionals, named) { | |
94 super(); | |
95 this.returnType = returnType; | |
96 this.args = args; | |
97 this.optionals = optionals; | |
98 this.named = named; | |
99 } | |
100 } | |
101 | |
102 class Typedef extends AbstractFunctionType { | |
103 constructor(name, closure) { | |
104 super(); | |
105 this._name = name; | |
106 this._closure = closure; | |
107 this._functionType = null; | |
108 } | |
109 | |
110 get name() { | |
111 return this._name; | |
112 } | |
113 | |
114 get functionType() { | |
115 if (!this._functionType) { | |
116 this._functionType = this._closure(); | |
117 } | |
118 return this._functionType; | |
119 } | |
120 | |
121 get returnType() { | |
122 return this.functionType.returnType; | |
123 } | |
124 | |
125 get args() { | |
126 return this.functionType.args; | |
127 } | |
128 | |
129 get optionals() { | |
130 return this.functionType.optionals; | |
131 } | |
132 | |
133 get named() { | |
134 return this.functionType.named; | |
135 } | |
136 } | |
137 | |
138 function functionType(returnType, args, extra) { | |
139 // TODO(vsm): Cache / memomize? | |
140 let optionals; | |
141 let named; | |
142 if (extra === void 0) { | |
143 optionals = []; | |
144 named = {}; | |
145 } else if (extra instanceof Array) { | |
146 optionals = extra; | |
147 named = {}; | |
148 } else { | |
149 optionals = []; | |
150 named = extra; | |
151 } | |
152 return new FunctionType(returnType, args, optionals, named); | |
153 } | |
154 exports.functionType = functionType; | |
155 | |
156 function typedef(name, closure) { | |
157 return new Typedef(name, closure); | |
158 } | |
159 exports.typedef = typedef; | |
160 | |
161 function isDartType(type) { | |
162 return rtti.read(type) === core.Type; | |
163 } | |
164 exports.isDartType = isDartType; | |
165 | |
166 function typeToString(type) { | |
167 // Non-instance types | |
168 if (type instanceof TypeRep) return type.toString(); | |
169 // Instance types | |
170 let tag = rtti.read(type); | |
171 if (tag === core.Type) { | |
172 let name = type.name; | |
173 let args = classes.getGenericArgs(type); | |
174 if (args) { | |
175 name += '<'; | |
176 for (let i = 0; i < args.length; ++i) { | |
177 if (i > 0) name += ', '; | |
178 name += typeToString(args[i]); | |
179 } | |
180 name += '>'; | |
181 } | |
182 return name; | |
183 } | |
184 if (tag) return "Not a type: " + tag.name; | |
185 return "JSObject<" + type.name + ">"; | |
186 } | |
187 exports.typeToString = typeToString; | |
188 | |
189 function isFunctionType(type) { | |
190 return type instanceof AbstractFunctionType || type == core.Function; | |
191 } | |
192 | |
193 function isFunctionSubType(ft1, ft2) { | |
194 if (ft2 == core.Function) { | |
195 return true; | |
196 } | |
197 | |
198 let ret1 = ft1.returnType; | |
199 let ret2 = ft2.returnType; | |
200 | |
201 if (!isSubtype_(ret1, ret2)) { | |
202 // Covariant return types | |
203 // Note, void (which can only appear as a return type) is effectively | |
204 // treated as dynamic. If the base return type is void, we allow any | |
205 // subtype return type. | |
206 // E.g., we allow: | |
207 // () -> int <: () -> void | |
208 if (ret2 != voidR) { | |
209 return false; | |
210 } | |
211 } | |
212 | |
213 let args1 = ft1.args; | |
214 let args2 = ft2.args; | |
215 | |
216 if (args1.length > args2.length) { | |
217 return false; | |
218 } | |
219 | |
220 for (let i = 0; i < args1.length; ++i) { | |
221 if (!isSubtype_(args2[i], args1[i], true)) { | |
222 return false; | |
223 } | |
224 } | |
225 | |
226 let optionals1 = ft1.optionals; | |
227 let optionals2 = ft2.optionals; | |
228 | |
229 if (args1.length + optionals1.length < args2.length + optionals2.length) { | |
230 return false; | |
231 } | |
232 | |
233 let j = 0; | |
234 for (let i = args1.length; i < args2.length; ++i, ++j) { | |
235 if (!isSubtype_(args2[i], optionals1[j], true)) { | |
236 return false; | |
237 } | |
238 } | |
239 | |
240 for (let i = 0; i < optionals2.length; ++i, ++j) { | |
241 if (!isSubtype_(optionals2[i], optionals1[j], true)) { | |
242 return false; | |
243 } | |
244 } | |
245 | |
246 let named1 = ft1.named; | |
247 let named2 = ft2.named; | |
248 | |
249 let names = getOwnPropertyNames(named2); | |
250 for (let i = 0; i < names.length; ++i) { | |
251 let name = names[i]; | |
252 let n1 = named1[name]; | |
253 let n2 = named2[name]; | |
254 if (n1 === void 0) { | |
255 return false; | |
256 } | |
257 if (!isSubtype_(n2, n1, true)) { | |
258 return false; | |
259 } | |
260 } | |
261 | |
262 return true; | |
263 } | |
264 | |
265 /** | |
266 * Computes the canonical type. | |
267 * This maps JS types onto their corresponding Dart Type. | |
268 */ | |
269 // TODO(jmesserly): lots more needs to be done here. | |
270 function canonicalType(t) { | |
271 if (t === Object) return core.Object; | |
272 if (t === Function) return core.Function; | |
273 if (t === Array) return core.List; | |
274 | |
275 // We shouldn't normally get here with these types, unless something strange | |
276 // happens like subclassing Number in JS and passing it to Dart. | |
277 if (t === String) return core.String; | |
278 if (t === Number) return core.double; | |
279 if (t === Boolean) return core.bool; | |
280 return t; | |
281 } | |
282 | |
283 const subtypeMap = new Map(); | |
284 function isSubtype(t1, t2) { | |
285 // See if we already know the answer | |
286 // TODO(jmesserly): general purpose memoize function? | |
287 let map = subtypeMap.get(t1); | |
288 let result; | |
289 if (map) { | |
290 result = map.get(t2); | |
291 if (result !== void 0) return result; | |
292 } else { | |
293 subtypeMap.set(t1, map = new Map()); | |
294 } | |
295 result = isSubtype_(t1, t2) | |
296 map.set(t2, result); | |
297 return result; | |
298 } | |
299 exports.isSubtype = isSubtype; | |
300 | |
301 function _isBottom(type, dynamicIsBottom) { | |
302 return (type == dynamicR && dynamicIsBottom) || type == bottomR; | |
303 } | |
304 | |
305 function _isTop(type, dynamicIsBottom) { | |
306 return type == core.Object || (type == dynamicR && !dynamicIsBottom); | |
307 } | |
308 | |
309 function isSubtype_(t1, t2, opt_dynamicIsBottom) { | |
310 let dynamicIsBottom = | |
311 opt_dynamicIsBottom === void 0 ? false : opt_dynamicIsBottom; | |
312 | |
313 t1 = canonicalType(t1); | |
314 t2 = canonicalType(t2); | |
315 if (t1 == t2) return true; | |
316 | |
317 // In Dart, dynamic is effectively both top and bottom. | |
318 // Here, we treat dynamic as one or the other depending on context, | |
319 // but not both. | |
320 | |
321 // Trivially true. | |
322 if (_isTop(t2, dynamicIsBottom) || _isBottom(t1, dynamicIsBottom)) { | |
323 return true; | |
324 } | |
325 | |
326 // Trivially false. | |
327 if (_isTop(t1, dynamicIsBottom) || _isBottom(t2, dynamicIsBottom)) { | |
328 return false; | |
329 } | |
330 | |
331 // "Traditional" name-based subtype check. | |
332 if (isClassSubType(t1, t2)) { | |
333 return true; | |
334 } | |
335 | |
336 // Function subtyping. | |
337 // TODO(vsm): Handle Objects with call methods. Those are functions | |
338 // even if they do not *nominally* subtype core.Function. | |
339 if (isFunctionType(t1) && | |
340 isFunctionType(t2)) { | |
341 return isFunctionSubType(t1, t2); | |
342 } | |
343 return false; | |
344 } | |
345 | |
346 function isClassSubType(t1, t2) { | |
347 // We support Dart's covariant generics with the caveat that we do not | |
348 // substitute bottom for dynamic in subtyping rules. | |
349 // I.e., given T1, ..., Tn where at least one Ti != dynamic we disallow: | |
350 // - S !<: S<T1, ..., Tn> | |
351 // - S<dynamic, ..., dynamic> !<: S<T1, ..., Tn> | |
352 t1 = canonicalType(t1); | |
353 assert(t2 == canonicalType(t2)); | |
354 if (t1 == t2) return true; | |
355 | |
356 if (t1 == core.Object) return false; | |
357 | |
358 // If t1 is a JS Object, we may not hit core.Object. | |
359 if (t1 == null) return t2 == core.Object || t2 == dynamicR; | |
360 | |
361 // Check if t1 and t2 have the same raw type. If so, check covariance on | |
362 // type parameters. | |
363 let raw1 = classes.getGenericClass(t1); | |
364 let raw2 = classes.getGenericClass(t2); | |
365 if (raw1 != null && raw1 == raw2) { | |
366 let typeArguments1 = classes.getGenericArgs(t1); | |
367 let typeArguments2 = classes.getGenericArgs(t2); | |
368 let length = typeArguments1.length; | |
369 if (typeArguments2.length == 0) { | |
370 // t2 is the raw form of t1 | |
371 return true; | |
372 } else if (length == 0) { | |
373 // t1 is raw, but t2 is not | |
374 return false; | |
375 } | |
376 assert(length == typeArguments2.length); | |
377 for (let i = 0; i < length; ++i) { | |
378 if (!isSubtype(typeArguments1[i], typeArguments2[i])) { | |
379 return false; | |
380 } | |
381 } | |
382 return true; | |
383 } | |
384 | |
385 // Check superclass. | |
386 if (isClassSubType(t1.__proto__, t2)) return true; | |
387 | |
388 // Check mixins. | |
389 let mixins = classes.getMixins(t1); | |
390 if (mixins) { | |
391 for (let m1 of mixins) { | |
392 // TODO(jmesserly): remove the != null check once we can load core libs. | |
393 if (m1 != null && isClassSubType(m1, t2)) return true; | |
394 } | |
395 } | |
396 | |
397 // Check interfaces. | |
398 let getInterfaces = classes.getImplements(t1); | |
399 if (getInterfaces) { | |
400 for (let i1 of getInterfaces()) { | |
401 // TODO(jmesserly): remove the != null check once we can load core libs. | |
402 if (i1 != null && isClassSubType(i1, t2)) return true; | |
403 } | |
404 } | |
405 | |
406 return false; | |
407 } | |
408 | |
409 // TODO(jmesserly): this isn't currently used, but it could be if we want | |
410 // `obj is NonGroundType<T,S>` to be rejected at runtime instead of compile | |
411 // time. | |
412 function isGroundType(type) { | |
413 // TODO(vsm): Cache this if we start using it at runtime. | |
414 | |
415 if (type instanceof AbstractFunctionType) { | |
416 if (!_isTop(type.returnType, false)) return false; | |
417 for (let i = 0; i < type.args.length; ++i) { | |
418 if (!_isBottom(type.args[i], true)) return false; | |
419 } | |
420 for (let i = 0; i < type.optionals.length; ++i) { | |
421 if (!_isBottom(type.optionals[i], true)) return false; | |
422 } | |
423 let names = getOwnPropertyNames(type.named); | |
424 for (let i = 0; i < names.length; ++i) { | |
425 if (!_isBottom(type.named[names[i]], true)) return false; | |
426 } | |
427 return true; | |
428 } | |
429 | |
430 let typeArgs = classes.getGenericArgs(type); | |
431 if (!typeArgs) return true; | |
432 for (let t of typeArgs) { | |
433 if (t != core.Object && t != dynamicR) return false; | |
434 } | |
435 return true; | |
436 } | |
437 exports.isGroundType = isGroundType; | |
438 | |
439 }); | |
OLD | NEW |