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 class MemberSet { | |
6 final String name; | |
7 final List<Member> members; | |
8 final bool isVar; | |
9 | |
10 bool _treatAsField; | |
11 Type _returnTypeForGet; | |
12 bool _preparedForSet = false; | |
13 List<InvokeKey> _invokes; | |
14 | |
15 MemberSet(Member member, [bool isVar=false]) | |
16 : name = member.name, members = [member], isVar = isVar; | |
17 | |
18 toString() => '$name:${members.length}'; | |
19 | |
20 /** | |
21 * [jsname] should be called only when it is known that all the members have | |
22 * the same jsname. (The name safety pass can cause members of the same | |
23 * MemberSet to have different jsnames.) | |
24 */ | |
25 String get jsname() => members[0].jsname; | |
26 | |
27 void add(Member member) { | |
28 // Only methods on classes "really exist" - so warn if we add others? | |
29 members.add(member); | |
30 } | |
31 | |
32 // TODO(jimhug): Better way to check for operator. | |
33 bool get isOperator() => members[0].isOperator; | |
34 | |
35 bool get treatAsField() { | |
36 if (_treatAsField == null) { | |
37 // Must be all fields, all with the same jsname. | |
38 _treatAsField = !isVar && | |
39 members.every((m) => m.isField && m.jsname == members[0].jsname); | |
40 } | |
41 return _treatAsField; | |
42 } | |
43 | |
44 static Type unionTypes(Type t1, Type t2) { | |
45 if (t1 == null) return t2; | |
46 if (t2 == null) return t1; | |
47 return Type.union(t1, t2); | |
48 } | |
49 | |
50 /** | |
51 * This needs to generate one of the following: | |
52 * - target.name | |
53 * - target.get$name() | |
54 * - target.noSuchMethod(...) | |
55 * | |
56 * Can be treated as a field only if this is a properly resolved reference | |
57 * and all resolve members are fields. | |
58 */ | |
59 Value _get(CallingContext context, Node node, Value target) { | |
60 if (members.length == 1 && !isVar) { | |
61 return members[0]._get(context, node, target); | |
62 } | |
63 | |
64 if (_returnTypeForGet == null) { | |
65 for (var member in members) { | |
66 if (!member.canGet) continue; | |
67 if (!treatAsField) member.provideGetter(); | |
68 // TODO(jimhug): Need to make target less specific... | |
69 var r = member._get(context, node, target); | |
70 _returnTypeForGet = unionTypes(_returnTypeForGet, r.type); | |
71 } | |
72 if (_returnTypeForGet == null) { | |
73 world.error('no valid getters for "$name"', node.span); | |
74 } | |
75 } | |
76 | |
77 if (_treatAsField) { | |
78 return new Value(_returnTypeForGet, | |
79 '${target.code}.$jsname', node.span); | |
80 } else { | |
81 return new Value(_returnTypeForGet, | |
82 '${target.code}.${members[0].jsnameOfGetter}()', node.span); | |
83 } | |
84 } | |
85 | |
86 // TODO(jimhug): Return value of this method is unclear. | |
87 Value _set(CallingContext context, Node node, Value target, Value value) { | |
88 // If this is the global MemberSet from world, always bind dynamically. | |
89 // Note: we need this for proper noSuchMethod and REPL behavior. | |
90 if (members.length == 1 && !isVar) { | |
91 return members[0]._set(context, node, target, value); | |
92 } | |
93 | |
94 if (!_preparedForSet) { | |
95 _preparedForSet = true; | |
96 | |
97 for (var member in members) { | |
98 if (!member.canSet) continue; | |
99 if (!treatAsField) member.provideSetter(); | |
100 // !!! Need more generic args to this call below. | |
101 var r = member._set(context, node, target, value); | |
102 } | |
103 } | |
104 | |
105 if (treatAsField) { | |
106 return new Value(value.type, | |
107 '${target.code}.$jsname = ${value.code}', node.span); | |
108 } else { | |
109 return new Value(value.type, | |
110 '${target.code}.${members[0].jsnameOfSetter}(${value.code})', node.span)
; | |
111 } | |
112 } | |
113 | |
114 Value invoke(CallingContext context, Node node, Value target, | |
115 Arguments args) { | |
116 if (members.length == 1 && !isVar) { | |
117 return members[0].invoke(context, node, target, args); | |
118 } | |
119 | |
120 var invokeKey = null; | |
121 if (_invokes == null) { | |
122 _invokes = []; | |
123 invokeKey = null; | |
124 } else { | |
125 for (var ik in _invokes) { | |
126 if (ik.matches(args)) { | |
127 invokeKey = ik; | |
128 break; | |
129 } | |
130 } | |
131 } | |
132 if (invokeKey == null) { | |
133 invokeKey = new InvokeKey(args); | |
134 _invokes.add(invokeKey); | |
135 invokeKey.addMembers(members, context, target, args); | |
136 } | |
137 | |
138 // TODO(jimhug): isOperator test is too lenient - misses opt chances | |
139 if (invokeKey.needsVarCall || isOperator) { | |
140 if (name == ':call') { | |
141 return target._varCall(context, node, args); | |
142 } else if (isOperator) { | |
143 // TODO(jmesserly): make operators less special. | |
144 return invokeSpecial(target, args, invokeKey.returnType); | |
145 } else { | |
146 return invokeOnVar(context, node, target, args); | |
147 } | |
148 } else { | |
149 var code = '${target.code}.${jsname}(${args.getCode()})'; | |
150 return new Value(invokeKey.returnType, code, node.span); | |
151 } | |
152 } | |
153 | |
154 Value invokeSpecial(Value target, Arguments args, Type returnType) { | |
155 assert(name.startsWith(':')); | |
156 assert(!args.hasNames); | |
157 // TODO(jimhug): We need to do this a little bit more like get and set on | |
158 // properties. We should check the set of members for something | |
159 // like "requiresNativeIndexer" and "requiresDartIndexer" to | |
160 // decide on a strategy. | |
161 | |
162 var argsString = args.getCode(); | |
163 // Most operator calls need to be emitted as function calls, so we don't | |
164 // box numbers accidentally. Indexing is the exception. | |
165 if (name == ':index' || name == ':setindex') { | |
166 // TODO(jimhug): should not need this test both here and in invoke | |
167 if (name == ':index') { | |
168 world.gen.corejs.useIndex = true; | |
169 } else if (name == ':setindex') { | |
170 world.gen.corejs.useSetIndex = true; | |
171 } | |
172 return new Value(returnType, '${target.code}.$jsname($argsString)', | |
173 target.span); | |
174 } else { | |
175 if (argsString.length > 0) argsString = ', $argsString'; | |
176 world.gen.corejs.useOperator(name); | |
177 return new Value(returnType, '$jsname\$(${target.code}$argsString)', | |
178 target.span); | |
179 } | |
180 } | |
181 | |
182 Value invokeOnVar(CallingContext context, Node node, Value target, | |
183 Arguments args) { | |
184 context.counters.dynamicMethodCalls++; | |
185 | |
186 var member = getVarMember(context, node, args); | |
187 return member.invoke(context, node, target, args); | |
188 } | |
189 | |
190 dumpAllMembers() { | |
191 for (var member in members) { | |
192 world.warning('hard-multi $name on ${member.declaringType.name}', | |
193 member.span); | |
194 } | |
195 } | |
196 | |
197 VarMember getVarMember(CallingContext context, Node node, Arguments args) { | |
198 if (world.objectType.varStubs == null) { | |
199 world.objectType.varStubs = {}; | |
200 } | |
201 | |
202 var stubName = _getCallStubName(name, args); | |
203 var stub = world.objectType.varStubs[stubName]; | |
204 if (stub == null) { | |
205 // Ensure that we're making stub with all possible members of this name. | |
206 // We need this canonicalization step because only one VarMemberSet can | |
207 // live on Object.prototype | |
208 // TODO(jmesserly): this is ugly--we're throwing away type information! | |
209 // The right solution is twofold: | |
210 // 1. put stubs on a more precise type when possible | |
211 // 2. merge VarMemberSets together if necessary | |
212 final mset = context.findMembers(name).members; | |
213 | |
214 final targets = mset.filter((m) => m.canInvoke(context, args)); | |
215 stub = new VarMethodSet(name, stubName, targets, args, | |
216 _foldTypes(targets)); | |
217 world.objectType.varStubs[stubName] = stub; | |
218 } | |
219 return stub; | |
220 } | |
221 | |
222 Type _foldTypes(List<Member> targets) => | |
223 reduce(map(targets, (t) => t.returnType), Type.union, world.varType); | |
224 } | |
225 | |
226 | |
227 class InvokeKey { | |
228 int bareArgs; | |
229 List<String> namedArgs; | |
230 Type returnType; | |
231 bool needsVarCall = false; | |
232 | |
233 InvokeKey(Arguments args) { | |
234 bareArgs = args.bareCount; | |
235 if (bareArgs != args.length) { | |
236 namedArgs = args.getNames(); | |
237 } | |
238 } | |
239 | |
240 bool matches(Arguments args) { | |
241 if (namedArgs == null) { | |
242 if (bareArgs != args.length) return false; | |
243 } else { | |
244 if (bareArgs + namedArgs.length != args.length) return false; | |
245 } | |
246 if (bareArgs != args.bareCount) return false; | |
247 | |
248 if (namedArgs == null) return true; | |
249 | |
250 for (int i = 0; i < namedArgs.length; i++) { | |
251 if (namedArgs[i] != args.getName(bareArgs + i)) return false; | |
252 } | |
253 return true; | |
254 } | |
255 | |
256 void addMembers(List<Member> members, CallingContext context, Value target, | |
257 Arguments args) { | |
258 for (var member in members) { | |
259 // Check that this is a "perfect" match - or require a var call. | |
260 // TODO(jimhug): Add support of "perfect matches" even with names. | |
261 if (!(member.parameters.length == bareArgs && namedArgs == null)) { | |
262 // If we have named arguments or a mismatch in the number of | |
263 // formal and actual parameters, we go through a var call. | |
264 needsVarCall = true; | |
265 } else if (options.enableTypeChecks && | |
266 member.isMethod && | |
267 member.needsArgumentConversion(args)) { | |
268 // The member we're adding is a method that needs argument | |
269 // conversion, so we have to make it go through the var call | |
270 // path to get the correct type checks inserted. | |
271 needsVarCall = true; | |
272 } else if (member.jsname != members[0].jsname) { | |
273 // If the jsnames differ we need the var call since one of the stubs | |
274 // will change the name. Native methods can have different jsnames, | |
275 // e.g. | |
276 // foo() native 'bar'; | |
277 needsVarCall = true; | |
278 } else if (member.library.isDomOrHtml) { | |
279 // TODO(jimhug): Egregious hack for isolates + DOM - see | |
280 // Value._maybeWrapFunction for more details. | |
281 for (var p in member.parameters) { | |
282 if (p.type.getCallMethod() != null) { | |
283 needsVarCall = true; | |
284 } | |
285 } | |
286 } | |
287 | |
288 // TODO(jimhug): Should create a less specific version of args. | |
289 if (member.canInvoke(context, args)) { | |
290 if (member.isMethod) { | |
291 returnType = MemberSet.unionTypes(returnType, member.returnType); | |
292 member.declaringType.genMethod(member); | |
293 } else { | |
294 needsVarCall = true; | |
295 returnType = world.varType; | |
296 } | |
297 } | |
298 } | |
299 if (returnType == null) { | |
300 // TODO(jimhug): Warning here for no match anywhere in the world? | |
301 returnType = world.varType; | |
302 } | |
303 } | |
304 } | |
OLD | NEW |