summaryrefslogtreecommitdiffstats
path: root/devtools/client/sourceeditor/tern/infer.js
diff options
context:
space:
mode:
Diffstat (limited to 'devtools/client/sourceeditor/tern/infer.js')
-rwxr-xr-xdevtools/client/sourceeditor/tern/infer.js2119
1 files changed, 2119 insertions, 0 deletions
diff --git a/devtools/client/sourceeditor/tern/infer.js b/devtools/client/sourceeditor/tern/infer.js
new file mode 100755
index 000000000..94f0a9518
--- /dev/null
+++ b/devtools/client/sourceeditor/tern/infer.js
@@ -0,0 +1,2119 @@
+// Main type inference engine
+
+// Walks an AST, building up a graph of abstract values and constraints
+// that cause types to flow from one node to another. Also defines a
+// number of utilities for accessing ASTs and scopes.
+
+// Analysis is done in a context, which is tracked by the dynamically
+// bound cx variable. Use withContext to set the current context.
+
+// For memory-saving reasons, individual types export an interface
+// similar to abstract values (which can hold multiple types), and can
+// thus be used in place abstract values that only ever contain a
+// single type.
+
+(function(root, mod) {
+ if (typeof exports == "object" && typeof module == "object") // CommonJS
+ return mod(exports, require("acorn/acorn"), require("acorn/acorn_loose"), require("acorn/walk"),
+ require("./def"), require("./signal"));
+ if (typeof define == "function" && define.amd) // AMD
+ return define(["exports", "acorn/acorn", "acorn/acorn_loose", "acorn/walk", "./def", "./signal"], mod);
+ mod(root.tern || (root.tern = {}), acorn, acorn, acorn.walk, tern.def, tern.signal); // Plain browser env
+})(this, function(exports, acorn, acorn_loose, walk, def, signal) {
+ "use strict";
+
+ var toString = exports.toString = function(type, maxDepth, parent) {
+ if (!type || type == parent || maxDepth && maxDepth < -3) return "?";
+ return type.toString(maxDepth, parent);
+ };
+
+ // A variant of AVal used for unknown, dead-end values. Also serves
+ // as prototype for AVals, Types, and Constraints because it
+ // implements 'empty' versions of all the methods that the code
+ // expects.
+ var ANull = exports.ANull = signal.mixin({
+ addType: function() {},
+ propagate: function() {},
+ getProp: function() { return ANull; },
+ forAllProps: function() {},
+ hasType: function() { return false; },
+ isEmpty: function() { return true; },
+ getFunctionType: function() {},
+ getObjType: function() {},
+ getSymbolType: function() {},
+ getType: function() {},
+ gatherProperties: function() {},
+ propagatesTo: function() {},
+ typeHint: function() {},
+ propHint: function() {},
+ toString: function() { return "?"; }
+ });
+
+ function extend(proto, props) {
+ var obj = Object.create(proto);
+ if (props) for (var prop in props) obj[prop] = props[prop];
+ return obj;
+ }
+
+ // ABSTRACT VALUES
+
+ var WG_DEFAULT = 100, WG_NEW_INSTANCE = 90, WG_MADEUP_PROTO = 10,
+ WG_MULTI_MEMBER = 6, WG_CATCH_ERROR = 6,
+ WG_PHANTOM_OBJ = 1,
+ WG_GLOBAL_THIS = 90, WG_SPECULATIVE_THIS = 2, WG_SPECULATIVE_PROTO_THIS = 4;
+
+ var AVal = exports.AVal = function() {
+ this.types = [];
+ this.forward = null;
+ this.maxWeight = 0;
+ };
+ AVal.prototype = extend(ANull, {
+ addType: function(type, weight) {
+ weight = weight || WG_DEFAULT;
+ if (this.maxWeight < weight) {
+ this.maxWeight = weight;
+ if (this.types.length == 1 && this.types[0] == type) return;
+ this.types.length = 0;
+ } else if (this.maxWeight > weight || this.types.indexOf(type) > -1) {
+ return;
+ }
+
+ this.signal("addType", type);
+ this.types.push(type);
+ var forward = this.forward;
+ if (forward) withWorklist(function(add) {
+ for (var i = 0; i < forward.length; ++i) add(type, forward[i], weight);
+ });
+ },
+
+ propagate: function(target, weight) {
+ if (target == ANull || (target instanceof Type && this.forward && this.forward.length > 2)) return;
+ if (weight && weight != WG_DEFAULT) target = new Muffle(target, weight);
+ (this.forward || (this.forward = [])).push(target);
+ var types = this.types;
+ if (types.length) withWorklist(function(add) {
+ for (var i = 0; i < types.length; ++i) add(types[i], target, weight);
+ });
+ },
+
+ getProp: function(prop) {
+ if (prop == "__proto__" || prop == "✖") return ANull;
+ var found = (this.props || (this.props = Object.create(null)))[prop];
+ if (!found) {
+ found = this.props[prop] = new AVal;
+ this.propagate(new GetProp(prop, found));
+ }
+ return found;
+ },
+
+ forAllProps: function(c) {
+ this.propagate(new ForAllProps(c));
+ },
+
+ hasType: function(type) {
+ return this.types.indexOf(type) > -1;
+ },
+ isEmpty: function() { return this.types.length === 0; },
+ getFunctionType: function() {
+ for (var i = this.types.length - 1; i >= 0; --i)
+ if (this.types[i] instanceof Fn) return this.types[i];
+ },
+ getObjType: function() {
+ var seen = null;
+ for (var i = this.types.length - 1; i >= 0; --i) {
+ var type = this.types[i];
+ if (!(type instanceof Obj)) continue;
+ if (type.name) return type;
+ if (!seen) seen = type;
+ }
+ return seen;
+ },
+
+ getSymbolType: function() {
+ for (var i = this.types.length - 1; i >= 0; --i)
+ if (this.types[i] instanceof Sym) return this.types[i]
+ },
+
+ getType: function(guess) {
+ if (this.types.length === 0 && guess !== false) return this.makeupType();
+ if (this.types.length === 1) return this.types[0];
+ return canonicalType(this.types);
+ },
+
+ toString: function(maxDepth, parent) {
+ if (this.types.length == 0) return toString(this.makeupType(), maxDepth, parent);
+ if (this.types.length == 1) return toString(this.types[0], maxDepth, parent);
+ var simplified = simplifyTypes(this.types);
+ if (simplified.length > 2) return "?";
+ return simplified.map(function(tp) { return toString(tp, maxDepth, parent); }).join("|");
+ },
+
+ makeupPropType: function(obj) {
+ var propName = this.propertyName;
+
+ var protoProp = obj.proto && obj.proto.hasProp(propName);
+ if (protoProp) {
+ var fromProto = protoProp.getType();
+ if (fromProto) return fromProto;
+ }
+
+ if (propName != "<i>") {
+ var computedProp = obj.hasProp("<i>");
+ if (computedProp) return computedProp.getType();
+ } else if (obj.props["<i>"] != this) {
+ for (var prop in obj.props) {
+ var val = obj.props[prop];
+ if (!val.isEmpty()) return val.getType();
+ }
+ }
+ },
+
+ makeupType: function() {
+ var computed = this.propertyOf && this.makeupPropType(this.propertyOf);
+ if (computed) return computed;
+
+ if (!this.forward) return null;
+ for (var i = this.forward.length - 1; i >= 0; --i) {
+ var hint = this.forward[i].typeHint();
+ if (hint && !hint.isEmpty()) {guessing = true; return hint;}
+ }
+
+ var props = Object.create(null), foundProp = null;
+ for (var i = 0; i < this.forward.length; ++i) {
+ var prop = this.forward[i].propHint();
+ if (prop && prop != "length" && prop != "<i>" && prop != "✖" && prop != cx.completingProperty) {
+ props[prop] = true;
+ foundProp = prop;
+ }
+ }
+ if (!foundProp) return null;
+
+ var objs = objsWithProp(foundProp);
+ if (objs) {
+ var matches = [];
+ search: for (var i = 0; i < objs.length; ++i) {
+ var obj = objs[i];
+ for (var prop in props) if (!obj.hasProp(prop)) continue search;
+ if (obj.hasCtor) obj = getInstance(obj);
+ matches.push(obj);
+ }
+ var canon = canonicalType(matches);
+ if (canon) {guessing = true; return canon;}
+ }
+ },
+
+ typeHint: function() { return this.types.length ? this.getType() : null; },
+ propagatesTo: function() { return this; },
+
+ gatherProperties: function(f, depth) {
+ for (var i = 0; i < this.types.length; ++i)
+ this.types[i].gatherProperties(f, depth);
+ },
+
+ guessProperties: function(f) {
+ if (this.forward) for (var i = 0; i < this.forward.length; ++i) {
+ var prop = this.forward[i].propHint();
+ if (prop) f(prop, null, 0);
+ }
+ var guessed = this.makeupType();
+ if (guessed) guessed.gatherProperties(f);
+ }
+ });
+
+ function similarAVal(a, b, depth) {
+ var typeA = a.getType(false), typeB = b.getType(false);
+ if (!typeA || !typeB) return true;
+ return similarType(typeA, typeB, depth);
+ }
+
+ function similarType(a, b, depth) {
+ if (!a || depth >= 5) return b;
+ if (!a || a == b) return a;
+ if (!b) return a;
+ if (a.constructor != b.constructor) return false;
+ if (a.constructor == Arr) {
+ var innerA = a.getProp("<i>").getType(false);
+ if (!innerA) return b;
+ var innerB = b.getProp("<i>").getType(false);
+ if (!innerB || similarType(innerA, innerB, depth + 1)) return b;
+ } else if (a.constructor == Obj) {
+ var propsA = 0, propsB = 0, same = 0;
+ for (var prop in a.props) {
+ propsA++;
+ if (prop in b.props && similarAVal(a.props[prop], b.props[prop], depth + 1))
+ same++;
+ }
+ for (var prop in b.props) propsB++;
+ if (propsA && propsB && same < Math.max(propsA, propsB) / 2) return false;
+ return propsA > propsB ? a : b;
+ } else if (a.constructor == Fn) {
+ if (a.args.length != b.args.length ||
+ !a.args.every(function(tp, i) { return similarAVal(tp, b.args[i], depth + 1); }) ||
+ !similarAVal(a.retval, b.retval, depth + 1) || !similarAVal(a.self, b.self, depth + 1))
+ return false;
+ return a;
+ } else {
+ return false;
+ }
+ }
+
+ var simplifyTypes = exports.simplifyTypes = function(types) {
+ var found = [];
+ outer: for (var i = 0; i < types.length; ++i) {
+ var tp = types[i];
+ for (var j = 0; j < found.length; j++) {
+ var similar = similarType(tp, found[j], 0);
+ if (similar) {
+ found[j] = similar;
+ continue outer;
+ }
+ }
+ found.push(tp);
+ }
+ return found;
+ };
+
+ function canonicalType(types) {
+ var arrays = 0, fns = 0, objs = 0, prim = null;
+ for (var i = 0; i < types.length; ++i) {
+ var tp = types[i];
+ if (tp instanceof Arr) ++arrays;
+ else if (tp instanceof Fn) ++fns;
+ else if (tp instanceof Obj) ++objs;
+ else if (tp instanceof Prim) {
+ if (prim && tp.name != prim.name) return null;
+ prim = tp;
+ }
+ }
+ var kinds = (arrays && 1) + (fns && 1) + (objs && 1) + (prim && 1);
+ if (kinds > 1) return null;
+ if (prim) return prim;
+
+ var maxScore = 0, maxTp = null;
+ for (var i = 0; i < types.length; ++i) {
+ var tp = types[i], score = 0;
+ if (arrays) {
+ score = tp.getProp("<i>").isEmpty() ? 1 : 2;
+ } else if (fns) {
+ score = 1;
+ for (var j = 0; j < tp.args.length; ++j) if (!tp.args[j].isEmpty()) ++score;
+ if (!tp.retval.isEmpty()) ++score;
+ } else if (objs) {
+ score = tp.name ? 100 : 2;
+ }
+ if (score >= maxScore) { maxScore = score; maxTp = tp; }
+ }
+ return maxTp;
+ }
+
+ // PROPAGATION STRATEGIES
+
+ var constraint = exports.constraint = function(methods) {
+ var ctor = function() {
+ this.origin = cx.curOrigin;
+ this.construct.apply(this, arguments);
+ };
+ ctor.prototype = Object.create(ANull);
+ for (var m in methods) if (methods.hasOwnProperty(m)) ctor.prototype[m] = methods[m];
+ return ctor;
+ };
+
+ var GetProp = constraint({
+ construct: function(prop, target) {
+ this.prop = prop; this.target = target;
+ },
+ addType: function(type, weight) {
+ if (type.getProp)
+ type.getProp(this.prop).propagate(this.target, weight);
+ },
+ propHint: function() { return this.prop; },
+ propagatesTo: function() {
+ if (this.prop == "<i>" || !/[^\w_]/.test(this.prop))
+ return {target: this.target, pathExt: "." + this.prop};
+ }
+ });
+
+ var DefProp = exports.PropHasSubset = exports.DefProp = constraint({
+ construct: function(prop, type, originNode) {
+ this.prop = prop; this.type = type; this.originNode = originNode;
+ },
+ addType: function(type, weight) {
+ if (!(type instanceof Obj)) return;
+ var prop = type.defProp(this.prop, this.originNode);
+ if (!prop.origin) prop.origin = this.origin;
+ this.type.propagate(prop, weight);
+ },
+ propHint: function() { return this.prop; }
+ });
+
+ var ForAllProps = constraint({
+ construct: function(c) { this.c = c; },
+ addType: function(type) {
+ if (!(type instanceof Obj)) return;
+ type.forAllProps(this.c);
+ }
+ });
+
+ function withDisabledComputing(fn, body) {
+ cx.disabledComputing = {fn: fn, prev: cx.disabledComputing};
+ var result = body();
+ cx.disabledComputing = cx.disabledComputing.prev;
+ return result;
+ }
+ var IsCallee = exports.IsCallee = constraint({
+ construct: function(self, args, argNodes, retval) {
+ this.self = self; this.args = args; this.argNodes = argNodes; this.retval = retval;
+ this.disabled = cx.disabledComputing;
+ },
+ addType: function(fn, weight) {
+ if (!(fn instanceof Fn)) return;
+ for (var i = 0; i < this.args.length; ++i) {
+ if (i < fn.args.length) this.args[i].propagate(fn.args[i], weight);
+ if (fn.arguments) this.args[i].propagate(fn.arguments, weight);
+ }
+ this.self.propagate(fn.self, this.self == cx.topScope ? WG_GLOBAL_THIS : weight);
+ var compute = fn.computeRet, result = fn.retval
+ if (compute) for (var d = this.disabled; d; d = d.prev)
+ if (d.fn == fn || fn.originNode && d.fn.originNode == fn.originNode) compute = null;
+ if (compute) {
+ var old = cx.disabledComputing;
+ cx.disabledComputing = this.disabled;
+ result = compute(this.self, this.args, this.argNodes)
+ cx.disabledComputing = old;
+ }
+ maybeIterator(fn, result).propagate(this.retval, weight)
+ },
+ typeHint: function() {
+ var names = [];
+ for (var i = 0; i < this.args.length; ++i) names.push("?");
+ return new Fn(null, this.self, this.args, names, ANull);
+ },
+ propagatesTo: function() {
+ return {target: this.retval, pathExt: ".!ret"};
+ }
+ });
+
+ var HasMethodCall = constraint({
+ construct: function(propName, args, argNodes, retval) {
+ this.propName = propName; this.args = args; this.argNodes = argNodes; this.retval = retval;
+ this.disabled = cx.disabledComputing;
+ },
+ addType: function(obj, weight) {
+ var callee = new IsCallee(obj, this.args, this.argNodes, this.retval);
+ callee.disabled = this.disabled;
+ obj.getProp(this.propName).propagate(callee, weight);
+ },
+ propHint: function() { return this.propName; }
+ });
+
+ var IsCtor = exports.IsCtor = constraint({
+ construct: function(target, noReuse) {
+ this.target = target; this.noReuse = noReuse;
+ },
+ addType: function(f, weight) {
+ if (!(f instanceof Fn)) return;
+ if (cx.parent && !cx.parent.options.reuseInstances) this.noReuse = true;
+ f.getProp("prototype").propagate(new IsProto(this.noReuse ? false : f, this.target), weight);
+ }
+ });
+
+ var getInstance = exports.getInstance = function(obj, ctor) {
+ if (ctor === false) return new Obj(obj);
+
+ if (!ctor) ctor = obj.hasCtor;
+ if (!obj.instances) obj.instances = [];
+ for (var i = 0; i < obj.instances.length; ++i) {
+ var cur = obj.instances[i];
+ if (cur.ctor == ctor) return cur.instance;
+ }
+ var instance = new Obj(obj, ctor && ctor.name);
+ instance.origin = obj.origin;
+ obj.instances.push({ctor: ctor, instance: instance});
+ return instance;
+ };
+
+ var IsProto = exports.IsProto = constraint({
+ construct: function(ctor, target) {
+ this.ctor = ctor; this.target = target;
+ },
+ addType: function(o, _weight) {
+ if (!(o instanceof Obj)) return;
+ if ((this.count = (this.count || 0) + 1) > 8) return;
+ if (o == cx.protos.Array)
+ this.target.addType(new Arr);
+ else
+ this.target.addType(getInstance(o, this.ctor));
+ }
+ });
+
+ var FnPrototype = constraint({
+ construct: function(fn) { this.fn = fn; },
+ addType: function(o, _weight) {
+ if (o instanceof Obj && !o.hasCtor) {
+ o.hasCtor = this.fn;
+ var adder = new SpeculativeThis(o, this.fn);
+ adder.addType(this.fn);
+ o.forAllProps(function(_prop, val, local) {
+ if (local) val.propagate(adder);
+ });
+ }
+ }
+ });
+
+ var IsAdded = constraint({
+ construct: function(other, target) {
+ this.other = other; this.target = target;
+ },
+ addType: function(type, weight) {
+ if (type == cx.str)
+ this.target.addType(cx.str, weight);
+ else if (type == cx.num && this.other.hasType(cx.num))
+ this.target.addType(cx.num, weight);
+ },
+ typeHint: function() { return this.other; }
+ });
+
+ var IfObj = exports.IfObj = constraint({
+ construct: function(target) { this.target = target; },
+ addType: function(t, weight) {
+ if (t instanceof Obj) this.target.addType(t, weight);
+ },
+ propagatesTo: function() { return this.target; }
+ });
+
+ var SpeculativeThis = constraint({
+ construct: function(obj, ctor) { this.obj = obj; this.ctor = ctor; },
+ addType: function(tp) {
+ if (tp instanceof Fn && tp.self)
+ tp.self.addType(getInstance(this.obj, this.ctor), WG_SPECULATIVE_PROTO_THIS);
+ }
+ });
+
+ var HasProto = constraint({
+ construct: function(obj) { this.obj = obj },
+ addType: function(tp) {
+ if (tp instanceof Obj && this.obj.proto == cx.protos.Object)
+ this.obj.replaceProto(tp)
+ }
+ });
+
+ var Muffle = constraint({
+ construct: function(inner, weight) {
+ this.inner = inner; this.weight = weight;
+ },
+ addType: function(tp, weight) {
+ this.inner.addType(tp, Math.min(weight, this.weight));
+ },
+ propagatesTo: function() { return this.inner.propagatesTo(); },
+ typeHint: function() { return this.inner.typeHint(); },
+ propHint: function() { return this.inner.propHint(); }
+ });
+
+ // TYPE OBJECTS
+
+ var Type = exports.Type = function() {};
+ Type.prototype = extend(ANull, {
+ constructor: Type,
+ propagate: function(c, w) { c.addType(this, w); },
+ hasType: function(other) { return other == this; },
+ isEmpty: function() { return false; },
+ typeHint: function() { return this; },
+ getType: function() { return this; }
+ });
+
+ var Prim = exports.Prim = function(proto, name) { this.name = name; this.proto = proto; };
+ Prim.prototype = extend(Type.prototype, {
+ constructor: Prim,
+ toString: function() { return this.name; },
+ getProp: function(prop) {return this.proto.hasProp(prop) || ANull;},
+ gatherProperties: function(f, depth) {
+ if (this.proto) this.proto.gatherProperties(f, depth);
+ }
+ });
+
+ function isInteger(str) {
+ var c0 = str.charCodeAt(0)
+ if (c0 >= 48 && c0 <= 57) return !/\D/.test(str)
+ else return false
+ }
+
+ var Obj = exports.Obj = function(proto, name) {
+ if (!this.props) this.props = Object.create(null);
+ this.proto = proto === true ? cx.protos.Object : proto;
+ if (proto && !name && proto.name && !(this instanceof Fn)) {
+ var match = /^(.*)\.prototype$/.exec(this.proto.name);
+ if (match) name = match[1];
+ }
+ this.name = name;
+ this.maybeProps = null;
+ this.origin = cx.curOrigin;
+ };
+ Obj.prototype = extend(Type.prototype, {
+ constructor: Obj,
+ toString: function(maxDepth) {
+ if (maxDepth == null) maxDepth = 0;
+ if (maxDepth <= 0 && this.name) return this.name;
+ var props = [], etc = false;
+ for (var prop in this.props) if (prop != "<i>") {
+ if (props.length > 5) { etc = true; break; }
+ if (maxDepth)
+ props.push(prop + ": " + toString(this.props[prop], maxDepth - 1, this));
+ else
+ props.push(prop);
+ }
+ props.sort();
+ if (etc) props.push("...");
+ return "{" + props.join(", ") + "}";
+ },
+ hasProp: function(prop, searchProto) {
+ if (isInteger(prop)) prop = this.normalizeIntegerProp(prop)
+ var found = this.props[prop];
+ if (searchProto !== false)
+ for (var p = this.proto; p && !found; p = p.proto) found = p.props[prop];
+ return found;
+ },
+ defProp: function(prop, originNode) {
+ var found = this.hasProp(prop, false);
+ if (found) {
+ if (originNode && !found.originNode) found.originNode = originNode;
+ return found;
+ }
+ if (prop == "__proto__" || prop == "✖") return ANull;
+ if (isInteger(prop)) prop = this.normalizeIntegerProp(prop)
+
+ var av = this.maybeProps && this.maybeProps[prop];
+ if (av) {
+ delete this.maybeProps[prop];
+ this.maybeUnregProtoPropHandler();
+ } else {
+ av = new AVal;
+ av.propertyOf = this;
+ av.propertyName = prop;
+ }
+
+ this.props[prop] = av;
+ av.originNode = originNode;
+ av.origin = cx.curOrigin;
+ this.broadcastProp(prop, av, true);
+ return av;
+ },
+ getProp: function(prop) {
+ var found = this.hasProp(prop, true) || (this.maybeProps && this.maybeProps[prop]);
+ if (found) return found;
+ if (prop == "__proto__" || prop == "✖") return ANull;
+ if (isInteger(prop)) prop = this.normalizeIntegerProp(prop)
+ var av = this.ensureMaybeProps()[prop] = new AVal;
+ av.propertyOf = this;
+ av.propertyName = prop;
+ return av;
+ },
+ normalizeIntegerProp: function(_) { return "<i>" },
+ broadcastProp: function(prop, val, local) {
+ if (local) {
+ this.signal("addProp", prop, val);
+ // If this is a scope, it shouldn't be registered
+ if (!(this instanceof Scope)) registerProp(prop, this);
+ }
+
+ if (this.onNewProp) for (var i = 0; i < this.onNewProp.length; ++i) {
+ var h = this.onNewProp[i];
+ h.onProtoProp ? h.onProtoProp(prop, val, local) : h(prop, val, local);
+ }
+ },
+ onProtoProp: function(prop, val, _local) {
+ var maybe = this.maybeProps && this.maybeProps[prop];
+ if (maybe) {
+ delete this.maybeProps[prop];
+ this.maybeUnregProtoPropHandler();
+ this.proto.getProp(prop).propagate(maybe);
+ }
+ this.broadcastProp(prop, val, false);
+ },
+ replaceProto: function(proto) {
+ if (this.proto && this.maybeProps)
+ this.proto.unregPropHandler(this)
+ this.proto = proto
+ if (this.maybeProps)
+ this.proto.forAllProps(this)
+ },
+ ensureMaybeProps: function() {
+ if (!this.maybeProps) {
+ if (this.proto) this.proto.forAllProps(this);
+ this.maybeProps = Object.create(null);
+ }
+ return this.maybeProps;
+ },
+ removeProp: function(prop) {
+ var av = this.props[prop];
+ delete this.props[prop];
+ this.ensureMaybeProps()[prop] = av;
+ av.types.length = 0;
+ },
+ forAllProps: function(c) {
+ if (!this.onNewProp) {
+ this.onNewProp = [];
+ if (this.proto) this.proto.forAllProps(this);
+ }
+ this.onNewProp.push(c);
+ for (var o = this; o; o = o.proto) for (var prop in o.props) {
+ if (c.onProtoProp)
+ c.onProtoProp(prop, o.props[prop], o == this);
+ else
+ c(prop, o.props[prop], o == this);
+ }
+ },
+ maybeUnregProtoPropHandler: function() {
+ if (this.maybeProps) {
+ for (var _n in this.maybeProps) return;
+ this.maybeProps = null;
+ }
+ if (!this.proto || this.onNewProp && this.onNewProp.length) return;
+ this.proto.unregPropHandler(this);
+ },
+ unregPropHandler: function(handler) {
+ for (var i = 0; i < this.onNewProp.length; ++i)
+ if (this.onNewProp[i] == handler) { this.onNewProp.splice(i, 1); break; }
+ this.maybeUnregProtoPropHandler();
+ },
+ gatherProperties: function(f, depth) {
+ for (var prop in this.props) if (prop != "<i>" && prop.charAt(0) != ":")
+ f(prop, this, depth);
+ if (this.proto) this.proto.gatherProperties(f, depth + 1);
+ },
+ getObjType: function() { return this; }
+ });
+
+ var Fn = exports.Fn = function(name, self, args, argNames, retval, generator) {
+ Obj.call(this, cx.protos.Function, name);
+ this.self = self;
+ this.args = args;
+ this.argNames = argNames;
+ this.retval = retval;
+ this.generator = generator
+ };
+ Fn.prototype = extend(Obj.prototype, {
+ constructor: Fn,
+ toString: function(maxDepth) {
+ if (maxDepth == null) maxDepth = 0;
+ var str = this.generator ? "fn*(" : "fn(";
+ for (var i = 0; i < this.args.length; ++i) {
+ if (i) str += ", ";
+ var name = this.argNames[i];
+ if (name && name != "?") str += name + ": ";
+ str += maxDepth > -3 ? toString(this.args[i], maxDepth - 1, this) : "?";
+ }
+ str += ")";
+ if (!this.retval.isEmpty())
+ str += " -> " + (maxDepth > -3 ? toString(this.retval, maxDepth - 1, this) : "?");
+ return str;
+ },
+ getProp: function(prop) {
+ if (prop == "prototype") {
+ var known = this.hasProp(prop, false);
+ if (!known) {
+ known = this.defProp(prop);
+ var proto = new Obj(true, this.name && this.name + ".prototype");
+ proto.origin = this.origin;
+ known.addType(proto, WG_MADEUP_PROTO);
+ }
+ return known;
+ }
+ return Obj.prototype.getProp.call(this, prop);
+ },
+ defProp: function(prop, originNode) {
+ if (prop == "prototype") {
+ var found = this.hasProp(prop, false);
+ if (found) return found;
+ found = Obj.prototype.defProp.call(this, prop, originNode);
+ found.origin = this.origin;
+ found.propagate(new FnPrototype(this));
+ return found;
+ }
+ return Obj.prototype.defProp.call(this, prop, originNode);
+ },
+ getFunctionType: function() { return this; }
+ });
+
+ var Arr = exports.Arr = function(contentType) {
+ Obj.call(this, cx.protos.Array)
+ var content = this.defProp("<i>")
+ if (Array.isArray(contentType)) {
+ this.tuple = contentType.length
+ for (var i = 0; i < contentType.length; i++) {
+ var prop = this.defProp(String(i))
+ contentType[i].propagate(prop)
+ prop.propagate(content)
+ }
+ } else if (contentType) {
+ this.tuple = 0
+ contentType.propagate(content)
+ }
+ };
+ Arr.prototype = extend(Obj.prototype, {
+ constructor: Arr,
+ toString: function(maxDepth) {
+ if (maxDepth == null) maxDepth = 0
+ if (maxDepth <= -3) return "[?]"
+ var content = ""
+ if (this.tuple) {
+ var similar
+ for (var i = 0; i in this.props; i++) {
+ var type = toString(this.getProp(String(i)), maxDepth - 1, this)
+ if (similar == null)
+ similar = type
+ else if (similar != type)
+ similar = false
+ else
+ similar = type
+ content += (content ? ", " : "") + type
+ }
+ if (similar) content = similar
+ } else {
+ content = toString(this.getProp("<i>"), maxDepth - 1, this)
+ }
+ return "[" + content + "]"
+ },
+ normalizeIntegerProp: function(prop) {
+ if (+prop < this.tuple) return prop
+ else return "<i>"
+ }
+ });
+
+ var Sym = exports.Sym = function(name, originNode) {
+ Prim.call(this, cx.protos.Symbol, "Symbol")
+ this.symName = name
+ this.originNode = originNode
+ }
+ Sym.prototype = extend(Prim.prototype, {
+ constructor: Sym,
+ asPropName: function() { return ":" + this.symName },
+ getSymbolType: function() { return this }
+ })
+
+ exports.getSymbol = function(name, originNode) {
+ var cleanName = name.replace(/[^\w$\.]/g, "_")
+ var known = cx.symbols[cleanName]
+ if (known) {
+ if (originNode && !known.originNode) known.originNode = originNode
+ return known
+ }
+ return cx.symbols[cleanName] = new Sym(cleanName, originNode)
+ }
+
+ // THE PROPERTY REGISTRY
+
+ function registerProp(prop, obj) {
+ var data = cx.props[prop] || (cx.props[prop] = []);
+ data.push(obj);
+ }
+
+ function objsWithProp(prop) {
+ return cx.props[prop];
+ }
+
+ // INFERENCE CONTEXT
+
+ exports.Context = function(defs, parent) {
+ this.parent = parent;
+ this.props = Object.create(null);
+ this.protos = Object.create(null);
+ this.origins = [];
+ this.curOrigin = "ecma5";
+ this.paths = Object.create(null);
+ this.definitions = Object.create(null);
+ this.purgeGen = 0;
+ this.workList = null;
+ this.disabledComputing = null;
+ this.curSuperCtor = this.curSuper = null;
+ this.symbols = Object.create(null)
+
+ exports.withContext(this, function() {
+ cx.protos.Object = new Obj(null, "Object.prototype");
+ cx.topScope = new Scope();
+ cx.topScope.name = "<top>";
+ cx.protos.Array = new Obj(true, "Array.prototype");
+ cx.protos.Function = new Fn("Function.prototype", ANull, [], [], ANull);
+ cx.protos.Function.proto = cx.protos.Object;
+ cx.protos.RegExp = new Obj(true, "RegExp.prototype");
+ cx.protos.String = new Obj(true, "String.prototype");
+ cx.protos.Number = new Obj(true, "Number.prototype");
+ cx.protos.Boolean = new Obj(true, "Boolean.prototype");
+ cx.protos.Symbol = new Obj(true, "Symbol.prototype");
+ cx.str = new Prim(cx.protos.String, "string");
+ cx.bool = new Prim(cx.protos.Boolean, "bool");
+ cx.num = new Prim(cx.protos.Number, "number");
+ cx.curOrigin = null;
+
+ if (defs) for (var i = 0; i < defs.length; ++i)
+ def.load(defs[i]);
+ });
+ };
+
+ exports.Context.prototype.startAnalysis = function() {
+ this.disabledComputing = this.workList = this.curSuperCtor = this.curSuper = null;
+ };
+
+ var cx = null;
+ exports.cx = function() { return cx; };
+
+ exports.withContext = function(context, f) {
+ var old = cx;
+ cx = context;
+ try { return f(); }
+ finally { cx = old; }
+ };
+
+ exports.TimedOut = function() {
+ this.message = "Timed out";
+ this.stack = (new Error()).stack;
+ };
+ exports.TimedOut.prototype = Object.create(Error.prototype);
+ exports.TimedOut.prototype.name = "infer.TimedOut";
+
+ var timeout;
+ exports.withTimeout = function(ms, f) {
+ var end = +new Date + ms;
+ var oldEnd = timeout;
+ if (oldEnd && oldEnd < end) return f();
+ timeout = end;
+ try { return f(); }
+ finally { timeout = oldEnd; }
+ };
+
+ exports.addOrigin = function(origin) {
+ if (cx.origins.indexOf(origin) < 0) cx.origins.push(origin);
+ };
+
+ var baseMaxWorkDepth = 20, reduceMaxWorkDepth = 0.0001;
+ function withWorklist(f) {
+ if (cx.workList) return f(cx.workList);
+
+ var list = [], depth = 0;
+ var add = cx.workList = function(type, target, weight) {
+ if (depth < baseMaxWorkDepth - reduceMaxWorkDepth * list.length)
+ list.push(type, target, weight, depth);
+ };
+ var ret = f(add);
+ for (var i = 0; i < list.length; i += 4) {
+ if (timeout && +new Date >= timeout)
+ throw new exports.TimedOut();
+ depth = list[i + 3] + 1;
+ list[i + 1].addType(list[i], list[i + 2]);
+ }
+ cx.workList = null;
+ return ret;
+ }
+
+ function withSuper(ctor, obj, f) {
+ var oldCtor = cx.curSuperCtor, oldObj = cx.curSuper
+ cx.curSuperCtor = ctor; cx.curSuper = obj
+ var result = f()
+ cx.curSuperCtor = oldCtor; cx.curSuper = oldObj
+ return result
+ }
+
+ // SCOPES
+
+ var Scope = exports.Scope = function(prev, originNode, isBlock) {
+ Obj.call(this, prev || true);
+ this.prev = prev;
+ this.originNode = originNode
+ this.isBlock = !!isBlock
+ };
+ Scope.prototype = extend(Obj.prototype, {
+ constructor: Scope,
+ defVar: function(name, originNode) {
+ for (var s = this; ; s = s.proto) {
+ var found = s.props[name];
+ if (found) return found;
+ if (!s.prev) return s.defProp(name, originNode);
+ }
+ }
+ });
+
+ function functionScope(scope) {
+ while (scope.isBlock) scope = scope.prev
+ return scope
+ }
+
+
+ // RETVAL COMPUTATION HEURISTICS
+
+ function maybeInstantiate(scope, score) {
+ var fn = functionScope(scope).fnType
+ if (fn) fn.instantiateScore = (fn.instantiateScore || 0) + score;
+ }
+
+ var NotSmaller = {};
+ function nodeSmallerThan(node, n) {
+ try {
+ walk.simple(node, {Expression: function() { if (--n <= 0) throw NotSmaller; }});
+ return true;
+ } catch(e) {
+ if (e == NotSmaller) return false;
+ throw e;
+ }
+ }
+
+ function maybeTagAsInstantiated(node, fn) {
+ var score = fn.instantiateScore;
+ if (!cx.disabledComputing && score && fn.args.length && nodeSmallerThan(node, score * 5)) {
+ maybeInstantiate(functionScope(fn.originNode.scope.prev), score / 2);
+ setFunctionInstantiated(node, fn);
+ return true;
+ } else {
+ fn.instantiateScore = null;
+ }
+ }
+
+ function setFunctionInstantiated(node, fn) {
+ // Disconnect the arg avals, so that we can add info to them without side effects
+ for (var i = 0; i < fn.args.length; ++i) fn.args[i] = new AVal;
+ fn.self = new AVal;
+ fn.computeRet = function(self, args) {
+ // Prevent recursion
+ return withDisabledComputing(fn, function() {
+ var oldOrigin = cx.curOrigin;
+ cx.curOrigin = fn.origin;
+ var scope = node.scope
+ var scopeCopy = new Scope(scope.prev, scope.originNode);
+ for (var v in scope.props) {
+ var local = scopeCopy.defProp(v, scope.props[v].originNode);
+ for (var i = 0; i < args.length; ++i) if (fn.argNames[i] == v && i < args.length)
+ args[i].propagate(local);
+ }
+ var argNames = fn.argNames.length != args.length ? fn.argNames.slice(0, args.length) : fn.argNames;
+ while (argNames.length < args.length) argNames.push("?");
+ scopeCopy.fnType = new Fn(fn.name, self, args, argNames, ANull, fn.generator);
+ scopeCopy.fnType.originNode = fn.originNode;
+ if (fn.arguments) {
+ var argset = scopeCopy.fnType.arguments = new AVal;
+ scopeCopy.defProp("arguments").addType(new Arr(argset));
+ for (var i = 0; i < args.length; ++i) args[i].propagate(argset);
+ }
+ node.scope = scopeCopy;
+ walk.recursive(node.body, scopeCopy, null, scopeGatherer);
+ walk.recursive(node.body, scopeCopy, null, inferWrapper);
+ cx.curOrigin = oldOrigin;
+ return scopeCopy.fnType.retval;
+ });
+ };
+ }
+
+ function maybeTagAsGeneric(fn) {
+ var target = fn.retval;
+ if (target == ANull) return;
+ var targetInner, asArray;
+ if (!target.isEmpty() && (targetInner = target.getType()) instanceof Arr)
+ target = asArray = targetInner.getProp("<i>");
+
+ function explore(aval, path, depth) {
+ if (depth > 3 || !aval.forward) return;
+ for (var i = 0; i < aval.forward.length; ++i) {
+ var prop = aval.forward[i].propagatesTo();
+ if (!prop) continue;
+ var newPath = path, dest;
+ if (prop instanceof AVal) {
+ dest = prop;
+ } else if (prop.target instanceof AVal) {
+ newPath += prop.pathExt;
+ dest = prop.target;
+ } else continue;
+ if (dest == target) return newPath;
+ var found = explore(dest, newPath, depth + 1);
+ if (found) return found;
+ }
+ }
+
+ var foundPath = explore(fn.self, "!this", 0);
+ for (var i = 0; !foundPath && i < fn.args.length; ++i)
+ foundPath = explore(fn.args[i], "!" + i, 0);
+
+ if (foundPath) {
+ if (asArray) foundPath = "[" + foundPath + "]";
+ var p = new def.TypeParser(foundPath);
+ var parsed = p.parseType(true);
+ fn.computeRet = parsed.apply ? parsed : function() { return parsed; };
+ fn.computeRetSource = foundPath;
+ return true;
+ }
+ }
+
+ // SCOPE GATHERING PASS
+
+ function addVar(scope, nameNode) {
+ return scope.defProp(nameNode.name, nameNode);
+ }
+ function patternName(node) {
+ if (node.type == "Identifier") return node.name
+ if (node.type == "AssignmentPattern") return patternName(node.left)
+ if (node.type == "ObjectPattern") return "{" + node.properties.map(function(e) { return patternName(e.value) }).join(", ") + "}"
+ if (node.type == "ArrayPattern") return "[" + node.elements.map(patternName).join(", ") + "]"
+ if (node.type == "RestElement") return "..." + patternName(node.argument)
+ return "_"
+ }
+
+ function isBlockScopedDecl(node) {
+ return node.type == "VariableDeclaration" && node.kind != "var" ||
+ node.type == "FunctionDeclaration" ||
+ node.type == "ClassDeclaration";
+ }
+
+ function patternScopes(inner, outer) {
+ return {inner: inner, outer: outer || inner}
+ }
+
+ var scopeGatherer = exports.scopeGatherer = walk.make({
+ VariablePattern: function(node, scopes) {
+ if (scopes.inner) addVar(scopes.inner, node)
+ },
+ AssignmentPattern: function(node, scopes, c) {
+ c(node.left, scopes, "Pattern")
+ c(node.right, scopes.outer, "Expression")
+ },
+ AssignmentExpression: function(node, scope, c) {
+ if (node.left.type == "MemberExpression")
+ c(node.left, scope, "Expression")
+ else
+ c(node.left, patternScopes(false, scope), "Pattern")
+ c(node.right, scope, "Expression")
+ },
+ Function: function(node, scope, c) {
+ var inner = node.scope = new Scope(scope, node)
+ var argVals = [], argNames = []
+ for (var i = 0; i < node.params.length; ++i) {
+ var param = node.params[i]
+ argNames.push(patternName(param))
+ if (param.type == "Identifier") {
+ argVals.push(addVar(inner, param))
+ } else {
+ var arg = new AVal
+ argVals.push(arg)
+ arg.originNode = param
+ c(param, patternScopes(inner), "Pattern")
+ }
+ }
+ inner.fnType = new Fn(node.id && node.id.name, new AVal, argVals, argNames, ANull, node.generator)
+ inner.fnType.originNode = node;
+ if (node.id) {
+ var decl = node.type == "FunctionDeclaration";
+ addVar(decl ? scope : inner, node.id);
+ }
+ c(node.body, inner, node.expression ? "Expression" : "Statement");
+ },
+ BlockStatement: function(node, scope, c) {
+ if (!node.scope && node.body.some(isBlockScopedDecl))
+ scope = node.scope = new Scope(scope, node, true)
+ walk.base.BlockStatement(node, scope, c)
+ },
+ TryStatement: function(node, scope, c) {
+ c(node.block, scope, "Statement");
+ if (node.handler) {
+ if (node.handler.param.type == "Identifier") {
+ var v = addVar(scope, node.handler.param);
+ c(node.handler.body, scope, "Statement");
+ var e5 = cx.definitions.ecma5;
+ if (e5 && v.isEmpty()) getInstance(e5["Error.prototype"]).propagate(v, WG_CATCH_ERROR);
+ } else {
+ c(node.handler.param, patternScopes(scope), "Pattern")
+ }
+ }
+ if (node.finalizer) c(node.finalizer, scope, "Statement");
+ },
+ VariableDeclaration: function(node, scope, c) {
+ var targetScope = node.kind == "var" ? functionScope(scope) : scope
+ for (var i = 0; i < node.declarations.length; ++i) {
+ var decl = node.declarations[i];
+ c(decl.id, patternScopes(targetScope, scope), "Pattern")
+ if (decl.init) c(decl.init, scope, "Expression");
+ }
+ },
+ ClassDeclaration: function(node, scope, c) {
+ addVar(scope, node.id)
+ if (node.superClass) c(node.superClass, scope, "Expression")
+ for (var i = 0; i < node.body.body.length; i++)
+ c(node.body.body[i], scope)
+ },
+ ForInStatement: function(node, scope, c) {
+ if (!node.scope && isBlockScopedDecl(node.left))
+ scope = node.scope = new Scope(scope, node, true)
+ walk.base.ForInStatement(node, scope, c)
+ },
+ ForStatement: function(node, scope, c) {
+ if (!node.scope && node.init && isBlockScopedDecl(node.init))
+ scope = node.scope = new Scope(scope, node, true)
+ walk.base.ForStatement(node, scope, c)
+ },
+ ImportDeclaration: function(node, scope) {
+ for (var i = 0; i < node.specifiers.length; i++)
+ addVar(scope, node.specifiers[i].local)
+ }
+ });
+ scopeGatherer.ForOfStatement = scopeGatherer.ForInStatement
+
+ // CONSTRAINT GATHERING PASS
+
+ var propName = exports.propName = function(node, inferInScope) {
+ var key = node.property || node.key;
+ if (!node.computed && key.type == "Identifier") return key.name;
+ if (key.type == "Literal") {
+ if (typeof key.value == "string") return key.value
+ if (typeof key.value == "number") return String(key.value)
+ }
+ if (inferInScope) {
+ var symName = symbolName(infer(key, inferInScope))
+ if (symName) return node.propName = symName
+ } else if (node.propName) {
+ return node.propName
+ }
+ return "<i>";
+ }
+ function symbolName(val) {
+ var sym = val.getSymbolType()
+ if (sym) return sym.asPropName()
+ }
+
+ function unopResultType(op) {
+ switch (op) {
+ case "+": case "-": case "~": return cx.num;
+ case "!": return cx.bool;
+ case "typeof": return cx.str;
+ case "void": case "delete": return ANull;
+ }
+ }
+ function binopIsBoolean(op) {
+ switch (op) {
+ case "==": case "!=": case "===": case "!==": case "<": case ">": case ">=": case "<=":
+ case "in": case "instanceof": return true;
+ }
+ }
+ function literalType(node) {
+ if (node.regex) return getInstance(cx.protos.RegExp);
+ switch (typeof node.value) {
+ case "boolean": return cx.bool;
+ case "number": return cx.num;
+ case "string": return cx.str;
+ case "object":
+ case "function":
+ if (!node.value) return ANull;
+ return getInstance(cx.protos.RegExp);
+ }
+ }
+
+ function join(a, b) {
+ if (a == b || b == ANull) return a
+ if (a == ANull) return b
+ var joined = new AVal
+ a.propagate(joined)
+ b.propagate(joined)
+ return joined
+ }
+
+ function connectParams(node, scope) {
+ for (var i = 0; i < node.params.length; i++) {
+ var param = node.params[i]
+ if (param.type == "Identifier") continue
+ connectPattern(param, scope, node.scope.fnType.args[i])
+ }
+ }
+
+ function ensureVar(node, scope) {
+ return scope.hasProp(node.name) || cx.topScope.defProp(node.name, node)
+ }
+
+ var inferPatternVisitor = exports.inferPatternVisitor = {
+ Identifier: function(node, scope, source) {
+ source.propagate(ensureVar(node, scope))
+ },
+ MemberExpression: function(node, scope, source) {
+ var obj = infer(node.object, scope)
+ var pName = propName(node, scope)
+ obj.propagate(new DefProp(pName, source, node.property))
+ },
+ RestElement: function(node, scope, source) {
+ connectPattern(node.argument, scope, new Arr(source))
+ },
+ ObjectPattern: function(node, scope, source) {
+ for (var i = 0; i < node.properties.length; ++i) {
+ var prop = node.properties[i]
+ connectPattern(prop.value, scope, source.getProp(prop.key.name))
+ }
+ },
+ ArrayPattern: function(node, scope, source) {
+ for (var i = 0; i < node.elements.length; i++)
+ if (node.elements[i])
+ connectPattern(node.elements[i], scope, source.getProp(String(i)))
+ },
+ AssignmentPattern: function(node, scope, source) {
+ connectPattern(node.left, scope, join(source, infer(node.right, scope)))
+ }
+ }
+
+ function connectPattern(node, scope, source) {
+ var connecter = inferPatternVisitor[node.type]
+ if (connecter) connecter(node, scope, source)
+ }
+
+ function getThis(scope) {
+ var fnScope = functionScope(scope)
+ return fnScope.fnType ? fnScope.fnType.self : fnScope
+ }
+
+ function maybeAddPhantomObj(obj) {
+ if (!obj.isEmpty() || !obj.propertyOf) return
+ obj.propertyOf.getProp(obj.propertyName).addType(new Obj, WG_PHANTOM_OBJ)
+ maybeAddPhantomObj(obj.propertyOf)
+ }
+
+ function inferClass(node, scope, name) {
+ if (!name && node.id) name = node.id.name
+
+ var sup = cx.protos.Object, supCtor, delayed
+ if (node.superClass) {
+ if (node.superClass.type == "Literal" && node.superClass.value == null) {
+ sup = null
+ } else {
+ var supVal = infer(node.superClass, scope), supProto
+ supCtor = supVal.getFunctionType()
+ if (supCtor && (supProto = supCtor.getProp("prototype").getObjType())) {
+ sup = supProto
+ } else {
+ supCtor = supVal
+ delayed = supVal.getProp("prototype")
+ }
+ }
+ }
+ var proto = new Obj(sup, name && name + ".prototype")
+ if (delayed) delayed.propagate(new HasProto(proto))
+
+ return withSuper(supCtor, delayed || sup, function() {
+ var ctor, body = node.body.body
+ for (var i = 0; i < body.length; i++)
+ if (body[i].kind == "constructor") ctor = body[i].value
+ var fn = node.objType = ctor ? infer(ctor, scope) : new Fn(name, ANull, [], null, ANull)
+ fn.originNode = node.id || ctor || node
+
+ var inst = getInstance(proto, fn)
+ fn.self.addType(inst)
+ fn.defProp("prototype", node).addType(proto)
+ for (var i = 0; i < body.length; i++) {
+ var method = body[i], target
+ if (method.kind == "constructor") continue
+ var pName = propName(method, scope)
+ if (pName == "<i>" || method.kind == "set") {
+ target = ANull
+ } else {
+ target = (method.static ? fn : proto).defProp(pName, method.key)
+ target.initializer = true
+ if (method.kind == "get") target = new IsCallee(inst, [], null, target)
+ }
+ infer(method.value, scope, target)
+ var methodFn = target.getFunctionType()
+ if (methodFn) methodFn.self.addType(inst)
+ }
+ return fn
+ })
+ }
+
+ function arrayLiteralType(elements, scope, inner) {
+ var tuple = elements.length > 1 && elements.length < 6
+ if (tuple) {
+ var homogenous = true, litType
+ for (var i = 0; i < elements.length; i++) {
+ var elt = elements[i]
+ if (!elt)
+ tuple = false
+ else if (elt.type != "Literal" || (litType && litType != typeof elt.value))
+ homogenous = false
+ else
+ litType = typeof elt.value
+ }
+ if (homogenous) tuple = false
+ }
+
+ if (tuple) {
+ var types = []
+ for (var i = 0; i < elements.length; ++i)
+ types.push(inner(elements[i], scope))
+ return new Arr(types)
+ } else if (elements.length < 2) {
+ return new Arr(elements[0] && inner(elements[0], scope))
+ } else {
+ var eltVal = new AVal
+ for (var i = 0; i < elements.length; i++)
+ if (elements[i]) inner(elements[i], scope).propagate(eltVal)
+ return new Arr(eltVal)
+ }
+ }
+
+ function ret(f) {
+ return function(node, scope, out, name) {
+ var r = f(node, scope, name);
+ if (out) r.propagate(out);
+ return r;
+ };
+ }
+ function fill(f) {
+ return function(node, scope, out, name) {
+ if (!out) out = new AVal;
+ f(node, scope, out, name);
+ return out;
+ };
+ }
+
+ var inferExprVisitor = exports.inferExprVisitor = {
+ ArrayExpression: ret(function(node, scope) {
+ return arrayLiteralType(node.elements, scope, infer)
+ }),
+ ObjectExpression: ret(function(node, scope, name) {
+ var proto = true, waitForProto
+ for (var i = 0; i < node.properties.length; ++i) {
+ var prop = node.properties[i]
+ if (prop.key.name == "__proto__") {
+ if (prop.value.type == "Literal" && prop.value.value == null) {
+ proto = null
+ } else {
+ var protoVal = infer(prop.value, scope), known = protoVal.getObjType()
+ if (known) proto = known
+ else waitForProto = protoVal
+ }
+ }
+ }
+
+ var obj = node.objType = new Obj(proto, name);
+ if (waitForProto) waitForProto.propagate(new HasProto(obj))
+ obj.originNode = node;
+
+ withSuper(null, waitForProto || proto, function() {
+ for (var i = 0; i < node.properties.length; ++i) {
+ var prop = node.properties[i], key = prop.key;
+ if (prop.value.name == "✖" || prop.key.name == "__proto__") continue;
+
+ var name = propName(prop, scope), target
+ if (name == "<i>" || prop.kind == "set") {
+ target = ANull;
+ } else {
+ var val = target = obj.defProp(name, key);
+ val.initializer = true;
+ if (prop.kind == "get")
+ target = new IsCallee(obj, [], null, val);
+ }
+ infer(prop.value, scope, target, name);
+ if (prop.value.type == "FunctionExpression")
+ prop.value.scope.fnType.self.addType(obj, WG_SPECULATIVE_THIS);
+ }
+ })
+ return obj;
+ }),
+ FunctionExpression: ret(function(node, scope, name) {
+ var inner = node.scope, fn = inner.fnType;
+ if (name && !fn.name) fn.name = name;
+ connectParams(node, inner)
+ if (node.expression)
+ infer(node.body, inner, inner.fnType.retval = new AVal)
+ else
+ walk.recursive(node.body, inner, null, inferWrapper, "Statement")
+ if (node.type == "ArrowFunctionExpression") {
+ getThis(scope).propagate(fn.self)
+ fn.self = ANull
+ }
+ maybeTagAsInstantiated(node, fn) || maybeTagAsGeneric(fn);
+ if (node.id) inner.getProp(node.id.name).addType(fn);
+ return fn;
+ }),
+ ClassExpression: ret(inferClass),
+ SequenceExpression: ret(function(node, scope) {
+ for (var i = 0, l = node.expressions.length - 1; i < l; ++i)
+ infer(node.expressions[i], scope, ANull);
+ return infer(node.expressions[l], scope);
+ }),
+ UnaryExpression: ret(function(node, scope) {
+ infer(node.argument, scope, ANull);
+ return unopResultType(node.operator);
+ }),
+ UpdateExpression: ret(function(node, scope) {
+ infer(node.argument, scope, ANull);
+ return cx.num;
+ }),
+ BinaryExpression: ret(function(node, scope) {
+ if (node.operator == "+") {
+ var lhs = infer(node.left, scope);
+ var rhs = infer(node.right, scope);
+ if (lhs.hasType(cx.str) || rhs.hasType(cx.str)) return cx.str;
+ if (lhs.hasType(cx.num) && rhs.hasType(cx.num)) return cx.num;
+ var result = new AVal;
+ lhs.propagate(new IsAdded(rhs, result));
+ rhs.propagate(new IsAdded(lhs, result));
+ return result;
+ } else {
+ infer(node.left, scope, ANull);
+ infer(node.right, scope, ANull);
+ return binopIsBoolean(node.operator) ? cx.bool : cx.num;
+ }
+ }),
+ AssignmentExpression: ret(function(node, scope, name) {
+ var rhs, pName;
+ if (node.left.type == "MemberExpression") {
+ pName = propName(node.left, scope)
+ if (!name)
+ name = node.left.object.type == "Identifier" ? node.left.object.name + "." + pName : pName
+ } else if (!name && node.left.type == "Identifier") {
+ name = node.left.name
+ }
+
+ if (node.operator && node.operator != "=" && node.operator != "+=") {
+ infer(node.right, scope, ANull);
+ rhs = cx.num;
+ } else {
+ rhs = infer(node.right, scope, null, name);
+ }
+
+ if (node.left.type == "MemberExpression") {
+ var obj = infer(node.left.object, scope);
+ if (pName == "prototype") maybeInstantiate(scope, 20);
+ if (pName == "<i>") {
+ // This is a hack to recognize for/in loops that copy
+ // properties, and do the copying ourselves, insofar as we
+ // manage, because such loops tend to be relevant for type
+ // information.
+ var v = node.left.property.name, local = scope.props[v], over = local && local.iteratesOver;
+ if (over) {
+ maybeInstantiate(scope, 20);
+ var fromRight = node.right.type == "MemberExpression" && node.right.computed && node.right.property.name == v;
+ over.forAllProps(function(prop, val, local) {
+ if (local && prop != "prototype" && prop != "<i>")
+ obj.propagate(new DefProp(prop, fromRight ? val : ANull));
+ });
+ return rhs;
+ }
+ }
+
+ obj.propagate(new DefProp(pName, rhs, node.left.property));
+ maybeAddPhantomObj(obj)
+ if (node.right.type == "FunctionExpression")
+ obj.propagate(node.right.scope.fnType.self, WG_SPECULATIVE_THIS);
+ } else {
+ connectPattern(node.left, scope, rhs)
+ }
+ return rhs;
+ }),
+ LogicalExpression: fill(function(node, scope, out) {
+ infer(node.left, scope, out);
+ infer(node.right, scope, out);
+ }),
+ ConditionalExpression: fill(function(node, scope, out) {
+ infer(node.test, scope, ANull);
+ infer(node.consequent, scope, out);
+ infer(node.alternate, scope, out);
+ }),
+ NewExpression: fill(function(node, scope, out, name) {
+ if (node.callee.type == "Identifier" && node.callee.name in scope.props)
+ maybeInstantiate(scope, 20);
+
+ for (var i = 0, args = []; i < node.arguments.length; ++i)
+ args.push(infer(node.arguments[i], scope));
+ var callee = infer(node.callee, scope);
+ var self = new AVal;
+ callee.propagate(new IsCtor(self, name && /\.prototype$/.test(name)));
+ self.propagate(out, WG_NEW_INSTANCE);
+ callee.propagate(new IsCallee(self, args, node.arguments, new IfObj(out)));
+ }),
+ CallExpression: fill(function(node, scope, out) {
+ for (var i = 0, args = []; i < node.arguments.length; ++i)
+ args.push(infer(node.arguments[i], scope));
+ var outerFn = functionScope(scope).fnType
+ if (node.callee.type == "MemberExpression") {
+ var self = infer(node.callee.object, scope);
+ var pName = propName(node.callee, scope)
+ if (outerFn && (pName == "call" || pName == "apply") &&
+ outerFn.args.indexOf(self) > -1)
+ maybeInstantiate(scope, 30);
+ self.propagate(new HasMethodCall(pName, args, node.arguments, out));
+ } else if (node.callee.type == "Super" && cx.curSuperCtor) {
+ cx.curSuperCtor.propagate(new IsCallee(getThis(scope), args, node.arguments, out))
+ } else {
+ var callee = infer(node.callee, scope);
+ if (outerFn && outerFn.args.indexOf(callee) > -1)
+ maybeInstantiate(scope, 30);
+ var knownFn = callee.getFunctionType();
+ if (knownFn && knownFn.instantiateScore && outerFn)
+ maybeInstantiate(scope, knownFn.instantiateScore / 5);
+ callee.propagate(new IsCallee(cx.topScope, args, node.arguments, out));
+ }
+ }),
+ MemberExpression: fill(function(node, scope, out) {
+ var name = propName(node), wg;
+ if (name == "<i>") {
+ var propType = infer(node.property, scope)
+ var symName = symbolName(propType)
+ if (symName)
+ name = node.propName = symName
+ else if (!propType.hasType(cx.num))
+ wg = WG_MULTI_MEMBER
+ }
+ infer(node.object, scope).getProp(name).propagate(out, wg)
+ }),
+ Identifier: ret(function(node, scope) {
+ if (node.name == "arguments") {
+ var fnScope = functionScope(scope)
+ if (fnScope.fnType && !(node.name in fnScope.props))
+ scope.defProp(node.name, fnScope.fnType.originNode)
+ .addType(new Arr(fnScope.fnType.arguments = new AVal));
+ }
+ return scope.getProp(node.name);
+ }),
+ ThisExpression: ret(function(_node, scope) {
+ return getThis(scope)
+ }),
+ Super: ret(function(node) {
+ return node.superType = cx.curSuper || ANull
+ }),
+ Literal: ret(function(node) {
+ return literalType(node);
+ }),
+ TemplateLiteral: ret(function(node, scope) {
+ for (var i = 0; i < node.expressions.length; ++i)
+ infer(node.expressions[i], scope, ANull)
+ return cx.str
+ }),
+ TaggedTemplateExpression: fill(function(node, scope, out) {
+ var args = [new Arr(cx.str)]
+ for (var i = 0; i < node.quasi.expressions.length; ++i)
+ args.push(infer(node.quasi.expressions[i], scope))
+ infer(node.tag, scope, new IsCallee(cx.topScope, args, node.quasi.expressions, out))
+ }),
+ YieldExpression: ret(function(node, scope) {
+ var output = ANull, fn = functionScope(scope).fnType
+ if (fn) {
+ if (fn.retval == ANull) fn.retval = new AVal
+ if (!fn.yieldval) fn.yieldval = new AVal
+ output = fn.retval
+ }
+ if (node.argument) {
+ if (node.delegate) {
+ infer(node.argument, scope, new HasMethodCall("next", [], null,
+ new GetProp("value", output)))
+ } else {
+ infer(node.argument, scope, output)
+ }
+ }
+ return fn ? fn.yieldval : ANull
+ })
+ };
+ inferExprVisitor.ArrowFunctionExpression = inferExprVisitor.FunctionExpression
+
+ function infer(node, scope, out, name) {
+ var handler = inferExprVisitor[node.type];
+ return handler ? handler(node, scope, out, name) : ANull;
+ }
+
+ function loopPattern(init) {
+ return init.type == "VariableDeclaration" ? init.declarations[0].id : init
+ }
+
+ var inferWrapper = exports.inferWrapper = walk.make({
+ Expression: function(node, scope) {
+ infer(node, node.scope || scope, ANull);
+ },
+
+ FunctionDeclaration: function(node, scope, c) {
+ var inner = node.scope, fn = inner.fnType;
+ connectParams(node, inner)
+ c(node.body, inner, "Statement");
+ maybeTagAsInstantiated(node, fn) || maybeTagAsGeneric(fn);
+ scope.getProp(node.id.name).addType(fn)
+ },
+
+ Statement: function(node, scope, c) {
+ c(node, node.scope || scope)
+ },
+
+ VariableDeclaration: function(node, scope) {
+ for (var i = 0; i < node.declarations.length; ++i) {
+ var decl = node.declarations[i];
+ if (decl.id.type == "Identifier") {
+ var prop = scope.getProp(decl.id.name);
+ if (decl.init)
+ infer(decl.init, scope, prop, decl.id.name);
+ } else if (decl.init) {
+ connectPattern(decl.id, scope, infer(decl.init, scope))
+ }
+ }
+ },
+
+ ClassDeclaration: function(node, scope) {
+ scope.getProp(node.id.name).addType(inferClass(node, scope, node.id.name))
+ },
+
+ ReturnStatement: function(node, scope) {
+ if (!node.argument) return;
+ var output = ANull, fn = functionScope(scope).fnType
+ if (fn) {
+ if (fn.retval == ANull) fn.retval = new AVal;
+ output = fn.retval;
+ }
+ infer(node.argument, scope, output);
+ },
+
+ ForInStatement: function(node, scope, c) {
+ var source = infer(node.right, scope);
+ if ((node.right.type == "Identifier" && node.right.name in scope.props) ||
+ (node.right.type == "MemberExpression" && node.right.property.name == "prototype")) {
+ maybeInstantiate(scope, 5);
+ var pattern = loopPattern(node.left)
+ if (pattern.type == "Identifier") {
+ if (pattern.name in scope.props)
+ scope.getProp(pattern.name).iteratesOver = source
+ source.getProp("<i>").propagate(ensureVar(pattern, scope))
+ } else {
+ connectPattern(pattern, scope, source.getProp("<i>"))
+ }
+ }
+ c(node.body, scope, "Statement");
+ },
+
+ ForOfStatement: function(node, scope, c) {
+ var pattern = loopPattern(node.left), target
+ if (pattern.type == "Identifier")
+ target = ensureVar(pattern, scope)
+ else
+ connectPattern(pattern, scope, target = new AVal)
+ infer(node.right, scope, new HasMethodCall(":Symbol.iterator", [], null,
+ new HasMethodCall("next", [], null,
+ new GetProp("value", target))))
+ c(node.body, scope, "Statement")
+ }
+ });
+
+ // PARSING
+
+ var parse = exports.parse = function(text, options, thirdArg) {
+ if (!options || Array.isArray(options)) options = thirdArg
+ var ast;
+ try { ast = acorn.parse(text, options); }
+ catch(e) { ast = acorn_loose.parse_dammit(text, options); }
+ return ast;
+ };
+
+ // ANALYSIS INTERFACE
+
+ exports.analyze = function(ast, name, scope) {
+ if (typeof ast == "string") ast = parse(ast);
+
+ if (!name) name = "file#" + cx.origins.length;
+ exports.addOrigin(cx.curOrigin = name);
+
+ if (!scope) scope = cx.topScope;
+ cx.startAnalysis();
+
+ walk.recursive(ast, scope, null, scopeGatherer);
+ if (cx.parent) cx.parent.signal("preInfer", ast, scope)
+ walk.recursive(ast, scope, null, inferWrapper);
+ if (cx.parent) cx.parent.signal("postInfer", ast, scope)
+
+ cx.curOrigin = null;
+ };
+
+ // PURGING
+
+ exports.purge = function(origins, start, end) {
+ var test = makePredicate(origins, start, end);
+ ++cx.purgeGen;
+ cx.topScope.purge(test);
+ for (var prop in cx.props) {
+ var list = cx.props[prop];
+ for (var i = 0; i < list.length; ++i) {
+ var obj = list[i], av = obj.props[prop];
+ if (!av || test(av, av.originNode)) list.splice(i--, 1);
+ }
+ if (!list.length) delete cx.props[prop];
+ }
+ };
+
+ function makePredicate(origins, start, end) {
+ var arr = Array.isArray(origins);
+ if (arr && origins.length == 1) { origins = origins[0]; arr = false; }
+ if (arr) {
+ if (end == null) return function(n) { return origins.indexOf(n.origin) > -1; };
+ return function(n, pos) { return pos && pos.start >= start && pos.end <= end && origins.indexOf(n.origin) > -1; };
+ } else {
+ if (end == null) return function(n) { return n.origin == origins; };
+ return function(n, pos) { return pos && pos.start >= start && pos.end <= end && n.origin == origins; };
+ }
+ }
+
+ AVal.prototype.purge = function(test) {
+ if (this.purgeGen == cx.purgeGen) return;
+ this.purgeGen = cx.purgeGen;
+ for (var i = 0; i < this.types.length; ++i) {
+ var type = this.types[i];
+ if (test(type, type.originNode))
+ this.types.splice(i--, 1);
+ else
+ type.purge(test);
+ }
+ if (!this.types.length) this.maxWeight = 0;
+
+ if (this.forward) for (var i = 0; i < this.forward.length; ++i) {
+ var f = this.forward[i];
+ if (test(f)) {
+ this.forward.splice(i--, 1);
+ if (this.props) this.props = null;
+ } else if (f.purge) {
+ f.purge(test);
+ }
+ }
+ };
+ ANull.purge = function() {};
+ Obj.prototype.purge = function(test) {
+ if (this.purgeGen == cx.purgeGen) return true;
+ this.purgeGen = cx.purgeGen;
+ for (var p in this.props) {
+ var av = this.props[p];
+ if (test(av, av.originNode))
+ this.removeProp(p);
+ av.purge(test);
+ }
+ };
+ Fn.prototype.purge = function(test) {
+ if (Obj.prototype.purge.call(this, test)) return;
+ this.self.purge(test);
+ this.retval.purge(test);
+ for (var i = 0; i < this.args.length; ++i) this.args[i].purge(test);
+ };
+
+ // EXPRESSION TYPE DETERMINATION
+
+ function findByPropertyName(name) {
+ guessing = true;
+ var found = objsWithProp(name);
+ if (found) for (var i = 0; i < found.length; ++i) {
+ var val = found[i].getProp(name);
+ if (!val.isEmpty()) return val;
+ }
+ return ANull;
+ }
+
+ function generatorResult(input, output) {
+ var retObj = new Obj(true)
+ retObj.defProp("done").addType(cx.bool)
+ output.propagate(retObj.defProp("value"))
+ var method = new Fn(null, ANull, input ? [input] : [], input ? ["?"] : [], retObj)
+ var result = new Obj(cx.definitions.ecma6 && cx.definitions.ecma6.generator_prototype || true)
+ result.defProp("next").addType(method)
+ return result
+ }
+
+ function maybeIterator(fn, output) {
+ if (!fn.generator) return output
+ if (!fn.computeRet) { // Reuse iterator objects for non-computed return types
+ if (fn.generator === true) fn.generator = generatorResult(fn.yieldval, output)
+ return fn.generator
+ }
+ return generatorResult(fn.yieldval, output)
+ }
+
+ function computeReturnType(funcNode, argNodes, scope) {
+ var fn = findType(funcNode, scope).getFunctionType()
+ if (!fn) return ANull
+ var result = fn.retval
+ if (fn.computeRet) {
+ for (var i = 0, args = []; i < argNodes.length; ++i)
+ args.push(findType(argNodes[i], scope))
+ var self = ANull
+ if (funcNode.type == "MemberExpression")
+ self = findType(funcNode.object, scope)
+ result = fn.computeRet(self, args, argNodes);
+ }
+ return maybeIterator(fn, result)
+ }
+
+ var typeFinder = exports.typeFinder = {
+ ArrayExpression: function(node, scope) {
+ return arrayLiteralType(node.elements, scope, findType)
+ },
+ ObjectExpression: function(node) {
+ return node.objType;
+ },
+ ClassExpression: function(node) {
+ return node.objType;
+ },
+ FunctionExpression: function(node) {
+ return node.scope.fnType;
+ },
+ ArrowFunctionExpression: function(node) {
+ return node.scope.fnType;
+ },
+ SequenceExpression: function(node, scope) {
+ return findType(node.expressions[node.expressions.length-1], scope);
+ },
+ UnaryExpression: function(node) {
+ return unopResultType(node.operator);
+ },
+ UpdateExpression: function() {
+ return cx.num;
+ },
+ BinaryExpression: function(node, scope) {
+ if (binopIsBoolean(node.operator)) return cx.bool;
+ if (node.operator == "+") {
+ var lhs = findType(node.left, scope);
+ var rhs = findType(node.right, scope);
+ if (lhs.hasType(cx.str) || rhs.hasType(cx.str)) return cx.str;
+ }
+ return cx.num;
+ },
+ AssignmentExpression: function(node, scope) {
+ return findType(node.right, scope);
+ },
+ LogicalExpression: function(node, scope) {
+ var lhs = findType(node.left, scope);
+ return lhs.isEmpty() ? findType(node.right, scope) : lhs;
+ },
+ ConditionalExpression: function(node, scope) {
+ var lhs = findType(node.consequent, scope);
+ return lhs.isEmpty() ? findType(node.alternate, scope) : lhs;
+ },
+ NewExpression: function(node, scope) {
+ var f = findType(node.callee, scope).getFunctionType();
+ var proto = f && f.getProp("prototype").getObjType();
+ if (!proto) return ANull;
+ return getInstance(proto, f);
+ },
+ CallExpression: function(node, scope) {
+ return computeReturnType(node.callee, node.arguments, scope)
+ },
+ MemberExpression: function(node, scope) {
+ var propN = propName(node), obj = findType(node.object, scope).getType();
+ if (obj) return obj.getProp(propN);
+ if (propN == "<i>") return ANull;
+ return findByPropertyName(propN);
+ },
+ Identifier: function(node, scope) {
+ return scope.hasProp(node.name) || ANull;
+ },
+ ThisExpression: function(_node, scope) {
+ return getThis(scope)
+ },
+ Literal: function(node) {
+ return literalType(node);
+ },
+ Super: ret(function(node) {
+ return node.superType
+ }),
+ TemplateLiteral: function() {
+ return cx.str
+ },
+ TaggedTemplateExpression: function(node, scope) {
+ return computeReturnType(node.tag, node.quasi.expressions, scope)
+ },
+ YieldExpression: function(_node, scope) {
+ var fn = functionScope(scope).fnType
+ return fn ? fn.yieldval : ANull
+ }
+ };
+
+ function findType(node, scope) {
+ var finder = typeFinder[node.type];
+ return finder ? finder(node, scope) : ANull;
+ }
+
+ var searchVisitor = exports.searchVisitor = walk.make({
+ Function: function(node, _st, c) {
+ walk.base.Function(node, node.scope, c)
+ },
+ Property: function(node, st, c) {
+ if (node.computed) c(node.key, st, "Expression");
+ if (node.key != node.value) c(node.value, st, "Expression");
+ },
+ Statement: function(node, st, c) {
+ c(node, node.scope || st)
+ },
+ ImportSpecifier: function(node, st, c) {
+ c(node.local, st)
+ },
+ ImportDefaultSpecifier: function(node, st, c) {
+ c(node.local, st)
+ },
+ ImportNamespaceSpecifier: function(node, st, c) {
+ c(node.local, st)
+ }
+ });
+ exports.fullVisitor = walk.make({
+ MemberExpression: function(node, st, c) {
+ c(node.object, st, "Expression");
+ c(node.property, st, node.computed ? "Expression" : null);
+ },
+ ObjectExpression: function(node, st, c) {
+ for (var i = 0; i < node.properties.length; ++i) {
+ c(node.properties[i].value, st, "Expression");
+ c(node.properties[i].key, st);
+ }
+ }
+ }, searchVisitor);
+
+ exports.findExpressionAt = function(ast, start, end, defaultScope, filter) {
+ var test = filter || function(_t, node) {
+ if (node.type == "Identifier" && node.name == "✖") return false;
+ return typeFinder.hasOwnProperty(node.type);
+ };
+ return walk.findNodeAt(ast, start, end, test, searchVisitor, defaultScope || cx.topScope);
+ };
+
+ exports.findExpressionAround = function(ast, start, end, defaultScope, filter) {
+ var test = filter || function(_t, node) {
+ if (start != null && node.start > start) return false;
+ if (node.type == "Identifier" && node.name == "✖") return false;
+ return typeFinder.hasOwnProperty(node.type);
+ };
+ return walk.findNodeAround(ast, end, test, searchVisitor, defaultScope || cx.topScope);
+ };
+
+ exports.expressionType = function(found) {
+ return findType(found.node, found.state);
+ };
+
+ // Finding the expected type of something, from context
+
+ exports.parentNode = function(child, ast) {
+ var stack = [];
+ function c(node, st, override) {
+ if (node.start <= child.start && node.end >= child.end) {
+ var top = stack[stack.length - 1];
+ if (node == child) throw {found: top};
+ if (top != node) stack.push(node);
+ walk.base[override || node.type](node, st, c);
+ if (top != node) stack.pop();
+ }
+ }
+ try {
+ c(ast, null);
+ } catch (e) {
+ if (e.found) return e.found;
+ throw e;
+ }
+ };
+
+ var findTypeFromContext = exports.findTypeFromContext = {
+ ArrayExpression: function(parent, _, get) { return get(parent, true).getProp("<i>"); },
+ ObjectExpression: function(parent, node, get) {
+ for (var i = 0; i < parent.properties.length; ++i) {
+ var prop = node.properties[i];
+ if (prop.value == node)
+ return get(parent, true).getProp(prop.key.name);
+ }
+ },
+ UnaryExpression: function(parent) { return unopResultType(parent.operator); },
+ UpdateExpression: function() { return cx.num; },
+ BinaryExpression: function(parent) { return binopIsBoolean(parent.operator) ? cx.bool : cx.num; },
+ AssignmentExpression: function(parent, _, get) { return get(parent.left); },
+ LogicalExpression: function(parent, _, get) { return get(parent, true); },
+ ConditionalExpression: function(parent, node, get) {
+ if (parent.consequent == node || parent.alternate == node) return get(parent, true);
+ },
+ CallExpression: function(parent, node, get) {
+ for (var i = 0; i < parent.arguments.length; i++) {
+ var arg = parent.arguments[i];
+ if (arg == node) {
+ var calleeType = get(parent.callee).getFunctionType();
+ if (calleeType instanceof Fn)
+ return calleeType.args[i];
+ break;
+ }
+ }
+ },
+ ReturnStatement: function(_parent, node, get) {
+ var fnNode = walk.findNodeAround(node.sourceFile.ast, node.start, "Function");
+ if (fnNode) {
+ var fnType = fnNode.node.type != "FunctionDeclaration"
+ ? get(fnNode.node, true).getFunctionType()
+ : fnNode.node.scope.fnType;
+ if (fnType) return fnType.retval.getType();
+ }
+ },
+ VariableDeclarator: function(parent, node, get) {
+ if (parent.init == node) return get(parent.id)
+ }
+ };
+ findTypeFromContext.NewExpression = findTypeFromContext.CallExpression
+
+ exports.typeFromContext = function(ast, found) {
+ var parent = exports.parentNode(found.node, ast);
+ var type = null;
+ if (findTypeFromContext.hasOwnProperty(parent.type)) {
+ var finder = findTypeFromContext[parent.type];
+ type = finder && finder(parent, found.node, function(node, fromContext) {
+ var obj = {node: node, state: found.state};
+ var tp = fromContext ? exports.typeFromContext(ast, obj) : exports.expressionType(obj);
+ return tp || ANull;
+ });
+ }
+ return type || exports.expressionType(found);
+ };
+
+ // Flag used to indicate that some wild guessing was used to produce
+ // a type or set of completions.
+ var guessing = false;
+
+ exports.resetGuessing = function(val) { guessing = val; };
+ exports.didGuess = function() { return guessing; };
+
+ exports.forAllPropertiesOf = function(type, f) {
+ type.gatherProperties(f, 0);
+ };
+
+ var refFindWalker = walk.make({}, searchVisitor);
+
+ exports.findRefs = function(ast, baseScope, name, refScope, f) {
+ refFindWalker.Identifier = refFindWalker.VariablePattern = function(node, scope) {
+ if (node.name != name) return;
+ for (var s = scope; s; s = s.prev) {
+ if (s == refScope) f(node, scope);
+ if (name in s.props) return;
+ }
+ };
+ walk.recursive(ast, baseScope, null, refFindWalker);
+ };
+
+ var simpleWalker = walk.make({
+ Function: function(node, _scope, c) {
+ c(node.body, node.scope, node.expression ? "Expression" : "Statement")
+ },
+ Statement: function(node, scope, c) {
+ c(node, node.scope || scope)
+ }
+ });
+
+ exports.findPropRefs = function(ast, scope, objType, propName, f) {
+ walk.simple(ast, {
+ MemberExpression: function(node, scope) {
+ if (node.computed || node.property.name != propName) return;
+ if (findType(node.object, scope).getType() == objType) f(node.property);
+ },
+ ObjectExpression: function(node, scope) {
+ if (findType(node, scope).getType() != objType) return;
+ for (var i = 0; i < node.properties.length; ++i)
+ if (node.properties[i].key.name == propName) f(node.properties[i].key);
+ }
+ }, simpleWalker, scope);
+ };
+
+ // LOCAL-VARIABLE QUERIES
+
+ var scopeAt = exports.scopeAt = function(ast, pos, defaultScope) {
+ var found = walk.findNodeAround(ast, pos, function(_, node) {
+ return node.scope;
+ });
+ if (found) return found.node.scope;
+ else return defaultScope || cx.topScope;
+ };
+
+ exports.forAllLocalsAt = function(ast, pos, defaultScope, f) {
+ var scope = scopeAt(ast, pos, defaultScope);
+ scope.gatherProperties(f, 0);
+ };
+
+ // INIT DEF MODULE
+
+ // Delayed initialization because of cyclic dependencies.
+ def = exports.def = def.init({}, exports);
+});