| 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 /** The [Type] of the [Value]. */ | 9 /** The [Type] of the [Value]. */ |
| 10 Type type; | 10 Type type; |
| (...skipping 275 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 286 return this; | 286 return this; |
| 287 } | 287 } |
| 288 | 288 |
| 289 if (checked && !toType.isSubtypeOf(type)) { | 289 if (checked && !toType.isSubtypeOf(type)) { |
| 290 // According to the static types, this conversion can't work. | 290 // According to the static types, this conversion can't work. |
| 291 convertWarning(toType, node); | 291 convertWarning(toType, node); |
| 292 } | 292 } |
| 293 | 293 |
| 294 // Generate a runtime checks if they're turned on, otherwise skip it. | 294 // Generate a runtime checks if they're turned on, otherwise skip it. |
| 295 if (options.enableTypeChecks) { | 295 if (options.enableTypeChecks) { |
| 296 return _typeAssert(context, toType, node); | 296 return _typeAssert(context, toType, node, isDynamic); |
| 297 } else { | 297 } else { |
| 298 return this; | 298 return this; |
| 299 } | 299 } |
| 300 } | 300 } |
| 301 | 301 |
| 302 /** | 302 /** |
| 303 * Checks whether [toType] is a callback function, and it is defined in the | 303 * Checks whether [toType] is a callback function, and it is defined in the |
| 304 * dom library. | 304 * dom library. |
| 305 */ | 305 */ |
| 306 bool _isDomCallback(toType) { | 306 bool _isDomCallback(toType) { |
| (...skipping 17 matching lines...) Expand all Loading... |
| 324 world.gen.corejs.useWrap1 = true; | 324 world.gen.corejs.useWrap1 = true; |
| 325 } | 325 } |
| 326 return new Value(toType, '\$wrap_call\$$arity($code)', span); | 326 return new Value(toType, '\$wrap_call\$$arity($code)', span); |
| 327 } | 327 } |
| 328 | 328 |
| 329 /** | 329 /** |
| 330 * Generates a run time type assertion for the given value. This works like | 330 * Generates a run time type assertion for the given value. This works like |
| 331 * [instanceOf], but it allows null since Dart types are nullable. | 331 * [instanceOf], but it allows null since Dart types are nullable. |
| 332 * Also it will throw a TypeError if it gets the wrong type. | 332 * Also it will throw a TypeError if it gets the wrong type. |
| 333 */ | 333 */ |
| 334 Value _typeAssert(MethodGenerator context, Type toType, Node node) { | 334 Value _typeAssert(MethodGenerator context, Type toType, Node node, |
| 335 bool isDynamic) { |
| 335 if (toType is ParameterType) { | 336 if (toType is ParameterType) { |
| 336 ParameterType p = toType; | 337 ParameterType p = toType; |
| 337 toType = p.extendsType; | 338 toType = p.extendsType; |
| 338 } | 339 } |
| 339 | 340 |
| 340 // TODO(jmesserly): fix checking of function types, and DOM objects | 341 if (toType.isObject || toType.isVar) { |
| 341 // For now, don't generate a broken check. | 342 world.internalError( |
| 342 // (For DOM types to work right, we need to lazily patch the "is$DOMWindow" | 343 'We thought ${type.name} is not a subtype of ${toType.name}?'); |
| 343 // check methods, by catching it on Object.prototype like VarMember does) | |
| 344 if (toType.getCallMethod() != null || toType.library == world.dom) { | |
| 345 return this; | |
| 346 } | 344 } |
| 347 | 345 |
| 348 if (toType.isObject || toType.isVar) { | 346 final typeError = world.corelib.types['TypeError']; |
| 349 world.internalError('We thought ${type.name} is not a subtype of ${toType.
name}?'); | 347 final typeErrorCtor = typeError.getConstructor('_internal'); |
| 350 } | 348 world.gen.corejs.ensureTypeNameOf(); |
| 349 final result = typeErrorCtor.invoke(context, node, |
| 350 new Value.type(typeError, null), |
| 351 new Arguments(null, [ |
| 352 new Value(world.objectType, 'this', null), |
| 353 new Value(world.stringType, '"${toType.name}"', null)]), |
| 354 isDynamic); |
| 355 world.gen.corejs.useThrow = true; |
| 356 final throwTypeError = '\$throw(${result.code})'; |
| 351 | 357 |
| 352 // TODO(jmesserly): better assert for integers? | 358 // TODO(jmesserly): better assert for integers? |
| 353 if (toType.isNum) toType = world.numType; | 359 if (toType.isNum) toType = world.numType; |
| 354 | 360 |
| 355 // Generate a check like these: | 361 // Generate a check like these: |
| 356 // obj && obj.is$TypeName() | 362 // obj && obj.is$TypeName() |
| 357 // $assert_int(obj) | 363 // $assert_int(obj) |
| 358 // | 364 // |
| 359 // We rely on the fact that calling an undefined method produces a JS | 365 // We rely on the fact that calling an undefined method produces a JS |
| 360 // TypeError. Alternatively we could define fallbacks on Object that throw. | 366 // TypeError. Alternatively we could define fallbacks on Object that throw. |
| 361 String check; | 367 String check; |
| 362 if (toType.isVoid) { | 368 if (toType.isVoid) { |
| 363 check = '\$assert_void($code)'; | 369 check = '\$assert_void($code)'; |
| 364 if (toType.typeCheckCode == null) { | 370 if (toType.typeCheckCode == null) { |
| 365 toType.typeCheckCode = ''' | 371 toType.typeCheckCode = ''' |
| 366 function \$assert_void(x) { | 372 function \$assert_void(x) { |
| 367 return x == null ? x : x.is\$void(); // throws TypeError | 373 if (x == null) return null; |
| 374 $throwTypeError |
| 368 }'''; | 375 }'''; |
| 369 } | 376 } |
| 370 } else if (toType == world.nonNullBool) { | 377 } else if (toType == world.nonNullBool) { |
| 371 // This could be made less of a special case | 378 // This could be made less of a special case |
| 372 world.gen.corejs.useNotNullBool = true; | 379 world.gen.corejs.useNotNullBool = true; |
| 373 check = '\$notnull_bool($code)'; | 380 check = '\$notnull_bool($code)'; |
| 374 | 381 |
| 375 } else if (toType.library.isCore && toType.typeofName != null) { | 382 } else if (toType.library.isCore && toType.typeofName != null) { |
| 376 check = '\$assert_${toType.name}($code)'; | 383 check = '\$assert_${toType.name}($code)'; |
| 377 | 384 |
| 378 if (toType.typeCheckCode == null) { | 385 if (toType.typeCheckCode == null) { |
| 379 toType.typeCheckCode = ''' | 386 toType.typeCheckCode = ''' |
| 380 function \$assert_${toType.name}(x) { | 387 function \$assert_${toType.name}(x) { |
| 381 if (x == null || typeof(x) == "${toType.typeofName}") return x; | 388 if (x == null || typeof(x) == "${toType.typeofName}") return x; |
| 382 throw new TypeError("'" + x + "' is not a ${toType.name}."); | 389 $throwTypeError |
| 383 }'''; | 390 }'''; |
| 384 } | 391 } |
| 385 } else { | 392 } else { |
| 386 toType.isTested = true; | 393 toType.isChecked = true; |
| 394 |
| 395 String checkName = 'assert\$' + toType.jsname; |
| 387 | 396 |
| 388 // If we track nullability, we could simplify this check. | 397 // If we track nullability, we could simplify this check. |
| 389 var temp = context.getTemp(this); | 398 var temp = context.getTemp(this); |
| 390 check = '(${context.assignTemp(temp, this).code} &&'; | 399 check = '(${context.assignTemp(temp, this).code} == null ? null :'; |
| 391 check += ' ${temp.code}.is\$${toType.jsname}())'; | 400 check += ' ${temp.code}.$checkName())'; |
| 392 if (this != temp) context.freeTemp(temp); | 401 if (this != temp) context.freeTemp(temp); |
| 402 |
| 403 // Generate the fallback on Object (that throws a TypeError) |
| 404 if (!world.objectType.varStubs.containsKey(checkName)) { |
| 405 world.objectType.varStubs[checkName] = |
| 406 new VarMethodStub(checkName, null, Arguments.EMPTY, throwTypeError); |
| 407 } |
| 393 } | 408 } |
| 394 | 409 |
| 395 return new Value(toType, check, span); | 410 return new Value(toType, check, span); |
| 396 } | 411 } |
| 397 | 412 |
| 398 /** | 413 /** |
| 399 * Test to see if value is an instance of this type. | 414 * Test to see if value is an instance of this type. |
| 400 * | 415 * |
| 401 * - If a primitive type, then uses the JavaScript typeof. | 416 * - If a primitive type, then uses the JavaScript typeof. |
| 402 * - If it's a non-generic class, use instanceof. | 417 * - If it's a non-generic class, use instanceof. |
| (...skipping 18 matching lines...) Expand all Loading... |
| 421 return new EvaluatedValue(world.nonNullBool, true, 'true', null); | 436 return new EvaluatedValue(world.nonNullBool, true, 'true', null); |
| 422 } | 437 } |
| 423 } | 438 } |
| 424 | 439 |
| 425 if (toType.library.isCore) { | 440 if (toType.library.isCore) { |
| 426 var typeofName = toType.typeofName; | 441 var typeofName = toType.typeofName; |
| 427 if (typeofName != null) { | 442 if (typeofName != null) { |
| 428 testCode = "(typeof($code) ${isTrue ? '==' : '!='} '$typeofName')"; | 443 testCode = "(typeof($code) ${isTrue ? '==' : '!='} '$typeofName')"; |
| 429 } | 444 } |
| 430 } | 445 } |
| 431 if (toType.isClass && toType is !ConcreteType) { | 446 if (toType.isClass && toType is !ConcreteType |
| 447 && !toType.isHiddenNativeType) { |
| 432 toType.markUsed(); | 448 toType.markUsed(); |
| 433 testCode = '($code instanceof ${toType.jsname})'; | 449 testCode = '($code instanceof ${toType.jsname})'; |
| 434 if (!isTrue) { | 450 if (!isTrue) { |
| 435 testCode = '!' + testCode; | 451 testCode = '!' + testCode; |
| 436 } | 452 } |
| 437 } | 453 } |
| 438 if (testCode == null) { | 454 if (testCode == null) { |
| 439 toType.isTested = true; | 455 toType.isTested = true; |
| 440 | 456 |
| 441 // If we track nullability, we could simplify this check. | 457 // If we track nullability, we could simplify this check. |
| 442 var temp = context.getTemp(this); | 458 var temp = context.getTemp(this); |
| 443 | 459 |
| 460 String checkName = 'is\$${toType.jsname}'; |
| 444 testCode = '(${context.assignTemp(temp, this).code} &&'; | 461 testCode = '(${context.assignTemp(temp, this).code} &&'; |
| 445 testCode += ' ${temp.code}.is\$${toType.jsname})'; | 462 testCode += ' ${temp.code}.$checkName())'; |
| 446 if (isTrue) { | 463 if (isTrue) { |
| 447 // Add !! to convert to boolean. | 464 // Add !! to convert to boolean. |
| 448 // TODO(jimhug): only do this if needed | 465 // TODO(jimhug): only do this if needed |
| 449 testCode = '!!' + testCode; | 466 testCode = '!!' + testCode; |
| 450 } else { | 467 } else { |
| 451 // The single ! here nicely converts undefined to false and function | 468 // The single ! here nicely converts undefined to false and function |
| 452 // to true. | 469 // to true. |
| 453 testCode = '!' + testCode; | 470 testCode = '!' + testCode; |
| 454 } | 471 } |
| 455 if (this != temp) context.freeTemp(temp); | 472 if (this != temp) context.freeTemp(temp); |
| 473 |
| 474 // Generate the fallback on Object (that returns false) |
| 475 if (!world.objectType.varStubs.containsKey(checkName)) { |
| 476 world.objectType.varStubs[checkName] = |
| 477 new VarMethodStub(checkName, null, Arguments.EMPTY, 'return false'); |
| 478 } |
| 456 } | 479 } |
| 457 return new Value(world.nonNullBool, testCode, span); | 480 return new Value(world.nonNullBool, testCode, span); |
| 458 } | 481 } |
| 459 | 482 |
| 460 void convertWarning(Type toType, Node node) { | 483 void convertWarning(Type toType, Node node) { |
| 461 // TODO(jmesserly): better error messages for type conversion failures | 484 // TODO(jmesserly): better error messages for type conversion failures |
| 462 world.warning('type "${type.name}" is not assignable to "${toType.name}"', | 485 world.warning('type "${type.name}" is not assignable to "${toType.name}"', |
| 463 node.span); | 486 node.span); |
| 464 } | 487 } |
| 465 | 488 |
| (...skipping 260 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 726 // Then look for members in my library. | 749 // Then look for members in my library. |
| 727 member = home.library.lookup(name, span); | 750 member = home.library.lookup(name, span); |
| 728 if (member != null) { | 751 if (member != null) { |
| 729 return member; | 752 return member; |
| 730 } | 753 } |
| 731 | 754 |
| 732 _ensureCode(); | 755 _ensureCode(); |
| 733 return null; | 756 return null; |
| 734 } | 757 } |
| 735 } | 758 } |
| OLD | NEW |