OLD | NEW |
1 // Copyright (c) 2011, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2011, the Dart project authors. Please see the AUTHORS file |
2 // for details. All rights reserved. Use of this source code is governed by a | 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. | 3 // BSD-style license that can be found in the LICENSE file. |
4 | 4 |
5 /** | 5 /** |
6 * Represents a meta-value for code generation. | 6 * Represents a meta-value for code generation. |
7 */ | 7 */ |
8 class Value { | 8 class Value { |
9 Type _type; | 9 Type _type; |
10 | 10 |
(...skipping 193 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
204 /** Generate a call to an unknown function type. */ | 204 /** Generate a call to an unknown function type. */ |
205 Value _varCall(MethodGenerator context, Node node, Arguments args) { | 205 Value _varCall(MethodGenerator context, Node node, Arguments args) { |
206 // TODO(jmesserly): calls to unknown functions will bypass type checks, | 206 // TODO(jmesserly): calls to unknown functions will bypass type checks, |
207 // which normally happen on the caller side, or in the generated stub for | 207 // which normally happen on the caller side, or in the generated stub for |
208 // dynamic method calls. What should we do? | 208 // dynamic method calls. What should we do? |
209 var stub = world.functionType.getCallStub(args); | 209 var stub = world.functionType.getCallStub(args); |
210 return stub.invoke(context, node, this, args); | 210 return stub.invoke(context, node, this, args); |
211 } | 211 } |
212 | 212 |
213 /** True if convertTo would generate a conversion. */ | 213 /** True if convertTo would generate a conversion. */ |
214 // TODO(jmesserly): I don't like how this is coupled to convertTo. | |
215 bool needsConversion(Type toType) { | 214 bool needsConversion(Type toType) { |
216 var callMethod = toType.getCallMethod(); | 215 return this != convertTo(null, toType, isDynamic:true); |
217 if (callMethod != null) { | |
218 int arity = callMethod.parameters.length; | |
219 var myCall = type.getCallMethod(); | |
220 if (myCall == null || myCall.parameters.length != arity) { | |
221 return true; | |
222 } | |
223 } | |
224 if (options.enableTypeChecks) { | |
225 Type fromType = type; | |
226 if (type.isVar && (code != 'null' || !toType.isNullable)) { | |
227 fromType = world.objectType; | |
228 } | |
229 bool bothNum = type.isNum && toType.isNum; | |
230 return !(fromType.isSubtypeOf(toType) || bothNum); | |
231 } | |
232 return false; | |
233 } | 216 } |
234 | 217 |
235 /** | 218 /** |
236 * Assign or convert this value to another type. | 219 * Assign or convert this value to another type. |
237 * This is used for converting between function types, inserting type | 220 * This is used for converting between function types, inserting type |
238 * checks when --enable_type_checks is enabled, and wrapping callback | 221 * checks when --enable_type_checks is enabled, and wrapping callback |
239 * functions passed to the dom so we can restore their isolate context. | 222 * functions passed to the dom so we can restore their isolate context. |
240 */ | 223 */ |
241 // WARNING: this needs to be kept in sync with needsConversion above. | 224 Value convertTo(MethodGenerator context, Type toType, |
242 Value convertTo(MethodGenerator context, Type toType, Node node, | |
243 [bool isDynamic=false]) { | 225 [bool isDynamic=false]) { |
244 | 226 |
245 // Issue type warnings unless we are processing a dynamic operation. | 227 // Issue type warnings unless we are processing a dynamic operation. |
246 bool checked = !isDynamic; | 228 bool checked = !isDynamic; |
247 | 229 |
248 var callMethod = toType.getCallMethod(); | 230 var callMethod = toType.getCallMethod(); |
249 if (callMethod != null) { | 231 if (callMethod != null) { |
250 if (checked && !toType.isAssignable(type)) { | 232 if (checked && !toType.isAssignable(type)) { |
251 convertWarning(toType, node); | 233 convertWarning(toType); |
252 } | 234 } |
253 | 235 |
254 int arity = callMethod.parameters.length; | 236 return _maybeWrapFunction(toType, callMethod); |
255 var myCall = type.getCallMethod(); | |
256 if (myCall == null || myCall.parameters.length != arity) { | |
257 final stub = world.functionType.getCallStub(new Arguments.bare(arity)); | |
258 var val = new Value(toType, 'to\$${stub.name}($code)', node.span); | |
259 // TODO(sigmund): try to remove, see below | |
260 return _isDomCallback(toType) && !_isDomCallback(type) ? | |
261 val._wrapDomCallback(toType, arity) : val; | |
262 } else if (_isDomCallback(toType) && !_isDomCallback(type)) { | |
263 // TODO(sigmund): try to remove, see below | |
264 return _wrapDomCallback(toType, arity); | |
265 } | |
266 } | 237 } |
267 | 238 |
268 // If we're assigning from a var, pretend it's Object for the purpose of | 239 // If we're assigning from a var, pretend it's Object for the purpose of |
269 // runtime checks. | 240 // runtime checks. |
270 | 241 |
271 // TODO(jmesserly): I'm a little bothered by the fact that we can't call | 242 // TODO(jmesserly): I'm a little bothered by the fact that we can't call |
272 // isSubtypeOf directly. If we tracked null literals as the bottom type, | 243 // isSubtypeOf directly. If we tracked null literals as the bottom type, |
273 // and then only allowed Dynamic to be bottom for generic type args, I think | 244 // and then only allowed Dynamic to be bottom for generic type args, I think |
274 // we'd get the right behavior from isSubtypeOf. | 245 // we'd get the right behavior from isSubtypeOf. |
275 Type fromType = type; | 246 Type fromType = type; |
276 if (type.isVar && (code != 'null' || !toType.isNullable)) { | 247 if (type.isVar && (code != 'null' || !toType.isNullable)) { |
277 fromType = world.objectType; | 248 fromType = world.objectType; |
278 } | 249 } |
279 | 250 |
280 // TODO(jmesserly): remove the special case for "num" when our num handling | 251 // TODO(jmesserly): remove the special case for "num" when our num handling |
281 // is better. | 252 // is better. |
282 bool bothNum = type.isNum && toType.isNum; | 253 bool bothNum = type.isNum && toType.isNum; |
283 if (fromType.isSubtypeOf(toType) || bothNum) { | 254 if (fromType.isSubtypeOf(toType) || bothNum) { |
284 // No checks needed for a widening conversion. | 255 // No checks needed for a widening conversion. |
285 return this; | 256 return this; |
286 } | 257 } |
287 | 258 |
288 if (checked && !toType.isSubtypeOf(type)) { | 259 if (checked && !toType.isSubtypeOf(type)) { |
289 // According to the static types, this conversion can't work. | 260 // According to the static types, this conversion can't work. |
290 convertWarning(toType, node); | 261 convertWarning(toType); |
291 } | 262 } |
292 | 263 |
293 // Generate a runtime checks if they're turned on, otherwise skip it. | 264 // Generate a runtime checks if they're turned on, otherwise skip it. |
294 if (options.enableTypeChecks) { | 265 if (options.enableTypeChecks) { |
295 return _typeAssert(context, toType, node, isDynamic); | 266 if (context == null && isDynamic) { |
| 267 // If we're just testing if we need the conversion, not actually doing |
| 268 // it we don't need a context. Just return something that is != this. |
| 269 // TODO(jmesserly): I don't like using null in this fashion, but it's |
| 270 // better than before where we had two code paths that needed to be |
| 271 // kept in sync. |
| 272 return null; |
| 273 } |
| 274 return _typeAssert(context, toType, isDynamic); |
296 } else { | 275 } else { |
297 return this; | 276 return this; |
298 } | 277 } |
299 } | 278 } |
300 | 279 |
301 /** | 280 /** |
302 * Checks whether [toType] is a callback function, and it is defined in the | 281 * Wraps a function with a conversion, so it can be called directly from |
303 * dom library. | 282 * Dart or JS code with the proper arity. We avoid the wrapping if the target |
| 283 * function has the same arity. |
| 284 * |
| 285 * Also wraps a callback attached to the dom (e.g. event listeners, |
| 286 * setTimeout) so we can restore it's isolate context information. This is |
| 287 * needed so that callbacks are executed within the context of the isolate |
| 288 * that created them in the first place. |
304 */ | 289 */ |
305 bool _isDomCallback(toType) { | 290 Value _maybeWrapFunction(Type toType, MethodMember callMethod) { |
306 return (toType.definition is FunctionTypeDefinition | 291 int arity = callMethod.parameters.length; |
307 && toType.library == world.dom); | 292 var myCall = type.getCallMethod(); |
| 293 |
| 294 Value result = this; |
| 295 if (myCall == null || myCall.parameters.length != arity) { |
| 296 final stub = world.functionType.getCallStub(new Arguments.bare(arity)); |
| 297 result = new Value(toType, 'to\$${stub.name}($code)', span); |
| 298 } |
| 299 |
| 300 if (toType.library.isDom && !type.library.isDom) { |
| 301 // TODO(jmesserly): either remove this or make it a more first class |
| 302 // feature of our native interop. We shouldn't be checking for the DOM |
| 303 // library--any host environment (like node.js) might need this feature |
| 304 // for isolates too. But we don't want to wrap every function we send to |
| 305 // native code--many callbacks like List.filter are perfectly safe. |
| 306 if (arity == 0) { |
| 307 world.gen.corejs.useWrap0 = true; |
| 308 } else { |
| 309 world.gen.corejs.useWrap1 = true; |
| 310 } |
| 311 |
| 312 result = new Value(toType, '\$wrap_call\$$arity(${result.code})', span); |
| 313 } |
| 314 |
| 315 return result; |
308 } | 316 } |
309 | 317 |
310 /** | 318 /** |
311 * Wraps a callback attached to the dom (e.g. event listeners, setTimeout) so | |
312 * we can restore it's isolate context information. This is needed so that | |
313 * callbacks are executed within the context of the isolate that created them | |
314 * in the first place. | |
315 */ | |
316 // TODO(sigmund): try to remove this specialized logic about isolates | |
317 // and the dom from the compiler, move into the actual dom library if | |
318 // possible. | |
319 Value _wrapDomCallback(Type toType, int arity) { | |
320 if (arity == 0) { | |
321 world.gen.corejs.useWrap0 = true; | |
322 } else { | |
323 world.gen.corejs.useWrap1 = true; | |
324 } | |
325 return new Value(toType, '\$wrap_call\$$arity($code)', span); | |
326 } | |
327 | |
328 /** | |
329 * Generates a run time type assertion for the given value. This works like | 319 * Generates a run time type assertion for the given value. This works like |
330 * [instanceOf], but it allows null since Dart types are nullable. | 320 * [instanceOf], but it allows null since Dart types are nullable. |
331 * Also it will throw a TypeError if it gets the wrong type. | 321 * Also it will throw a TypeError if it gets the wrong type. |
332 */ | 322 */ |
333 Value _typeAssert(MethodGenerator context, Type toType, Node node, | 323 Value _typeAssert(MethodGenerator context, Type toType, bool isDynamic) { |
334 bool isDynamic) { | |
335 if (toType is ParameterType) { | 324 if (toType is ParameterType) { |
336 ParameterType p = toType; | 325 ParameterType p = toType; |
337 toType = p.extendsType; | 326 toType = p.extendsType; |
338 } | 327 } |
339 | 328 |
340 if (toType.isObject || toType.isVar) { | 329 if (toType.isObject || toType.isVar) { |
341 world.internalError( | 330 world.internalError( |
342 'We thought ${type.name} is not a subtype of ${toType.name}?'); | 331 'We thought ${type.name} is not a subtype of ${toType.name}?'); |
343 } | 332 } |
344 | 333 |
345 // Prevent a stack overflow when forceDynamic and type checks are both | 334 // Prevent a stack overflow when forceDynamic and type checks are both |
346 // enabled. forceDynamic would cause the TypeError constructor to type check | 335 // enabled. forceDynamic would cause the TypeError constructor to type check |
347 // its arguments, which in turn invokes the TypeError constructor, ad | 336 // its arguments, which in turn invokes the TypeError constructor, ad |
348 // infinitum. | 337 // infinitum. |
349 String throwTypeError(String paramName) => world.withoutForceDynamic(() { | 338 String throwTypeError(String paramName) => world.withoutForceDynamic(() { |
350 final typeError = world.corelib.types['TypeError']; | 339 final typeErrorCtor = world.typeErrorType.getConstructor('_internal'); |
351 final typeErrorCtor = typeError.getConstructor('_internal'); | |
352 world.gen.corejs.ensureTypeNameOf(); | 340 world.gen.corejs.ensureTypeNameOf(); |
353 final result = typeErrorCtor.invoke(context, node, | 341 final result = typeErrorCtor.invoke(context, null, |
354 new Value.type(typeError, null), | 342 new Value.type(world.typeErrorType, null), |
355 new Arguments(null, [ | 343 new Arguments(null, [ |
356 new Value(world.objectType, paramName, null), | 344 new Value(world.objectType, paramName, null), |
357 new Value(world.stringType, '"${toType.name}"', null)]), | 345 new Value(world.stringType, '"${toType.name}"', null)]), |
358 isDynamic); | 346 isDynamic); |
359 world.gen.corejs.useThrow = true; | 347 world.gen.corejs.useThrow = true; |
360 return '\$throw(${result.code})'; | 348 return '\$throw(${result.code})'; |
361 }); | 349 }); |
362 | 350 |
363 // TODO(jmesserly): better assert for integers? | 351 // TODO(jmesserly): better assert for integers? |
364 if (toType.isNum) toType = world.numType; | 352 if (toType.isNum) toType = world.numType; |
(...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
399 | 387 |
400 String checkName = 'assert\$' + toType.jsname; | 388 String checkName = 'assert\$' + toType.jsname; |
401 | 389 |
402 // If we track nullability, we could simplify this check. | 390 // If we track nullability, we could simplify this check. |
403 var temp = context.getTemp(this); | 391 var temp = context.getTemp(this); |
404 check = '(${context.assignTemp(temp, this).code} == null ? null :'; | 392 check = '(${context.assignTemp(temp, this).code} == null ? null :'; |
405 check += ' ${temp.code}.$checkName())'; | 393 check += ' ${temp.code}.$checkName())'; |
406 if (this != temp) context.freeTemp(temp); | 394 if (this != temp) context.freeTemp(temp); |
407 | 395 |
408 // Generate the fallback on Object (that throws a TypeError) | 396 // Generate the fallback on Object (that throws a TypeError) |
409 if (!world.objectType.varStubs.containsKey(checkName)) { | 397 world.objectType.varStubs.putIfAbsent(checkName, |
410 world.objectType.varStubs[checkName] = new VarMethodStub(checkName, | 398 () => new VarMethodStub(checkName, null, Arguments.EMPTY, |
411 null, Arguments.EMPTY, throwTypeError('this')); | 399 throwTypeError('this'))); |
412 } | |
413 } | 400 } |
414 | 401 |
415 return new Value(toType, check, span); | 402 return new Value(toType, check, span); |
416 } | 403 } |
417 | 404 |
418 /** | 405 /** |
419 * Test to see if value is an instance of this type. | 406 * Test to see if value is an instance of this type. |
420 * | 407 * |
421 * - If a primitive type, then uses the JavaScript typeof. | 408 * - If a primitive type, then uses the JavaScript typeof. |
422 * - If it's a non-generic class, use instanceof. | 409 * - If it's a non-generic class, use instanceof. |
(...skipping 55 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
478 | 465 |
479 // Generate the fallback on Object (that returns false) | 466 // Generate the fallback on Object (that returns false) |
480 if (!world.objectType.varStubs.containsKey(checkName)) { | 467 if (!world.objectType.varStubs.containsKey(checkName)) { |
481 world.objectType.varStubs[checkName] = | 468 world.objectType.varStubs[checkName] = |
482 new VarMethodStub(checkName, null, Arguments.EMPTY, 'return false'); | 469 new VarMethodStub(checkName, null, Arguments.EMPTY, 'return false'); |
483 } | 470 } |
484 } | 471 } |
485 return new Value(world.nonNullBool, testCode, span); | 472 return new Value(world.nonNullBool, testCode, span); |
486 } | 473 } |
487 | 474 |
488 void convertWarning(Type toType, Node node) { | 475 void convertWarning(Type toType) { |
489 // TODO(jmesserly): better error messages for type conversion failures | 476 // TODO(jmesserly): better error messages for type conversion failures |
490 world.warning('type "${type.name}" is not assignable to "${toType.name}"', | 477 world.warning('type "${type.name}" is not assignable to "${toType.name}"', |
491 node.span); | 478 span); |
492 } | 479 } |
493 | 480 |
494 Value invokeNoSuchMethod(MethodGenerator context, String name, Node node, | 481 Value invokeNoSuchMethod(MethodGenerator context, String name, Node node, |
495 [Arguments args]) { | 482 [Arguments args]) { |
496 var pos = ''; | 483 var pos = ''; |
497 if (args != null) { | 484 if (args != null) { |
498 var argsCode = []; | 485 var argsCode = []; |
499 for (int i = 0; i < args.length; i++) { | 486 for (int i = 0; i < args.length; i++) { |
500 argsCode.add(args.values[i].code); | 487 argsCode.add(args.values[i].code); |
501 } | 488 } |
(...skipping 249 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
751 } | 738 } |
752 | 739 |
753 _ensureCode(); | 740 _ensureCode(); |
754 return null; | 741 return null; |
755 } | 742 } |
756 } | 743 } |
757 | 744 |
758 String _escapeForComment(String text) { | 745 String _escapeForComment(String text) { |
759 return text.replaceAll('/*', '/ *').replaceAll('*/', '* /'); | 746 return text.replaceAll('/*', '/ *').replaceAll('*/', '* /'); |
760 } | 747 } |
OLD | NEW |