Chromium Code Reviews| 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 139 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 150 } | 150 } |
| 151 | 151 |
| 152 checkFirstClass(SourceSpan span) { | 152 checkFirstClass(SourceSpan span) { |
| 153 if (isType) { | 153 if (isType) { |
| 154 world.error('Types are not first class', span); | 154 world.error('Types are not first class', span); |
| 155 } | 155 } |
| 156 } | 156 } |
| 157 | 157 |
| 158 /** Generate a call to an unknown function type. */ | 158 /** Generate a call to an unknown function type. */ |
| 159 Value _varCall(MethodGenerator context, Arguments args) { | 159 Value _varCall(MethodGenerator context, Arguments args) { |
| 160 // TODO(jmesserly): calls to unknown functions will bypass type checks, | |
| 161 // which normally happen on the caller side, or in the generated stub for | |
| 162 // dynamic method calls. What should we do? | |
|
jimhug
2011/11/07 16:41:26
I believe that in checked mode we will have to gen
| |
| 160 var stub = world.functionType.getCallStub(args); | 163 var stub = world.functionType.getCallStub(args); |
| 161 return new Value(null, '$code.${stub.name}(${args.getCode()})'); | 164 return new Value(null, '$code.${stub.name}(${args.getCode()})'); |
| 162 } | 165 } |
| 163 | 166 |
| 167 /** True if convertTo would generate a conversion. */ | |
| 168 // TODO(jmesserly): I don't like how this is coupled to convertTo. | |
| 169 bool needsConversion(Type toType) { | |
| 170 var callMethod = toType.getCallMethod(); | |
| 171 if (callMethod != null) { | |
| 172 int arity = callMethod.parameters.length; | |
| 173 var myCall = type.getCallMethod(); | |
| 174 if (myCall == null || myCall.parameters.length != arity) { | |
| 175 return true; | |
| 176 } | |
| 177 } | |
| 178 if (options.enableTypeChecks) { | |
| 179 Type fromType = type; | |
| 180 if (type.isVar && code != 'null') { | |
| 181 fromType = world.objectType; | |
| 182 } | |
| 183 bool bothNum = type.isNum && toType.isNum; | |
| 184 return fromType.isSubtypeOf(toType) || bothNum; | |
| 185 } | |
| 186 return false; | |
| 187 } | |
| 188 | |
| 164 /** | 189 /** |
| 165 * Assign or convert this value to another type. | 190 * Assign or convert this value to another type. |
| 166 * This is used for converting between function types, and inserting type | 191 * This is used for converting between function types, and inserting type |
| 167 * checks when --enable_type_checks is enabled. | 192 * checks when --enable_type_checks is enabled. |
| 168 */ | 193 */ |
| 194 // WARNING: this needs to be kept in sync with needsConversion above. | |
| 169 Value convertTo(MethodGenerator context, Type toType, Node node, | 195 Value convertTo(MethodGenerator context, Type toType, Node node, |
| 170 [bool isDynamic=false]) { | 196 [bool isDynamic=false]) { |
| 171 | 197 |
| 172 // Check types if enabled, unless this is a dynamic operation | 198 // Issue type warnings unless we are processing a dynamic operation. |
| 173 bool checked = options.enableTypeChecks && !isDynamic; | 199 bool checked = !isDynamic; |
| 174 | 200 |
| 175 var callMethod = toType.getCallMethod(); | 201 var callMethod = toType.getCallMethod(); |
| 176 if (callMethod != null) { | 202 if (callMethod != null) { |
| 177 if (checked && !toType.isAssignable(type)) { | 203 if (checked && !toType.isAssignable(type)) { |
| 178 convertWarning(toType, node); | 204 convertWarning(toType, node); |
| 179 } | 205 } |
| 180 | 206 |
| 181 int arity = callMethod.parameters.length; | 207 int arity = callMethod.parameters.length; |
| 182 var myCall = type.getCallMethod(); | 208 var myCall = type.getCallMethod(); |
| 183 if (myCall == null || myCall.parameters.length != arity) { | 209 if (myCall == null || myCall.parameters.length != arity) { |
| 184 final stub = world.functionType.getCallStub(new Arguments.bare(arity)); | 210 final stub = world.functionType.getCallStub(new Arguments.bare(arity)); |
| 185 return new Value(toType, 'to\$${stub.name}($code)'); | 211 return new Value(toType, 'to\$${stub.name}($code)'); |
| 186 } | 212 } |
| 187 } | 213 } |
| 188 | 214 |
| 189 // Don't add runtime asserts unless we have type checks turned on. | 215 // Don't add runtime asserts unless we have type checks turned on. |
| 190 if (!options.enableTypeChecks) { | 216 if (!options.enableTypeChecks) { |
| 191 return this; | 217 return this; |
| 192 } | 218 } |
| 193 | 219 |
| 194 if (type.isSubtypeOf(toType)) { | 220 // If we're assigning from a var, pretend it's Object for the purpose of |
| 195 return this; // widening conversion | 221 // runtime checks. |
| 196 } else if (checked && !toType.isSubtypeOf(type)) { | 222 |
| 223 // TODO(jmesserly): I'm a little bothered by the fact that we can't call | |
| 224 // isSubtypeOf directly. If we tracked null literals as the bottom type, | |
| 225 // and then only allowed Dynamic to be bottom for generic type args, I think | |
| 226 // we'd get the right behavior from isSubtypeOf. | |
| 227 Type fromType = type; | |
| 228 if (type.isVar && code != 'null') { | |
| 229 fromType = world.objectType; | |
| 230 } | |
| 231 | |
| 232 // TODO(jmesserly): remove the special case for "num" when our num handling | |
| 233 // is better. | |
| 234 bool bothNum = type.isNum && toType.isNum; | |
| 235 if (!checked || fromType.isSubtypeOf(toType) || bothNum) { | |
| 236 // No checks needed for a widening conversion. | |
| 237 return this; | |
| 238 } | |
| 239 | |
| 240 if (!toType.isSubtypeOf(type)) { | |
| 197 // According to the static types, this conversion can't work. | 241 // According to the static types, this conversion can't work. |
| 198 convertWarning(toType, node); | 242 convertWarning(toType, node); |
| 199 } | 243 } |
| 200 | 244 |
| 245 // Generate a runtime check | |
| 201 return _typeAssert(context, toType, node); | 246 return _typeAssert(context, toType, node); |
| 202 } | 247 } |
| 203 | 248 |
| 249 // TODO(jmesserly): this generates an unnecessary check for the 90% | |
| 250 // case where the thing passed in was a non-overloaded == or != expression | |
| 251 // We'll want to eliminate these, probably by tracking non-null bools in the | |
| 252 // type system. | |
| 253 Value convertToNonNullBool(MethodGenerator context, Node node) { | |
| 254 if (!type.isAssignable(world.boolType)) { | |
| 255 convertWarning(world.boolType, node); | |
| 256 } | |
| 257 if (!options.enableTypeChecks) { | |
| 258 return this; | |
| 259 } else { | |
| 260 // TODO(jmesserly): this is hacky. | |
| 261 if (code.startsWith('\$notnull_bool')) { | |
| 262 return this; | |
| 263 } else { | |
| 264 return new Value(world.boolType, '\$notnull_bool($code)'); | |
| 265 } | |
| 266 } | |
| 267 } | |
| 268 | |
| 204 /** | 269 /** |
| 205 * Generates a run time type assertion for the given value. This works like | 270 * Generates a run time type assertion for the given value. This works like |
| 206 * [instanceOf], but it allows null since Dart types are nullable. | 271 * [instanceOf], but it allows null since Dart types are nullable. |
| 207 * Also it will throw a TypeError if it gets the wrong type. | 272 * Also it will throw a TypeError if it gets the wrong type. |
| 208 */ | 273 */ |
| 209 // TODO(jmesserly): this generated code is too verbose. | |
| 210 Value _typeAssert(MethodGenerator context, Type toType, Node node) { | 274 Value _typeAssert(MethodGenerator context, Type toType, Node node) { |
| 211 if (toType is ParameterType) { | 275 if (toType is ParameterType) { |
| 212 ParameterType p = toType; | 276 ParameterType p = toType; |
| 213 toType = p.extendsType; | 277 toType = p.extendsType; |
| 214 } | 278 } |
| 215 | 279 |
| 216 // TODO(jmesserly): I don't like the duplication with instanceOf | 280 if (toType.isObject || toType.isVar) { |
| 217 var temp = context.getTemp(this); | 281 world.internalError('We thought ${type.name} is not a subtype of ${toType. name}?'); |
| 218 String testCode; | 282 } |
| 283 | |
| 284 // TODO(jmesserly): better assert for integers? | |
| 285 if (toType.isNum) toType = world.numType; | |
| 286 | |
| 287 // Generate a check like these: | |
| 288 // obj && obj.is$TypeName() | |
| 289 // $assert_int(obj) | |
| 290 // | |
| 291 // We rely on the fact that calling an undefined method produces a JS | |
| 292 // TypeError. Alternatively we could define fallbacks on Object that throw. | |
| 293 String check; | |
| 219 if (toType.library.isCore && toType.typeofName != null) { | 294 if (toType.library.isCore && toType.typeofName != null) { |
| 220 testCode = "typeof(${temp.code}) == '${toType.typeofName}'"; | 295 check = '\$assert_${toType.name}($code)'; |
| 221 } else if (toType.isClass && toType is !ConcreteType) { | 296 |
| 222 toType.markUsed(); | 297 if (toType.typeCheckCode == null) { |
| 223 testCode = '${temp.code} instanceof ${toType.jsname}'; | 298 toType.typeCheckCode = ''' |
| 299 function \$assert_${toType.name}(x) { | |
| 300 if (x == null || typeof(x) == "${toType.typeofName}") return x; | |
| 301 throw new TypeError("'" + x + "' is not a ${toType.name}."); | |
| 302 }'''; | |
| 303 } | |
| 224 } else { | 304 } else { |
| 225 toType.isTested = true; | 305 toType.isTested = true; |
| 226 testCode = '${temp.code}.is\$${toType.jsname}'; | 306 |
| 307 // If we track nullability, we could simplify this check. | |
| 308 var temp = context.getTemp(this); | |
| 309 check = '(${context.assignTemp(temp, this).code} &&'; | |
| 310 check += ' ${temp.code}.is\$${toType.jsname}())'; | |
| 311 if (this != temp) context.freeTemp(temp); | |
| 227 } | 312 } |
| 228 testCode = '(${context.assignTemp(temp, this).code} == null || $testCode)'; | |
| 229 var test = new Value(world.boolType, testCode); | |
| 230 | 313 |
| 231 var err = world.corelib.types['TypeError']; | 314 return new Value(toType, check); |
| 232 world.gen.genMethod(err.members['toString']); | |
| 233 var args = new Arguments(null, [temp, | |
| 234 new Value(world.stringType, '"${toType.name}"')]); | |
| 235 var typeErr = err.getConstructor('').invoke(context, node, null, args); | |
| 236 | |
| 237 var result = new Value(toType, '(${test.code} ? ${temp.code} : ' | |
| 238 + '\$throw(${typeErr.code}))'); | |
| 239 if (temp != this) context.freeTemp(temp); | |
| 240 return result; | |
| 241 } | 315 } |
| 242 | 316 |
| 243 /** | 317 /** |
| 244 * Test to see if value is an instance of this type. | 318 * Test to see if value is an instance of this type. |
| 245 * | 319 * |
| 246 * - If a primitive type, then uses the JavaScript typeof. | 320 * - If a primitive type, then uses the JavaScript typeof. |
| 247 * - If it's a non-generic class, use instanceof. | 321 * - If it's a non-generic class, use instanceof. |
| 248 * - Otherwise add a fake member to test for. This value is generated | 322 * - Otherwise add a fake member to test for. This value is generated |
| 249 * as a function so that it can be called for a runtime failure. | 323 * as a function so that it can be called for a runtime failure. |
| 250 */ | 324 */ |
| (...skipping 266 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 517 return 1; | 591 return 1; |
| 518 } else if (name != null && other.name == null) { | 592 } else if (name != null && other.name == null) { |
| 519 return -1; | 593 return -1; |
| 520 } else if (name != null) { | 594 } else if (name != null) { |
| 521 return name.compareTo(other.name); | 595 return name.compareTo(other.name); |
| 522 } else { | 596 } else { |
| 523 return field.name.compareTo(other.field.name); | 597 return field.name.compareTo(other.field.name); |
| 524 } | 598 } |
| 525 } | 599 } |
| 526 } | 600 } |
| OLD | NEW |