| Index: frog/member_set.dart
|
| diff --git a/frog/member_set.dart b/frog/member_set.dart
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..5912f54ca7b07237929eff507100f40fc36f12ef
|
| --- /dev/null
|
| +++ b/frog/member_set.dart
|
| @@ -0,0 +1,283 @@
|
| +// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
|
| +// for details. All rights reserved. Use of this source code is governed by a
|
| +// BSD-style license that can be found in the LICENSE file.
|
| +
|
| +class MemberSet {
|
| + final String name;
|
| + final List<Member> members;
|
| + final String jsname;
|
| + final bool isVar;
|
| +
|
| + bool _treatAsField;
|
| + Type _returnTypeForGet;
|
| + bool _preparedForSet = false;
|
| + List<InvokeKey> _invokes;
|
| +
|
| + MemberSet(Member member, [bool isVar=false]):
|
| + name = member.name, members = [member], jsname = member.jsname,
|
| + isVar = isVar;
|
| +
|
| + toString() => '$name:${members.length}';
|
| +
|
| + void add(Member member) {
|
| + // Only methods on classes "really exist" - so warn if we add others?
|
| + members.add(member);
|
| + }
|
| +
|
| + // TODO(jimhug): Better way to check for operator.
|
| + bool get isOperator() => members[0].isOperator;
|
| +
|
| + Value _makeError(Node node, Value target, String action) {
|
| + if (!target.type.isVar) {
|
| + world.warning('could not find applicable $action for "$name"', node.span);
|
| + }
|
| + return new Value(world.varType,
|
| + '${target.code}.$jsname() /*no applicable $action*/', node.span);
|
| + }
|
| +
|
| + bool get treatAsField() {
|
| + if (_treatAsField == null) {
|
| + _treatAsField = !isVar && members.every((m) => m.isField);
|
| + }
|
| + return _treatAsField;
|
| + }
|
| +
|
| + static Type unionTypes(Type t1, Type t2) {
|
| + if (t1 == null) return t2;
|
| + if (t2 == null) return t1;
|
| + return Type.union(t1, t2);
|
| + }
|
| +
|
| + /**
|
| + * This needs to generate one of the following:
|
| + * - target.name
|
| + * - target.get$name()
|
| + * - target.noSuchMethod(...)
|
| + *
|
| + * Can be treated as a field only if this is a properly resolved reference
|
| + * and all resolve members are fields.
|
| + */
|
| + Value _get(CallingContext context, Node node, Value target) {
|
| + if (members.length == 1 && !isVar) {
|
| + return members[0]._get(context, node, target);
|
| + }
|
| +
|
| + if (_returnTypeForGet == null) {
|
| + for (var member in members) {
|
| + if (!member.canGet) continue;
|
| + if (!treatAsField) member.providePropertySyntax();
|
| + // TODO(jimhug): Need to make target less specific...
|
| + var r = member._get(context, node, target);
|
| + _returnTypeForGet = unionTypes(_returnTypeForGet, r.type);
|
| + }
|
| + if (_returnTypeForGet == null) {
|
| + world.error('no valid getters for "$name"', node.span);
|
| + }
|
| + }
|
| +
|
| + if (_treatAsField) {
|
| + return new Value(_returnTypeForGet, '${target.code}.$jsname',
|
| + node.span);
|
| + } else {
|
| + return new Value(_returnTypeForGet, '${target.code}.get\$$jsname()',
|
| + node.span);
|
| + }
|
| + }
|
| +
|
| + // TODO(jimhug): Return value of this method is unclear.
|
| + Value _set(CallingContext context, Node node, Value target, Value value) {
|
| + // If this is the global MemberSet from world, always bind dynamically.
|
| + // Note: we need this for proper noSuchMethod and REPL behavior.
|
| + if (members.length == 1 && !isVar) {
|
| + return members[0]._set(context, node, target, value);
|
| + }
|
| +
|
| + if (!_preparedForSet) {
|
| + _preparedForSet = true;
|
| +
|
| + for (var member in members) {
|
| + if (!member.canSet) continue;
|
| + if (!treatAsField) member.providePropertySyntax();
|
| + // !!! Need more generic args to this call below.
|
| + var r = member._set(context, node, target, value);
|
| + }
|
| + }
|
| +
|
| + if (treatAsField) {
|
| + return new Value(value.type,
|
| + '${target.code}.$jsname = ${value.code}', node.span);
|
| + } else {
|
| + return new Value(value.type,
|
| + '${target.code}.set\$$jsname(${value.code})', node.span);
|
| + }
|
| + }
|
| +
|
| + Value invoke(CallingContext context, Node node, Value target,
|
| + Arguments args) {
|
| + if (members.length == 1 && !isVar) {
|
| + return members[0].invoke(context, node, target, args);
|
| + }
|
| +
|
| + var invokeKey = null;
|
| + if (_invokes == null) {
|
| + _invokes = [];
|
| + invokeKey = null;
|
| + } else {
|
| + for (var ik in _invokes) {
|
| + if (ik.matches(args)) {
|
| + invokeKey = ik;
|
| + break;
|
| + }
|
| + }
|
| + }
|
| + if (invokeKey == null) {
|
| + invokeKey = new InvokeKey(args);
|
| + _invokes.add(invokeKey);
|
| + invokeKey.addMembers(members, context, target, args);
|
| + }
|
| +
|
| + // TODO(jimhug): isOperator test is too lenient - misses opt chances
|
| + if (invokeKey.needsVarCall || isOperator) {
|
| + if (name == ':call') {
|
| + return target._varCall(context, node, args);
|
| + } else if (isOperator) {
|
| + // TODO(jmesserly): make operators less special.
|
| + return invokeSpecial(target, args, invokeKey.returnType);
|
| + } else {
|
| + return invokeOnVar(context, node, target, args);
|
| + }
|
| + } else {
|
| + var code = '${target.code}.${jsname}(${args.getCode()})';
|
| + return new Value(invokeKey.returnType, code, node.span);
|
| + }
|
| + }
|
| +
|
| + Value invokeSpecial(Value target, Arguments args, Type returnType) {
|
| + assert(name.startsWith(':'));
|
| + assert(!args.hasNames);
|
| + // TODO(jimhug): We need to do this a little bit more like get and set on
|
| + // properties. We should check the set of members for something
|
| + // like "requiresNativeIndexer" and "requiresDartIndexer" to
|
| + // decide on a strategy.
|
| +
|
| + var argsString = args.getCode();
|
| + // Most operator calls need to be emitted as function calls, so we don't
|
| + // box numbers accidentally. Indexing is the exception.
|
| + if (name == ':index' || name == ':setindex') {
|
| + // TODO(jimhug): should not need this test both here and in invoke
|
| + if (name == ':index') {
|
| + world.gen.corejs.useIndex = true;
|
| + } else if (name == ':setindex') {
|
| + world.gen.corejs.useSetIndex = true;
|
| + }
|
| + return new Value(returnType, '${target.code}.$jsname($argsString)',
|
| + target.span);
|
| + } else {
|
| + if (argsString.length > 0) argsString = ', $argsString';
|
| + world.gen.corejs.useOperator(name);
|
| + return new Value(returnType, '$jsname(${target.code}$argsString)',
|
| + target.span);
|
| + }
|
| + }
|
| +
|
| + Value invokeOnVar(CallingContext context, Node node, Value target,
|
| + Arguments args) {
|
| + context.counters.dynamicMethodCalls++;
|
| +
|
| + var member = getVarMember(context, node, args);
|
| + return member.invoke(context, node, target, args);
|
| + }
|
| +
|
| + dumpAllMembers() {
|
| + for (var member in members) {
|
| + world.warning('hard-multi $name on ${member.declaringType.name}',
|
| + member.span);
|
| + }
|
| + }
|
| +
|
| + VarMember getVarMember(CallingContext context, Node node, Arguments args) {
|
| + if (world.objectType.varStubs == null) {
|
| + world.objectType.varStubs = {};
|
| + }
|
| +
|
| + var stubName = _getCallStubName(name, args);
|
| + var stub = world.objectType.varStubs[stubName];
|
| + if (stub == null) {
|
| + // Ensure that we're making stub with all possible members of this name.
|
| + // We need this canonicalization step because only one VarMemberSet can
|
| + // live on Object.prototype
|
| + // TODO(jmesserly): this is ugly--we're throwing away type information!
|
| + // The right solution is twofold:
|
| + // 1. put stubs on a more precise type when possible
|
| + // 2. merge VarMemberSets together if necessary
|
| + final mset = context.findMembers(name).members;
|
| +
|
| + final targets = mset.filter((m) => m.canInvoke(context, args));
|
| + stub = new VarMethodSet(name, stubName, targets, args,
|
| + _foldTypes(targets));
|
| + world.objectType.varStubs[stubName] = stub;
|
| + }
|
| + return stub;
|
| + }
|
| +
|
| + Type _foldTypes(List<Member> targets) =>
|
| + reduce(map(targets, (t) => t.returnType), Type.union, world.varType);
|
| +}
|
| +
|
| +
|
| +class InvokeKey {
|
| + int bareArgs;
|
| + List<String> namedArgs;
|
| + Type returnType;
|
| + bool needsVarCall = false;
|
| +
|
| + InvokeKey(Arguments args) {
|
| + bareArgs = args.bareCount;
|
| + if (bareArgs != args.length) {
|
| + namedArgs = args.getNames();
|
| + }
|
| + }
|
| +
|
| + bool matches(Arguments args) {
|
| + if (namedArgs == null) {
|
| + if (bareArgs != args.length) return false;
|
| + } else {
|
| + if (bareArgs + namedArgs.length != args.length) return false;
|
| + }
|
| + if (bareArgs != args.bareCount) return false;
|
| +
|
| + if (namedArgs == null) return true;
|
| +
|
| + for (int i = 0; i < namedArgs.length; i++) {
|
| + if (namedArgs[i] != args.getName(bareArgs + i)) return false;
|
| + }
|
| + return true;
|
| + }
|
| +
|
| + void addMembers(List<Member> members, CallingContext context, Value target,
|
| + Arguments args) {
|
| + // TODO(jimhug): In checked mode include parameter types to determine
|
| + // need for varCall.
|
| + for (var member in members) {
|
| + // check that this is a "perfect" match - or require a var call
|
| + // TODO(jimhug): Add support of "perfect matches" even with names
|
| + if (!(member.parameters.length == bareArgs && namedArgs == null)) {
|
| + needsVarCall = true;
|
| + }
|
| + // TODO(jimhug): Should create a less specific version of args.
|
| + if (member.canInvoke(context, args)) {
|
| + if (member.isMethod) {
|
| + returnType = MemberSet.unionTypes(returnType, member.returnType);
|
| + member.declaringType.genMethod(member);
|
| + } else {
|
| + needsVarCall = true;
|
| + returnType = world.varType;
|
| + }
|
| + }
|
| + }
|
| + if (returnType == null) {
|
| + // TODO(jimhug): Warning here for no match anywhere in the world?
|
| + returnType = world.varType;
|
| + }
|
| + }
|
| +}
|
|
|