| 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 |