Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(48)

Side by Side Diff: frog/value.dart

Issue 8457007: Better runtime type checks. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: merged, and fix typo in member name Created 9 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
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
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
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 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698