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 |