| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | |
| 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. | |
| 4 | |
| 5 /** A formal parameter to a [Method]. */ | |
| 6 class Parameter { | |
| 7 FormalNode definition; | |
| 8 Member method; | |
| 9 | |
| 10 String name; | |
| 11 Type type; | |
| 12 bool isInitializer = false; | |
| 13 | |
| 14 Value value; | |
| 15 | |
| 16 Parameter(this.definition, this.method); | |
| 17 | |
| 18 resolve() { | |
| 19 name = definition.name.name; | |
| 20 if (name.startsWith('this.')) { | |
| 21 name = name.substring(5); | |
| 22 isInitializer = true; | |
| 23 } | |
| 24 | |
| 25 type = method.resolveType(definition.type, false, true); | |
| 26 | |
| 27 if (definition.value != null) { | |
| 28 // To match VM, detect cases where value was not actually specified in | |
| 29 // code and don't signal errors. | |
| 30 // TODO(jimhug): Clean up after issue #352 is resolved. | |
| 31 if (!hasDefaultValue) return; | |
| 32 | |
| 33 if (method.name == ':call') { | |
| 34 // TODO(jimhug): Need simpler way to detect "true" function types vs. | |
| 35 // regular methods being used as function types for closures. | |
| 36 // TODO(sigmund): Disallow non-null default values for native calls? | |
| 37 var methodDef = method.definition; | |
| 38 if (methodDef.body == null && !method.isNative) { | |
| 39 world.error('default value not allowed on function type', | |
| 40 definition.span); | |
| 41 } | |
| 42 } else if (method.isAbstract) { | |
| 43 world.error('default value not allowed on abstract methods', | |
| 44 definition.span); | |
| 45 } | |
| 46 } else if (isInitializer && !method.isConstructor) { | |
| 47 world.error('initializer parameters only allowed on constructors', | |
| 48 definition.span); | |
| 49 } | |
| 50 } | |
| 51 | |
| 52 genValue(MethodMember method, CallingContext context) { | |
| 53 if (definition.value == null || value != null) return; | |
| 54 | |
| 55 // TODO(jmesserly): what should we do when context is not a MethodGenerator? | |
| 56 if (context is MethodGenerator) { | |
| 57 MethodGenerator gen = context; | |
| 58 value = definition.value.visit(gen); | |
| 59 if (!value.isConst) { | |
| 60 world.error('default parameter values must be constant', value.span); | |
| 61 } | |
| 62 value = value.convertTo(context, type); | |
| 63 } | |
| 64 } | |
| 65 | |
| 66 Parameter copyWithNewType(Member newMethod, Type newType) { | |
| 67 var ret = new Parameter(definition, newMethod); | |
| 68 ret.type = newType; | |
| 69 ret.name = name; | |
| 70 ret.isInitializer = isInitializer; | |
| 71 return ret; | |
| 72 } | |
| 73 | |
| 74 bool get isOptional() => definition != null && definition.value != null; | |
| 75 | |
| 76 /** | |
| 77 * Gets whether this named parameter has an explicit default value or relies | |
| 78 * on the implicit `null`. | |
| 79 */ | |
| 80 bool get hasDefaultValue() => | |
| 81 definition.value.span.start != definition.span.start; | |
| 82 } | |
| 83 | |
| 84 | |
| 85 class Member extends Element { | |
| 86 final Type declaringType; | |
| 87 | |
| 88 Member genericMember; | |
| 89 | |
| 90 // A root string for getter and setter names. This is used e.g. to ensure | |
| 91 // that fields with the same Dart name but different jsnames (due to native | |
| 92 // name directives) still have a common getter name. Is null when there is no | |
| 93 // renaming. | |
| 94 String _jsnameRoot; | |
| 95 | |
| 96 Member(String name, Type declaringType) | |
| 97 : this.declaringType = declaringType, | |
| 98 super(name, declaringType); | |
| 99 | |
| 100 String mangleJsName() { | |
| 101 var mangled = super.mangleJsName(); | |
| 102 if (declaringType != null && declaringType.isTop) { | |
| 103 return JsNames.getValid(mangled); | |
| 104 } else { | |
| 105 // We don't need to mangle native member names unless | |
| 106 // they contain illegal characters. | |
| 107 return (isNative && !name.contains(':')) ? name : mangled; | |
| 108 } | |
| 109 } | |
| 110 | |
| 111 abstract bool get isStatic(); | |
| 112 abstract Type get returnType(); | |
| 113 | |
| 114 abstract bool get canGet(); | |
| 115 abstract bool get canSet(); | |
| 116 | |
| 117 Library get library() => declaringType.library; | |
| 118 | |
| 119 bool get isPrivate() => name !== null && name.startsWith('_'); | |
| 120 | |
| 121 bool get isConstructor() => false; | |
| 122 bool get isField() => false; | |
| 123 bool get isMethod() => false; | |
| 124 bool get isProperty() => false; | |
| 125 bool get isAbstract() => false; | |
| 126 | |
| 127 bool get isFinal() => false; | |
| 128 | |
| 129 // TODO(jmesserly): these only makes sense on methods, but because of | |
| 130 // ConcreteMember we need to support them on Member. | |
| 131 bool get isConst() => false; | |
| 132 bool get isFactory() => false; | |
| 133 | |
| 134 bool get isOperator() => name.startsWith(':'); | |
| 135 bool get isCallMethod() => name == ':call'; | |
| 136 | |
| 137 bool get requiresPropertySyntax() => false; | |
| 138 bool _provideGetter = false; | |
| 139 bool _provideSetter = false; | |
| 140 | |
| 141 bool get isNative() => false; | |
| 142 String get constructorName() { | |
| 143 world.internalError('cannot be a constructor', span); | |
| 144 } | |
| 145 | |
| 146 void provideGetter() {} | |
| 147 void provideSetter() {} | |
| 148 | |
| 149 String get jsnameOfGetter() => 'get\$$jsnameRoot'; | |
| 150 String get jsnameOfSetter() => 'set\$$jsnameRoot'; | |
| 151 String get jsnameRoot() => _jsnameRoot != null ? _jsnameRoot : _jsname; | |
| 152 | |
| 153 Member get initDelegate() { | |
| 154 world.internalError('cannot have initializers', span); | |
| 155 } | |
| 156 void set initDelegate(ctor) { | |
| 157 world.internalError('cannot have initializers', span); | |
| 158 } | |
| 159 | |
| 160 Value computeValue() { | |
| 161 world.internalError('cannot have value', span); | |
| 162 } | |
| 163 | |
| 164 /** | |
| 165 * The inferred returnType. Right now this is just used to track | |
| 166 * non-nullable bools. | |
| 167 */ | |
| 168 Type get inferredResult() { | |
| 169 var t = returnType; | |
| 170 if (t.isBool && (library.isCore || library.isCoreImpl)) { | |
| 171 // We trust our core libraries not to return null from bools. | |
| 172 // I hope this trust is well placed! | |
| 173 return world.nonNullBool; | |
| 174 } | |
| 175 return t; | |
| 176 } | |
| 177 | |
| 178 Definition get definition() => null; | |
| 179 | |
| 180 List<Parameter> get parameters() => []; | |
| 181 | |
| 182 MemberSet _preciseMemberSet, _potentialMemberSet; | |
| 183 | |
| 184 MemberSet get preciseMemberSet() { | |
| 185 if (_preciseMemberSet === null) { | |
| 186 _preciseMemberSet = new MemberSet(this); | |
| 187 } | |
| 188 return _preciseMemberSet; | |
| 189 } | |
| 190 | |
| 191 MemberSet get potentialMemberSet() { | |
| 192 // TODO(jimhug): This needs one more redesign - move to TypeSets... | |
| 193 | |
| 194 if (_potentialMemberSet === null) { | |
| 195 if (name == ':call') { | |
| 196 _potentialMemberSet = preciseMemberSet; | |
| 197 return _potentialMemberSet; | |
| 198 } | |
| 199 | |
| 200 final mems = new Set<Member>(); | |
| 201 if (declaringType.isClass) mems.add(this); | |
| 202 | |
| 203 for (var subtype in declaringType.genericType.subtypes) { | |
| 204 if (!subtype.isClass) continue; | |
| 205 var mem = subtype.members[name]; | |
| 206 if (mem !== null) { | |
| 207 if (mem.isDefinedOn(declaringType)) { | |
| 208 mems.add(mem); | |
| 209 } | |
| 210 } else if (!declaringType.isClass) { | |
| 211 // Handles weird interface case. | |
| 212 mem = subtype.getMember(name); | |
| 213 if (mem !== null && mem.isDefinedOn(declaringType)) { | |
| 214 mems.add(mem); | |
| 215 } | |
| 216 } | |
| 217 } | |
| 218 | |
| 219 if (mems.length != 0) { | |
| 220 // TODO(jimhug): This hack needs to be rationalized. | |
| 221 for (var mem in mems) { | |
| 222 if (declaringType.genericType != declaringType && | |
| 223 mem.genericMember != null && mems.contains(mem.genericMember)) { | |
| 224 //world.info('skip ${name} on ${mem.genericMember.declaringType.name
}' + | |
| 225 // ' because we have on ${mem.declaringType.name} for ${declaringTy
pe.name}'); | |
| 226 mems.remove(mem.genericMember); | |
| 227 } | |
| 228 } | |
| 229 | |
| 230 | |
| 231 for (var mem in mems) { | |
| 232 if (_potentialMemberSet === null) { | |
| 233 _potentialMemberSet = new MemberSet(mem); | |
| 234 } else { | |
| 235 _potentialMemberSet.add(mem); | |
| 236 } | |
| 237 } | |
| 238 } | |
| 239 } | |
| 240 return _potentialMemberSet; | |
| 241 } | |
| 242 | |
| 243 // If I have an object of [type] could I be invoking this member? | |
| 244 bool isDefinedOn(Type type) { | |
| 245 if (type.isClass) { | |
| 246 if (declaringType.isSubtypeOf(type)) { | |
| 247 return true; | |
| 248 } else if (type.isSubtypeOf(declaringType)) { | |
| 249 // maybe - but not if overridden somewhere | |
| 250 // TODO(jimhug): This lookup is not great for perf of this method. | |
| 251 return type.getMember(name) == this; | |
| 252 } else { | |
| 253 return false; | |
| 254 } | |
| 255 } else { | |
| 256 if (declaringType.isSubtypeOf(type)) { | |
| 257 return true; | |
| 258 } else { | |
| 259 // If this is an interface, the actual implementation may | |
| 260 // come from a class that does not implement this interface. | |
| 261 for (var t in declaringType.subtypes) { | |
| 262 if (t.isSubtypeOf(type) && t.getMember(name) == this) { | |
| 263 return true; | |
| 264 } | |
| 265 } | |
| 266 return false; | |
| 267 } | |
| 268 } | |
| 269 } | |
| 270 | |
| 271 abstract Value _get(CallingContext context, Node node, Value target); | |
| 272 | |
| 273 abstract Value _set(CallingContext context, Node node, Value target, | |
| 274 Value value); | |
| 275 | |
| 276 | |
| 277 bool canInvoke(CallingContext context, Arguments args) { | |
| 278 // Any gettable member whose return type is callable can be "invoked". | |
| 279 if (canGet && (isField || isProperty)) { | |
| 280 return this.returnType.isFunction || this.returnType.isVar || | |
| 281 this.returnType.getCallMethod() != null; | |
| 282 } | |
| 283 return false; | |
| 284 } | |
| 285 | |
| 286 Value invoke(CallingContext context, Node node, Value target, | |
| 287 Arguments args) { | |
| 288 var newTarget = _get(context, node, target); | |
| 289 return newTarget.invoke(context, ':call', node, args); | |
| 290 } | |
| 291 | |
| 292 bool override(Member other) { | |
| 293 if (isStatic) { | |
| 294 world.error('static members cannot hide parent members', | |
| 295 span, other.span); | |
| 296 return false; | |
| 297 } else if (other.isStatic) { | |
| 298 world.error('cannot override static member', span, other.span); | |
| 299 return false; | |
| 300 } | |
| 301 return true; | |
| 302 } | |
| 303 | |
| 304 String get generatedFactoryName() { | |
| 305 assert(this.isFactory); | |
| 306 String prefix = '${declaringType.genericType.jsname}.${constructorName}\$'; | |
| 307 if (name == '') { | |
| 308 return '${prefix}factory'; | |
| 309 } else { | |
| 310 return '${prefix}$name\$factory'; | |
| 311 } | |
| 312 } | |
| 313 | |
| 314 int hashCode() { | |
| 315 final typeCode = declaringType == null ? 1 : declaringType.hashCode(); | |
| 316 final nameCode = isConstructor ? constructorName.hashCode() : | |
| 317 name.hashCode(); | |
| 318 return (typeCode << 4) ^ nameCode; | |
| 319 } | |
| 320 | |
| 321 bool operator ==(other) { | |
| 322 return other is Member && isConstructor == other.isConstructor && | |
| 323 declaringType == other.declaringType && (isConstructor ? | |
| 324 constructorName == other.constructorName : name == other.name); | |
| 325 } | |
| 326 | |
| 327 /** Overriden to ensure that type arguments aren't used in static mems. */ | |
| 328 Type resolveType(TypeReference node, bool typeErrors, bool allowTypeParams) { | |
| 329 allowTypeParams = allowTypeParams && !(isStatic && !isFactory); | |
| 330 | |
| 331 return super.resolveType(node, typeErrors, allowTypeParams); | |
| 332 } | |
| 333 | |
| 334 // TODO(jimhug): Make this abstract. | |
| 335 Member makeConcrete(Type concreteType) { | |
| 336 world.internalError('cannot make this concrete', span); | |
| 337 } | |
| 338 } | |
| 339 | |
| 340 | |
| 341 /** | |
| 342 * Types are treated as first class members of their library's top type. | |
| 343 */ | |
| 344 // TODO(jmesserly): perhaps Type should extend Member, but that can get | |
| 345 // complicated. | |
| 346 class TypeMember extends Member { | |
| 347 final DefinedType type; | |
| 348 | |
| 349 TypeMember(DefinedType type) | |
| 350 : super(type.name, type.library.topType), | |
| 351 this.type = type; | |
| 352 | |
| 353 SourceSpan get span() => type.definition === null ? null : type.definition.spa
n; | |
| 354 | |
| 355 bool get isStatic() => true; | |
| 356 | |
| 357 // If this really becomes first class, this should return typeof(Type) | |
| 358 Type get returnType() => world.varType; | |
| 359 | |
| 360 bool canInvoke(CallingContext context, Arguments args) => false; | |
| 361 bool get canGet() => true; | |
| 362 bool get canSet() => false; | |
| 363 | |
| 364 Value _get(CallingContext context, Node node, Value target) { | |
| 365 return new TypeValue(type, node.span); | |
| 366 } | |
| 367 | |
| 368 Value _set(CallingContext context, Node node, Value target, Value value) { | |
| 369 world.error('cannot set type', node.span); | |
| 370 } | |
| 371 | |
| 372 Value invoke(CallingContext context, Node node, Value target, | |
| 373 Arguments args) { | |
| 374 world.error('cannot invoke type', node.span); | |
| 375 } | |
| 376 } | |
| 377 | |
| 378 /** Represents a Dart field from source code. */ | |
| 379 class FieldMember extends Member { | |
| 380 final VariableDefinition definition; | |
| 381 final Expression value; | |
| 382 | |
| 383 Type type; | |
| 384 Value _computedValue; | |
| 385 | |
| 386 bool isStatic; | |
| 387 bool isFinal; | |
| 388 final bool isNative; | |
| 389 | |
| 390 // TODO(jimhug): Better notion of fields that need special handling... | |
| 391 bool get overridesProperty() { | |
| 392 if (isStatic) return false; | |
| 393 | |
| 394 if (declaringType.parent != null) { | |
| 395 var p = declaringType.parent.getProperty(name); | |
| 396 if (p != null && p.isProperty) return true; | |
| 397 if (p is FieldMember && p != this) return p.overridesProperty; | |
| 398 } | |
| 399 return false; | |
| 400 } | |
| 401 | |
| 402 bool override(Member other) { | |
| 403 if (!super.override(other)) return false; | |
| 404 | |
| 405 // According to the specification, fields can override properties | |
| 406 // and other fields. | |
| 407 if (other.isProperty || other.isField) { | |
| 408 // TODO(jimhug): | |
| 409 // other.returnType.ensureAssignableFrom(returnType, null, true); | |
| 410 return true; | |
| 411 // TODO(jimhug): Merge in overridesProperty logic here. | |
| 412 } else { | |
| 413 world.error('field can only override field or property', | |
| 414 span, other.span); | |
| 415 return false; | |
| 416 } | |
| 417 } | |
| 418 | |
| 419 void provideGetter() { | |
| 420 _provideGetter = true; | |
| 421 if (genericMember !== null) { | |
| 422 genericMember.provideGetter(); | |
| 423 } | |
| 424 } | |
| 425 | |
| 426 void provideSetter() { | |
| 427 _provideSetter = true; | |
| 428 if (genericMember !== null) { | |
| 429 genericMember.provideSetter(); | |
| 430 } | |
| 431 } | |
| 432 | |
| 433 FieldMember(String name, Type declaringType, this.definition, this.value, | |
| 434 [bool this.isNative = false]) | |
| 435 : super(name, declaringType); | |
| 436 | |
| 437 Member makeConcrete(Type concreteType) { | |
| 438 var ret = new FieldMember(name, concreteType, definition, value); | |
| 439 ret.genericMember = this; | |
| 440 ret._jsname = _jsname; | |
| 441 return ret; | |
| 442 } | |
| 443 | |
| 444 SourceSpan get span() => definition == null ? null : definition.span; | |
| 445 | |
| 446 Type get returnType() => type; | |
| 447 | |
| 448 bool get canGet() => true; | |
| 449 bool get canSet() => !isFinal; | |
| 450 | |
| 451 bool get isField() => true; | |
| 452 | |
| 453 resolve() { | |
| 454 isStatic = declaringType.isTop; | |
| 455 isFinal = false; | |
| 456 if (definition.modifiers != null) { | |
| 457 for (var mod in definition.modifiers) { | |
| 458 if (mod.kind == TokenKind.STATIC) { | |
| 459 if (isStatic) { | |
| 460 world.error('duplicate static modifier', mod.span); | |
| 461 } | |
| 462 isStatic = true; | |
| 463 } else if (mod.kind == TokenKind.FINAL) { | |
| 464 if (isFinal) { | |
| 465 world.error('duplicate final modifier', mod.span); | |
| 466 } | |
| 467 isFinal = true; | |
| 468 } else { | |
| 469 world.error('${mod} modifier not allowed on field', mod.span); | |
| 470 } | |
| 471 } | |
| 472 } | |
| 473 type = resolveType(definition.type, false, true); | |
| 474 | |
| 475 if (isStatic && isFinal && value == null) { | |
| 476 world.error('static final field is missing initializer', span); | |
| 477 } | |
| 478 | |
| 479 if (declaringType.isClass) library._addMember(this); | |
| 480 } | |
| 481 | |
| 482 | |
| 483 bool _computing = false; | |
| 484 /** Generates the initial value for this field, if any. Marks it as used. */ | |
| 485 Value computeValue() { | |
| 486 if (value == null) return null; | |
| 487 | |
| 488 if (_computedValue == null) { | |
| 489 if (_computing) { | |
| 490 world.error('circular reference', value.span); | |
| 491 return null; | |
| 492 } | |
| 493 _computing = true; | |
| 494 var finalMethod = new MethodMember('final_context', declaringType, null); | |
| 495 finalMethod.isStatic = true; | |
| 496 var finalGen = new MethodGenerator(finalMethod, null); | |
| 497 _computedValue = value.visit(finalGen); | |
| 498 if (!_computedValue.isConst) { | |
| 499 if (isStatic) { | |
| 500 world.error( | |
| 501 'non constant static field must be initialized in functions', | |
| 502 value.span); | |
| 503 } else { | |
| 504 world.error( | |
| 505 'non constant field must be initialized in constructor', | |
| 506 value.span); | |
| 507 } | |
| 508 } | |
| 509 | |
| 510 | |
| 511 if (isStatic) { | |
| 512 if (isFinal && _computedValue.isConst) { | |
| 513 ; // keep const as is here | |
| 514 } else { | |
| 515 _computedValue = world.gen.globalForStaticField( | |
| 516 this, _computedValue, [_computedValue]); | |
| 517 } | |
| 518 } | |
| 519 _computing = false; | |
| 520 } | |
| 521 return _computedValue; | |
| 522 } | |
| 523 | |
| 524 Value _get(CallingContext context, Node node, Value target) { | |
| 525 if (!context.needsCode) { | |
| 526 return new PureStaticValue(type, node.span, isStatic && isFinal); | |
| 527 } | |
| 528 | |
| 529 if (isNative && returnType != null) { | |
| 530 returnType.markUsed(); | |
| 531 if (returnType is DefinedType) { | |
| 532 // TODO(jmesserly): this handles native fields that return types like | |
| 533 // "List". Is there a better solution for fields? Unlike methods we have | |
| 534 // no good way to annotate them. | |
| 535 var defaultType = returnType.genericType.defaultType; | |
| 536 if (defaultType != null && defaultType.isNative) { | |
| 537 defaultType.markUsed(); | |
| 538 } | |
| 539 } | |
| 540 } | |
| 541 | |
| 542 if (isStatic) { | |
| 543 // TODO(jmesserly): can we avoid generating the whole type? | |
| 544 declaringType.markUsed(); | |
| 545 | |
| 546 // Make sure to compute the value of all static fields, even if we don't | |
| 547 // use this value immediately. | |
| 548 var cv = computeValue(); | |
| 549 if (isFinal) { | |
| 550 return cv; | |
| 551 } | |
| 552 world.gen.hasStatics = true; | |
| 553 if (declaringType.isTop) { | |
| 554 return new Value(type, '\$globals.$jsname', node.span); | |
| 555 } else if (declaringType.isNative) { | |
| 556 if (declaringType.isHiddenNativeType) { | |
| 557 // TODO: Could warn at parse time. | |
| 558 world.error('static field of hidden native type is inaccessible', | |
| 559 node.span); | |
| 560 } | |
| 561 return new Value(type, '${declaringType.jsname}.$jsname', node.span); | |
| 562 } else { | |
| 563 return new Value(type, | |
| 564 '\$globals.${declaringType.jsname}_$jsname', node.span); | |
| 565 } | |
| 566 } | |
| 567 return new Value(type, '${target.code}.$jsname', node.span); | |
| 568 } | |
| 569 | |
| 570 Value _set(CallingContext context, Node node, Value target, Value value) { | |
| 571 if (!context.needsCode) { | |
| 572 // TODO(jimhug): Add type checks here. | |
| 573 return new PureStaticValue(type, node.span); | |
| 574 } | |
| 575 | |
| 576 var lhs = _get(context, node, target); | |
| 577 value = value.convertTo(context, type); | |
| 578 return new Value(type, '${lhs.code} = ${value.code}', node.span); | |
| 579 } | |
| 580 } | |
| 581 | |
| 582 class PropertyMember extends Member { | |
| 583 MethodMember getter; | |
| 584 MethodMember setter; | |
| 585 | |
| 586 Member _overriddenField; | |
| 587 | |
| 588 // TODO(jimhug): What is the right span for this beast? | |
| 589 SourceSpan get span() => getter != null ? getter.span : null; | |
| 590 | |
| 591 bool get canGet() => getter != null; | |
| 592 bool get canSet() => setter != null; | |
| 593 | |
| 594 // If the property is just a declaration in an interface, continue to allow | |
| 595 // field syntax in the generated code. | |
| 596 bool get requiresPropertySyntax() => declaringType.isClass; | |
| 597 | |
| 598 // When overriding native fields, we still provide a field syntax to ensure | |
| 599 // that native functions will find the appropriate property implementation. | |
| 600 // TODO(sigmund): should check for this transitively... | |
| 601 bool get needsFieldSyntax() => | |
| 602 _overriddenField != null && | |
| 603 _overriddenField.isNative && | |
| 604 // Can't put property on hidden native class... | |
| 605 !_overriddenField.declaringType.isHiddenNativeType | |
| 606 ; | |
| 607 | |
| 608 // TODO(jimhug): Union of getter and setters sucks! | |
| 609 bool get isStatic() => getter == null ? setter.isStatic : getter.isStatic; | |
| 610 | |
| 611 bool get isProperty() => true; | |
| 612 | |
| 613 Type get returnType() { | |
| 614 return getter == null ? setter.returnType : getter.returnType; | |
| 615 } | |
| 616 | |
| 617 PropertyMember(String name, Type declaringType): super(name, declaringType); | |
| 618 | |
| 619 Member makeConcrete(Type concreteType) { | |
| 620 var ret = new PropertyMember(name, concreteType); | |
| 621 if (getter !== null) ret.getter = getter.makeConcrete(concreteType); | |
| 622 if (setter !== null) ret.setter = setter.makeConcrete(concreteType); | |
| 623 ret._jsname = _jsname; | |
| 624 return ret; | |
| 625 } | |
| 626 | |
| 627 bool override(Member other) { | |
| 628 if (!super.override(other)) return false; | |
| 629 | |
| 630 // properties can override other properties and fields | |
| 631 if (other.isProperty || other.isField) { | |
| 632 // TODO(jimhug): | |
| 633 // other.returnType.ensureAssignableFrom(returnType, null, true); | |
| 634 if (other.isProperty) addFromParent(other); | |
| 635 else _overriddenField = other; | |
| 636 return true; | |
| 637 } else { | |
| 638 world.error('property can only override field or property', | |
| 639 span, other.span); | |
| 640 return false; | |
| 641 } | |
| 642 } | |
| 643 | |
| 644 Value _get(CallingContext context, Node node, Value target) { | |
| 645 if (getter == null) { | |
| 646 if (_overriddenField != null) { | |
| 647 return _overriddenField._get(context, node, target); | |
| 648 } | |
| 649 return target.invokeNoSuchMethod(context, 'get:$name', node); | |
| 650 } | |
| 651 return getter.invoke(context, node, target, Arguments.EMPTY); | |
| 652 } | |
| 653 | |
| 654 Value _set(CallingContext context, Node node, Value target, Value value) { | |
| 655 if (setter == null) { | |
| 656 if (_overriddenField != null) { | |
| 657 return _overriddenField._set(context, node, target, value); | |
| 658 } | |
| 659 return target.invokeNoSuchMethod(context, 'set:$name', node, | |
| 660 new Arguments(null, [value])); | |
| 661 } | |
| 662 return setter.invoke(context, node, target, new Arguments(null, [value])); | |
| 663 } | |
| 664 | |
| 665 addFromParent(Member parentMember) { | |
| 666 final parent = parentMember; | |
| 667 | |
| 668 if (getter == null) getter = parent.getter; | |
| 669 if (setter == null) setter = parent.setter; | |
| 670 } | |
| 671 | |
| 672 resolve() { | |
| 673 if (getter != null) { | |
| 674 getter.resolve(); | |
| 675 if (getter.parameters.length != 0) { | |
| 676 world.error('getter methods should take no arguments', | |
| 677 getter.definition.span); | |
| 678 } | |
| 679 if (getter.returnType.isVoid) { | |
| 680 world.warning('getter methods should not be void', | |
| 681 getter.definition.returnType.span); | |
| 682 } | |
| 683 } | |
| 684 if (setter != null) { | |
| 685 setter.resolve(); | |
| 686 if (setter.parameters.length != 1) { | |
| 687 world.error('setter methods should take a single argument', | |
| 688 setter.definition.span); | |
| 689 } | |
| 690 // Not issue warning if setter is implicitly dynamic (returnType == null), | |
| 691 // but do if it is explicit (returnType.isVar) | |
| 692 if (!setter.returnType.isVoid && setter.definition.returnType != null) { | |
| 693 world.warning('setter methods should be void', | |
| 694 setter.definition.returnType.span); | |
| 695 } | |
| 696 } | |
| 697 | |
| 698 if (declaringType.isClass) library._addMember(this); | |
| 699 } | |
| 700 } | |
| 701 | |
| 702 | |
| 703 /** Represents a Dart method or top-level function. */ | |
| 704 class MethodMember extends Member { | |
| 705 FunctionDefinition definition; | |
| 706 Type returnType; | |
| 707 List<Parameter> parameters; | |
| 708 | |
| 709 MethodData _methodData; | |
| 710 | |
| 711 Type _functionType; | |
| 712 bool isStatic = false; | |
| 713 bool isAbstract = false; | |
| 714 | |
| 715 // Note: these two modifiers are only legal on constructors | |
| 716 bool isConst = false; | |
| 717 bool isFactory = false; | |
| 718 | |
| 719 /** True if this is a function defined inside another method. */ | |
| 720 final bool isLambda; | |
| 721 | |
| 722 /** | |
| 723 * True if we should provide info on optional parameters for use by runtime | |
| 724 * dispatch. | |
| 725 */ | |
| 726 bool _provideOptionalParamInfo = false; | |
| 727 | |
| 728 /* | |
| 729 * When this is a constructor, contains any other constructor called during | |
| 730 * initialization (if any). | |
| 731 */ | |
| 732 Member initDelegate; | |
| 733 | |
| 734 bool _hasNativeBody = false; | |
| 735 | |
| 736 static final kIdentifierRegExp = const RegExp(@'^[a-zA-Z][a-zA-Z_$0-9]*$'); | |
| 737 | |
| 738 MethodMember(String name, Type declaringType, this.definition) | |
| 739 : isLambda = false, super(name, declaringType) { | |
| 740 if (isNative) { | |
| 741 // Parse the native string. The the native string can be a native name | |
| 742 // (identifier) or a chunk of JavaScript code. | |
| 743 // | |
| 744 // foo() native 'bar'; // The native method is called 'bar'. | |
| 745 // foo() native 'return 1'; // Defines method with native implementation. | |
| 746 // | |
| 747 if (kIdentifierRegExp.hasMatch(definition.nativeBody)) { | |
| 748 _jsname = definition.nativeBody; | |
| 749 // Prevent the compiler from using the name for a regular Dart member. | |
| 750 world._addHazardousMemberName(_jsname); | |
| 751 } | |
| 752 _hasNativeBody = definition.nativeBody != '' && | |
| 753 definition.nativeBody != _jsname; | |
| 754 } | |
| 755 } | |
| 756 | |
| 757 MethodMember.lambda(String name, Type declaringType, this.definition) | |
| 758 : isLambda = true, super(name, declaringType); | |
| 759 | |
| 760 Member makeConcrete(Type concreteType) { | |
| 761 var _name = isConstructor ? concreteType.name : name; | |
| 762 var ret = new MethodMember(_name, concreteType, definition); | |
| 763 ret.genericMember = this; | |
| 764 ret._jsname = _jsname; | |
| 765 return ret; | |
| 766 } | |
| 767 | |
| 768 MethodData get methodData() { | |
| 769 if (genericMember !== null) return genericMember.dynamic.methodData; | |
| 770 | |
| 771 if (_methodData === null) { | |
| 772 _methodData = new MethodData(this); | |
| 773 } | |
| 774 return _methodData; | |
| 775 } | |
| 776 | |
| 777 bool get isConstructor() => name == declaringType.name; | |
| 778 bool get isMethod() => !isConstructor; | |
| 779 | |
| 780 bool get isNative() { | |
| 781 if (definition == null) return false; | |
| 782 return definition.nativeBody != null; | |
| 783 } | |
| 784 | |
| 785 bool get hasNativeBody() => _hasNativeBody; | |
| 786 | |
| 787 bool get canGet() => true; | |
| 788 bool get canSet() => false; | |
| 789 | |
| 790 bool get requiresPropertySyntax() => true; | |
| 791 | |
| 792 SourceSpan get span() => definition == null ? null : definition.span; | |
| 793 | |
| 794 String get constructorName() { | |
| 795 var returnType = definition.returnType; | |
| 796 if (returnType == null) return ''; | |
| 797 if (returnType is GenericTypeReference) { | |
| 798 return ''; | |
| 799 } | |
| 800 | |
| 801 // TODO(jmesserly): make this easier? | |
| 802 if (returnType.names != null) { | |
| 803 return returnType.names[0].name; | |
| 804 } else if (returnType.name != null) { | |
| 805 return returnType.name.name; | |
| 806 } | |
| 807 world.internalError('no valid constructor name', definition.span); | |
| 808 } | |
| 809 | |
| 810 Type get functionType() { | |
| 811 if (_functionType == null) { | |
| 812 _functionType = library.getOrAddFunctionType(declaringType, name, | |
| 813 definition, methodData); | |
| 814 // TODO(jimhug): Better resolution checks. | |
| 815 if (parameters == null) { | |
| 816 resolve(); | |
| 817 } | |
| 818 } | |
| 819 return _functionType; | |
| 820 } | |
| 821 | |
| 822 bool override(Member other) { | |
| 823 if (!super.override(other)) return false; | |
| 824 | |
| 825 // methods can only override other methods | |
| 826 if (other.isMethod) { | |
| 827 // TODO(jimhug): | |
| 828 // other.returnType.ensureAssignableFrom(returnType, null, true); | |
| 829 // TODO(jimhug): Check for further parameter compatibility. | |
| 830 return true; | |
| 831 } else { | |
| 832 world.error('method can only override methods', span, other.span); | |
| 833 return false; | |
| 834 } | |
| 835 } | |
| 836 | |
| 837 bool canInvoke(CallingContext context, Arguments args) { | |
| 838 int bareCount = args.bareCount; | |
| 839 | |
| 840 if (bareCount > parameters.length) return false; | |
| 841 | |
| 842 if (bareCount == parameters.length) { | |
| 843 if (bareCount != args.length) return false; | |
| 844 } else { | |
| 845 if (!parameters[bareCount].isOptional) return false; | |
| 846 | |
| 847 for (int i = bareCount; i < args.length; i++) { | |
| 848 if (indexOfParameter(args.getName(i)) < 0) { | |
| 849 return false; | |
| 850 } | |
| 851 } | |
| 852 } | |
| 853 | |
| 854 return true; | |
| 855 } | |
| 856 | |
| 857 // TODO(jmesserly): might need to make this faster | |
| 858 /** Gets the index of an optional parameter. */ | |
| 859 int indexOfParameter(String name) { | |
| 860 for (int i = 0; i < parameters.length; i++) { | |
| 861 final p = parameters[i]; | |
| 862 if (p.isOptional && p.name == name) { | |
| 863 return i; | |
| 864 } | |
| 865 } | |
| 866 return -1; | |
| 867 } | |
| 868 | |
| 869 void provideGetter() { _provideGetter = true; } | |
| 870 void provideSetter() { _provideSetter = true; } | |
| 871 | |
| 872 Value _set(CallingContext context, Node node, Value target, Value value) { | |
| 873 world.error('cannot set method', node.span); | |
| 874 } | |
| 875 | |
| 876 Value _get(CallingContext context, Node node, Value target) { | |
| 877 if (!context.needsCode) { | |
| 878 return new PureStaticValue(functionType, node.span); | |
| 879 } | |
| 880 | |
| 881 // TODO(jimhug): Would prefer to invoke! | |
| 882 declaringType.genMethod(this); | |
| 883 _provideOptionalParamInfo = true; | |
| 884 if (isStatic) { | |
| 885 // ensure the type is generated. | |
| 886 // TODO(sigmund): can we avoid generating the entire type, but only what | |
| 887 // we need? | |
| 888 declaringType.markUsed(); | |
| 889 var type = declaringType.isTop ? '' : '${declaringType.jsname}.'; | |
| 890 return new Value(functionType, '$type$jsname', node.span); | |
| 891 } | |
| 892 _provideGetter = true; | |
| 893 return new Value(functionType, '${target.code}.$jsnameOfGetter()', node.span
); | |
| 894 } | |
| 895 | |
| 896 /** | |
| 897 * Checks if the named arguments are in their natural or 'home' positions, | |
| 898 * i.e. they may be passed directly without inserting, deleting or moving the | |
| 899 * arguments to correspond with the parameters. | |
| 900 */ | |
| 901 bool namesInHomePositions(Arguments args) { | |
| 902 if (!args.hasNames) return true; | |
| 903 | |
| 904 for (int i = args.bareCount; i < args.values.length; i++) { | |
| 905 if (i >= parameters.length) { | |
| 906 return false; | |
| 907 } | |
| 908 if (args.getName(i) != parameters[i].name) { | |
| 909 return false; | |
| 910 } | |
| 911 } | |
| 912 return true; | |
| 913 } | |
| 914 | |
| 915 bool namesInOrder(Arguments args) { | |
| 916 if (!args.hasNames) return true; | |
| 917 | |
| 918 int lastParameter = null; | |
| 919 for (int i = args.bareCount; i < parameters.length; i++) { | |
| 920 var p = args.getIndexOfName(parameters[i].name); | |
| 921 // Only worry about parameters that needTemps. Otherwise it's fine to | |
| 922 // reorder. | |
| 923 if (p >= 0 && args.values[p].needsTemp) { | |
| 924 if (lastParameter != null && lastParameter > p) { | |
| 925 return false; | |
| 926 } | |
| 927 lastParameter = p; | |
| 928 } | |
| 929 } | |
| 930 return true; | |
| 931 } | |
| 932 | |
| 933 /** Returns true if any of the arguments will need conversion. */ | |
| 934 // TODO(jmesserly): I don't like how this is coupled to invoke | |
| 935 bool needsArgumentConversion(Arguments args) { | |
| 936 int bareCount = args.bareCount; | |
| 937 for (int i = 0; i < bareCount; i++) { | |
| 938 var arg = args.values[i]; | |
| 939 if (arg.needsConversion(parameters[i].type)) { | |
| 940 return true; | |
| 941 } | |
| 942 } | |
| 943 | |
| 944 if (bareCount < parameters.length) { | |
| 945 for (int i = bareCount; i < parameters.length; i++) { | |
| 946 var arg = args.getValue(parameters[i].name); | |
| 947 if (arg != null && arg.needsConversion(parameters[i].type)) { | |
| 948 return true; | |
| 949 } | |
| 950 } | |
| 951 } | |
| 952 | |
| 953 return false; | |
| 954 } | |
| 955 | |
| 956 /** Returns true if any of the parameters are optional. */ | |
| 957 bool hasOptionalParameters() { | |
| 958 return parameters.some((Parameter p) => p.isOptional); | |
| 959 } | |
| 960 | |
| 961 String _tooManyArgumentsMsg(int actual, int expected) { | |
| 962 return hasOptionalParameters() | |
| 963 ? 'too many arguments, expected at most $expected but found $actual' | |
| 964 : _wrongArgumentCountMsg(actual, expected); | |
| 965 } | |
| 966 | |
| 967 String _tooFewArgumentsMsg(int actual, int expected) { | |
| 968 return hasOptionalParameters() | |
| 969 ? 'too few arguments, expected at least $expected but found $actual' | |
| 970 : _wrongArgumentCountMsg(actual, expected); | |
| 971 } | |
| 972 | |
| 973 String _wrongArgumentCountMsg(int actual, int expected) { | |
| 974 return 'wrong number of arguments, expected $expected but found $actual'; | |
| 975 } | |
| 976 | |
| 977 Value _argError(CallingContext context, Node node, Value target, | |
| 978 Arguments args, String msg, int argIndex) { | |
| 979 if (context.showWarnings) { | |
| 980 SourceSpan span; | |
| 981 if ((args.nodes == null) || (argIndex >= args.nodes.length)) { | |
| 982 span = node.span; | |
| 983 } else { | |
| 984 span = args.nodes[argIndex].span; | |
| 985 } | |
| 986 if (isStatic || isConstructor) { | |
| 987 world.error(msg, span); | |
| 988 } else { | |
| 989 world.warning(msg, span); | |
| 990 } | |
| 991 } | |
| 992 return target.invokeNoSuchMethod(context, name, node, args); | |
| 993 } | |
| 994 | |
| 995 genParameterValues(CallingContext context) { | |
| 996 // TODO(jimhug): Is this the right context? | |
| 997 if (context.needsCode) { | |
| 998 for (var p in parameters) p.genValue(this, context); | |
| 999 } | |
| 1000 } | |
| 1001 | |
| 1002 /** | |
| 1003 * Invokes this method on the given [target] with the given [args]. | |
| 1004 * [node] provides a [SourceSpan] for any error messages. | |
| 1005 */ | |
| 1006 Value invoke(CallingContext context, Node node, Value target, | |
| 1007 Arguments args) { | |
| 1008 | |
| 1009 var argValues = <Value>[]; | |
| 1010 int bareCount = args.bareCount; | |
| 1011 for (int i = 0; i < bareCount; i++) { | |
| 1012 var arg = args.values[i]; | |
| 1013 if (i >= parameters.length) { | |
| 1014 var msg = _tooManyArgumentsMsg(args.length, parameters.length); | |
| 1015 return _argError(context, node, target, args, msg, i); | |
| 1016 } | |
| 1017 argValues.add(arg.convertTo(context, parameters[i].type)); | |
| 1018 } | |
| 1019 | |
| 1020 int namedArgsUsed = 0; | |
| 1021 if (bareCount < parameters.length) { | |
| 1022 genParameterValues(context); | |
| 1023 | |
| 1024 for (int i = bareCount; i < parameters.length; i++) { | |
| 1025 var param = parameters[i]; | |
| 1026 var arg = args.getValue(param.name); | |
| 1027 if (arg == null) { | |
| 1028 arg = param.value; | |
| 1029 if (arg == null) { | |
| 1030 // TODO(jmesserly): should we be use the actual constant value here? | |
| 1031 arg = new PureStaticValue(param.type, param.definition.span, true); | |
| 1032 } | |
| 1033 } else { | |
| 1034 arg = arg.convertTo(context, parameters[i].type); | |
| 1035 namedArgsUsed++; | |
| 1036 } | |
| 1037 | |
| 1038 if (arg == null || !parameters[i].isOptional) { | |
| 1039 var msg = _tooFewArgumentsMsg(Math.min(i, args.length), i + 1); | |
| 1040 return _argError(context, node, target, args, msg, i); | |
| 1041 } else { | |
| 1042 argValues.add(arg); | |
| 1043 } | |
| 1044 } | |
| 1045 } | |
| 1046 | |
| 1047 if (namedArgsUsed < args.nameCount) { | |
| 1048 // Find the unused argument name | |
| 1049 var seen = new Set<String>(); | |
| 1050 for (int i = bareCount; i < args.length; i++) { | |
| 1051 var name = args.getName(i); | |
| 1052 if (seen.contains(name)) { | |
| 1053 return _argError(context, node, target, args, | |
| 1054 'duplicate argument "$name"', i); | |
| 1055 } | |
| 1056 seen.add(name); | |
| 1057 int p = indexOfParameter(name); | |
| 1058 if (p < 0) { | |
| 1059 return _argError(context, node, target, args, | |
| 1060 'method does not have optional parameter "$name"', i); | |
| 1061 } else if (p < bareCount) { | |
| 1062 return _argError(context, node, target, args, | |
| 1063 'argument "$name" passed as positional and named', | |
| 1064 // Given that the named was mentioned explicitly, highlight the | |
| 1065 // positional location instead: | |
| 1066 p); | |
| 1067 } | |
| 1068 } | |
| 1069 world.internalError('wrong named arguments calling $name', node.span); | |
| 1070 } | |
| 1071 | |
| 1072 if (!context.needsCode) { | |
| 1073 return new PureStaticValue(returnType, node.span); | |
| 1074 } | |
| 1075 | |
| 1076 declaringType.genMethod(this); | |
| 1077 | |
| 1078 if (isStatic || isFactory) { | |
| 1079 // TODO(sigmund): can we avoid generating the entire type, but only what | |
| 1080 // we need? | |
| 1081 declaringType.markUsed(); | |
| 1082 } | |
| 1083 | |
| 1084 // TODO(jmesserly): get rid of this in favor of using the native method | |
| 1085 // "bodies" to tell the compiler about valid return types. | |
| 1086 if (isNative && returnType != null) returnType.markUsed(); | |
| 1087 | |
| 1088 if (!namesInOrder(args)) { | |
| 1089 // Names aren't in order. For now, use a var call because it's an | |
| 1090 // easy way to get the right eval order for out of order arguments. | |
| 1091 // TODO(jmesserly): temps would be better. | |
| 1092 return context.findMembers(name).invokeOnVar(context, node, target, args); | |
| 1093 } | |
| 1094 | |
| 1095 var argsCode = argValues.map((v) => v.code); | |
| 1096 if (!target.isType && (isConstructor || target.isSuper)) { | |
| 1097 argsCode.insertRange(0, 1, 'this'); | |
| 1098 } | |
| 1099 if (bareCount < parameters.length) { | |
| 1100 Arguments.removeTrailingNulls(argsCode); | |
| 1101 } | |
| 1102 var argsString = Strings.join(argsCode, ', '); | |
| 1103 | |
| 1104 if (isConstructor) { | |
| 1105 return _invokeConstructor(context, node, target, args, argsString); | |
| 1106 } | |
| 1107 | |
| 1108 if (target.isSuper) { | |
| 1109 return new Value(inferredResult, | |
| 1110 '${declaringType.jsname}.prototype.$jsname.call($argsString)', | |
| 1111 node.span); | |
| 1112 } | |
| 1113 | |
| 1114 if (isOperator) { | |
| 1115 return _invokeBuiltin(context, node, target, args, argsCode); | |
| 1116 } | |
| 1117 | |
| 1118 if (isFactory) { | |
| 1119 assert(target.isType); | |
| 1120 return new Value(target.type, '$generatedFactoryName($argsString)', | |
| 1121 node !== null ? node.span : null); | |
| 1122 } | |
| 1123 | |
| 1124 if (isStatic) { | |
| 1125 if (declaringType.isTop) { | |
| 1126 return new Value(inferredResult, | |
| 1127 '$jsname($argsString)', node !== null ? node.span : null); | |
| 1128 } | |
| 1129 return new Value(inferredResult, | |
| 1130 '${declaringType.jsname}.$jsname($argsString)', node.span); | |
| 1131 } | |
| 1132 | |
| 1133 // TODO(jmesserly): factor this better | |
| 1134 if (name == 'get:typeName' && declaringType.library.isDomOrHtml) { | |
| 1135 world.gen.corejs.ensureTypeNameOf(); | |
| 1136 } | |
| 1137 | |
| 1138 var code = '${target.code}.$jsname($argsString)'; | |
| 1139 return new Value(inferredResult, code, node.span); | |
| 1140 } | |
| 1141 | |
| 1142 Value _invokeConstructor(CallingContext context, Node node, | |
| 1143 Value target, Arguments args, argsString) { | |
| 1144 declaringType.markUsed(); | |
| 1145 | |
| 1146 String ctor = constructorName; | |
| 1147 if (ctor != '') ctor = '.${ctor}\$ctor'; | |
| 1148 | |
| 1149 final span = node != null ? node.span : target.span; | |
| 1150 if (!target.isType) { | |
| 1151 // initializer call to another constructor | |
| 1152 var code = '${declaringType.nativeName}${ctor}.call($argsString)'; | |
| 1153 return new Value(target.type, code, span); | |
| 1154 } else { | |
| 1155 // Start of abstract interpretation to replace const hacks goes here | |
| 1156 // TODO(jmesserly): using the "node" here feels really hacky | |
| 1157 if (isConst && node is NewExpression && node.dynamic.isConst) { | |
| 1158 // TODO(jimhug): Embedding JSSyntaxRegExp works around an annoying | |
| 1159 // issue with tracking native constructors for const objects. | |
| 1160 if (isNative || declaringType.name == 'JSSyntaxRegExp') { | |
| 1161 // check that all args are const? | |
| 1162 var code = 'new ${declaringType.nativeName}${ctor}($argsString)'; | |
| 1163 return world.gen.globalForConst(new Value(target.type, code, span), | |
| 1164 [args.values]); | |
| 1165 } | |
| 1166 var newType = declaringType; | |
| 1167 var newObject = new ObjectValue(true, newType, span); | |
| 1168 newObject.initFields(); | |
| 1169 _evalConstConstructor(newObject, args); | |
| 1170 return world.gen.globalForConst(newObject, [args.values]); | |
| 1171 } else { | |
| 1172 var code = 'new ${declaringType.nativeName}${ctor}($argsString)'; | |
| 1173 return new Value(target.type, code, span); | |
| 1174 } | |
| 1175 } | |
| 1176 } | |
| 1177 | |
| 1178 _evalConstConstructor(Value newObject, Arguments args) { | |
| 1179 declaringType.markUsed(); | |
| 1180 methodData.eval(this, newObject, args); | |
| 1181 } | |
| 1182 | |
| 1183 Value _invokeBuiltin(CallingContext context, Node node, Value target, | |
| 1184 Arguments args, argsCode) { | |
| 1185 // Handle some fast paths for Number, String, List and DOM. | |
| 1186 if (target.type.isNum) { | |
| 1187 // TODO(jimhug): This fails in bad ways when argsCode[1] is not num. | |
| 1188 // TODO(jimhug): What about null? | |
| 1189 var code = null; | |
| 1190 if (args.length == 0) { | |
| 1191 if (name == ':negate') { | |
| 1192 code = '-${target.code}'; | |
| 1193 } else if (name == ':bit_not') { | |
| 1194 code = '~${target.code}'; | |
| 1195 } | |
| 1196 } else if (args.length == 1 && args.values[0].type.isNum) { | |
| 1197 if (name == ':truncdiv' || name == ':mod') { | |
| 1198 world.gen.corejs.useOperator(name); | |
| 1199 code = '$jsname\$(${target.code}, ${argsCode[0]})'; | |
| 1200 } else { | |
| 1201 var op = TokenKind.rawOperatorFromMethod(name); | |
| 1202 code = '${target.code} $op ${argsCode[0]}'; | |
| 1203 } | |
| 1204 } | |
| 1205 if (code !== null) { | |
| 1206 return new Value(inferredResult, code, node.span); | |
| 1207 } | |
| 1208 } else if (target.type.isString) { | |
| 1209 if (name == ':index' && args.values[0].type.isNum) { | |
| 1210 return new Value(declaringType, '${target.code}[${argsCode[0]}]', | |
| 1211 node.span); | |
| 1212 } else if (name == ':add' && args.values[0].type.isNum) { | |
| 1213 return new Value(declaringType, '${target.code} + ${argsCode[0]}', | |
| 1214 node.span); | |
| 1215 } | |
| 1216 } else if (declaringType.isNative && options.disableBoundsChecks) { | |
| 1217 if (args.length > 0 && args.values[0].type.isNum) { | |
| 1218 if (name == ':index') { | |
| 1219 return new Value(returnType, | |
| 1220 '${target.code}[${argsCode[0]}]', node.span); | |
| 1221 } else if (name == ':setindex') { | |
| 1222 return new Value(returnType, | |
| 1223 '${target.code}[${argsCode[0]}] = ${argsCode[1]}', node.span); | |
| 1224 } | |
| 1225 } | |
| 1226 } | |
| 1227 | |
| 1228 // TODO(jimhug): Optimize null on lhs as well. | |
| 1229 if (name == ':eq' || name == ':ne') { | |
| 1230 final op = name == ':eq' ? '==' : '!='; | |
| 1231 | |
| 1232 if (name == ':ne') { | |
| 1233 // Ensure == is generated. | |
| 1234 target.invoke(context, ':eq', node, args); | |
| 1235 } | |
| 1236 | |
| 1237 // Optimize test when null is on the rhs. | |
| 1238 if (argsCode[0] == 'null') { | |
| 1239 return new Value(inferredResult, '${target.code} $op null', node.span); | |
| 1240 } else if (target.type.isNum || target.type.isString) { | |
| 1241 // TODO(jimhug): Maybe check rhs. | |
| 1242 return new Value(inferredResult, '${target.code} $op ${argsCode[0]}', | |
| 1243 node.span); | |
| 1244 } | |
| 1245 world.gen.corejs.useOperator(name); | |
| 1246 // TODO(jimhug): Should be able to use faster path sometimes here! | |
| 1247 return new Value(inferredResult, | |
| 1248 '$jsname\$(${target.code}, ${argsCode[0]})', node.span); | |
| 1249 } | |
| 1250 | |
| 1251 if (isCallMethod) { | |
| 1252 declaringType.markUsed(); | |
| 1253 return new Value(inferredResult, | |
| 1254 '${target.code}(${Strings.join(argsCode, ", ")})', node.span); | |
| 1255 } | |
| 1256 | |
| 1257 // TODO(jimhug): Reconcile with MethodSet version - ideally just eliminate | |
| 1258 if (name == ':index') { | |
| 1259 world.gen.corejs.useIndex = true; | |
| 1260 } else if (name == ':setindex') { | |
| 1261 world.gen.corejs.useSetIndex = true; | |
| 1262 } else { | |
| 1263 world.gen.corejs.useOperator(name); | |
| 1264 var argsString = argsCode.length == 0 ? '' : ', ${argsCode[0]}'; | |
| 1265 return new Value(returnType, '$jsname\$(${target.code}${argsString})', | |
| 1266 node.span); | |
| 1267 } | |
| 1268 | |
| 1269 // Fall back to normal method invocation. | |
| 1270 var argsString = Strings.join(argsCode, ', '); | |
| 1271 return new Value(inferredResult, '${target.code}.$jsname($argsString)', | |
| 1272 node.span); | |
| 1273 } | |
| 1274 | |
| 1275 resolve() { | |
| 1276 // TODO(jimhug): work through side-by-side with spec | |
| 1277 isStatic = declaringType.isTop; | |
| 1278 isConst = false; | |
| 1279 isFactory = false; | |
| 1280 isAbstract = !declaringType.isClass; | |
| 1281 if (definition.modifiers != null) { | |
| 1282 for (var mod in definition.modifiers) { | |
| 1283 if (mod.kind == TokenKind.STATIC) { | |
| 1284 if (isStatic) { | |
| 1285 world.error('duplicate static modifier', mod.span); | |
| 1286 } | |
| 1287 isStatic = true; | |
| 1288 } else if (isConstructor && mod.kind == TokenKind.CONST) { | |
| 1289 if (isConst) { | |
| 1290 world.error('duplicate const modifier', mod.span); | |
| 1291 } | |
| 1292 if (isFactory) { | |
| 1293 world.error('const factory not allowed', mod.span); | |
| 1294 } | |
| 1295 isConst = true; | |
| 1296 } else if (mod.kind == TokenKind.FACTORY) { | |
| 1297 if (isFactory) { | |
| 1298 world.error('duplicate factory modifier', mod.span); | |
| 1299 } | |
| 1300 if (isConst) { | |
| 1301 world.error('const factory not allowed', mod.span); | |
| 1302 } | |
| 1303 if (isStatic) { | |
| 1304 world.error('static factory not allowed', mod.span); | |
| 1305 } | |
| 1306 isFactory = true; | |
| 1307 } else if (mod.kind == TokenKind.ABSTRACT) { | |
| 1308 if (isAbstract) { | |
| 1309 if (declaringType.isClass) { | |
| 1310 world.error('duplicate abstract modifier', mod.span); | |
| 1311 } else if (!isCallMethod) { | |
| 1312 world.error('abstract modifier not allowed on interface members', | |
| 1313 mod.span); | |
| 1314 } | |
| 1315 } | |
| 1316 isAbstract = true; | |
| 1317 } else { | |
| 1318 world.error('${mod} modifier not allowed on method', mod.span); | |
| 1319 } | |
| 1320 } | |
| 1321 } | |
| 1322 | |
| 1323 if (isFactory) { | |
| 1324 isStatic = true; | |
| 1325 } | |
| 1326 | |
| 1327 // TODO(jimhug): need a better annotation for being an operator method | |
| 1328 if (isOperator && isStatic && !isCallMethod) { | |
| 1329 world.error('operator method may not be static "${name}"', span); | |
| 1330 } | |
| 1331 | |
| 1332 if (isAbstract) { | |
| 1333 if (definition.body != null && | |
| 1334 declaringType.definition is! FunctionTypeDefinition) { | |
| 1335 // TODO(jimhug): Creating function types for concrete methods is | |
| 1336 // steadily feeling uglier... | |
| 1337 world.error('abstract method cannot have a body', span); | |
| 1338 } | |
| 1339 if (isStatic && | |
| 1340 declaringType.definition is! FunctionTypeDefinition) { | |
| 1341 world.error('static method cannot be abstract', span); | |
| 1342 } | |
| 1343 } else { | |
| 1344 if (definition.body == null && !isConstructor && !isNative) { | |
| 1345 world.error('method needs a body', span); | |
| 1346 } | |
| 1347 } | |
| 1348 | |
| 1349 if (isConstructor && !isFactory) { | |
| 1350 returnType = declaringType; | |
| 1351 } else { | |
| 1352 // This is the one and only place we allow void. | |
| 1353 if (definition.returnType is SimpleTypeReference && | |
| 1354 definition.returnType.dynamic.type == world.voidType) { | |
| 1355 returnType = world.voidType; | |
| 1356 } else { | |
| 1357 returnType = resolveType(definition.returnType, false, !isStatic); | |
| 1358 } | |
| 1359 } | |
| 1360 parameters = []; | |
| 1361 for (var formal in definition.formals) { | |
| 1362 // TODO(jimhug): Clean up construction of Parameters. | |
| 1363 var param = new Parameter(formal, this); | |
| 1364 param.resolve(); | |
| 1365 parameters.add(param); | |
| 1366 } | |
| 1367 | |
| 1368 if (!isLambda && declaringType.isClass) { | |
| 1369 library._addMember(this); | |
| 1370 } | |
| 1371 } | |
| 1372 } | |
| 1373 | |
| 1374 | |
| 1375 /** | |
| 1376 * A [FactoryMap] maps type names to a list of factory constructors. | |
| 1377 * The constructors list is actually a map that maps factory names to | |
| 1378 * [MethodMember]. The reason why we need both indirections are: | |
| 1379 * 1) A class can define factory methods for multiple interfaces. | |
| 1380 * 2) A factory constructor can have a name. | |
| 1381 * | |
| 1382 * For example: | |
| 1383 * | |
| 1384 * [: | |
| 1385 * interface I factory A { | |
| 1386 * I(); | |
| 1387 * I.foo(); | |
| 1388 * } | |
| 1389 * | |
| 1390 * interface I2 factory A { | |
| 1391 * I2(); | |
| 1392 * } | |
| 1393 * | |
| 1394 * class A { | |
| 1395 * factory I() { ... } // Member1 | |
| 1396 * factory I.foo() { ... } // Member2 | |
| 1397 * factory I2() { ... } // Member3 | |
| 1398 * factory A() { ... } // Member4 | |
| 1399 * } | |
| 1400 * :] | |
| 1401 * | |
| 1402 * The [:factories:] field of A will be a [FactoryMap] that looks | |
| 1403 * like: | |
| 1404 * { "I" : { "": Member1, "foo": Member2 }, | |
| 1405 * "I2" : { "": Member3 }, | |
| 1406 * "A" : { "", Member4 } | |
| 1407 * } | |
| 1408 */ | |
| 1409 class FactoryMap { | |
| 1410 Map<String, Map<String, Member>> factories; | |
| 1411 | |
| 1412 FactoryMap() : factories = {}; | |
| 1413 | |
| 1414 // Returns the factories defined for [type]. | |
| 1415 Map<String, Member> getFactoriesFor(String typeName) { | |
| 1416 var ret = factories[typeName]; | |
| 1417 if (ret == null) { | |
| 1418 ret = {}; | |
| 1419 factories[typeName] = ret; | |
| 1420 } | |
| 1421 return ret; | |
| 1422 } | |
| 1423 | |
| 1424 void addFactory(String typeName, String name, Member member) { | |
| 1425 getFactoriesFor(typeName)[name] = member; | |
| 1426 } | |
| 1427 | |
| 1428 Member getFactory(String typeName, String name) { | |
| 1429 return getFactoriesFor(typeName)[name]; | |
| 1430 } | |
| 1431 | |
| 1432 void forEach(void f(Member member)) { | |
| 1433 factories.forEach((_, Map constructors) { | |
| 1434 constructors.forEach((_, Member member) { | |
| 1435 f(member); | |
| 1436 }); | |
| 1437 }); | |
| 1438 } | |
| 1439 | |
| 1440 bool isEmpty() { | |
| 1441 return factories.getValues() | |
| 1442 .every((Map constructors) => constructors.isEmpty()); | |
| 1443 } | |
| 1444 } | |
| OLD | NEW |