| 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 |