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