infer.js 55 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635
  1. // Main type inference engine
  2. // Walks an AST, building up a graph of abstract values and constraints
  3. // that cause types to flow from one node to another. Also defines a
  4. // number of utilities for accessing ASTs and scopes.
  5. // Analysis is done in a context, which is tracked by the dynamically
  6. // bound cx variable. Use withContext to set the current context.
  7. // For memory-saving reasons, individual types export an interface
  8. // similar to abstract values (which can hold multiple types), and can
  9. // thus be used in place abstract values that only ever contain a
  10. // single type.
  11. (function(root, mod) {
  12. if (typeof exports == "object" && typeof module == "object") // CommonJS
  13. return mod(exports, require("acorn"), require("acorn/dist/acorn_loose"), require("acorn/dist/walk"),
  14. require("./def"), require("./signal"));
  15. if (typeof define == "function" && define.amd) // AMD
  16. return define(["exports", "acorn/dist/acorn", "acorn/dist/acorn_loose", "acorn/dist/walk", "./def", "./signal"], mod);
  17. mod(root.tern || (root.tern = {}), acorn, acorn, acorn.walk, tern.def, tern.signal); // Plain browser env
  18. })(this, function(exports, acorn, acorn_loose, walk, def, signal) {
  19. "use strict";
  20. var toString = exports.toString = function(type, maxDepth, parent) {
  21. if (!type || type == parent || maxDepth && maxDepth < -3) return "?";
  22. return type.toString(maxDepth, parent);
  23. };
  24. // A variant of AVal used for unknown, dead-end values. Also serves
  25. // as prototype for AVals, Types, and Constraints because it
  26. // implements 'empty' versions of all the methods that the code
  27. // expects.
  28. var ANull = exports.ANull = signal.mixin({
  29. addType: function() {},
  30. propagate: function() {},
  31. getProp: function() { return ANull; },
  32. forAllProps: function() {},
  33. hasType: function() { return false; },
  34. isEmpty: function() { return true; },
  35. getFunctionType: function() {},
  36. getObjType: function() {},
  37. getType: function() {},
  38. gatherProperties: function() {},
  39. propagatesTo: function() {},
  40. typeHint: function() {},
  41. propHint: function() {},
  42. toString: function() { return "?"; }
  43. });
  44. function extend(proto, props) {
  45. var obj = Object.create(proto);
  46. if (props) for (var prop in props) obj[prop] = props[prop];
  47. return obj;
  48. }
  49. // ABSTRACT VALUES
  50. var WG_DEFAULT = 100, WG_NEW_INSTANCE = 90, WG_MADEUP_PROTO = 10, WG_MULTI_MEMBER = 5,
  51. WG_CATCH_ERROR = 5, WG_GLOBAL_THIS = 90, WG_SPECULATIVE_THIS = 2;
  52. var AVal = exports.AVal = function() {
  53. this.types = [];
  54. this.forward = null;
  55. this.maxWeight = 0;
  56. };
  57. AVal.prototype = extend(ANull, {
  58. addType: function(type, weight) {
  59. weight = weight || WG_DEFAULT;
  60. if (this.maxWeight < weight) {
  61. this.maxWeight = weight;
  62. if (this.types.length == 1 && this.types[0] == type) return;
  63. this.types.length = 0;
  64. } else if (this.maxWeight > weight || this.types.indexOf(type) > -1) {
  65. return;
  66. }
  67. this.signal("addType", type);
  68. this.types.push(type);
  69. var forward = this.forward;
  70. if (forward) withWorklist(function(add) {
  71. for (var i = 0; i < forward.length; ++i) add(type, forward[i], weight);
  72. });
  73. },
  74. propagate: function(target, weight) {
  75. if (target == ANull || (target instanceof Type && this.forward && this.forward.length > 2)) return;
  76. if (weight && weight != WG_DEFAULT) target = new Muffle(target, weight);
  77. (this.forward || (this.forward = [])).push(target);
  78. var types = this.types;
  79. if (types.length) withWorklist(function(add) {
  80. for (var i = 0; i < types.length; ++i) add(types[i], target, weight);
  81. });
  82. },
  83. getProp: function(prop) {
  84. if (prop == "__proto__" || prop == "✖") return ANull;
  85. var found = (this.props || (this.props = Object.create(null)))[prop];
  86. if (!found) {
  87. found = this.props[prop] = new AVal;
  88. this.propagate(new PropIsSubset(prop, found));
  89. }
  90. return found;
  91. },
  92. forAllProps: function(c) {
  93. this.propagate(new ForAllProps(c));
  94. },
  95. hasType: function(type) {
  96. return this.types.indexOf(type) > -1;
  97. },
  98. isEmpty: function() { return this.types.length === 0; },
  99. getFunctionType: function() {
  100. for (var i = this.types.length - 1; i >= 0; --i)
  101. if (this.types[i] instanceof Fn) return this.types[i];
  102. },
  103. getObjType: function() {
  104. var seen = null;
  105. for (var i = this.types.length - 1; i >= 0; --i) {
  106. var type = this.types[i];
  107. if (!(type instanceof Obj)) continue;
  108. if (type.name) return type;
  109. if (!seen) seen = type;
  110. }
  111. return seen;
  112. },
  113. getType: function(guess) {
  114. if (this.types.length === 0 && guess !== false) return this.makeupType();
  115. if (this.types.length === 1) return this.types[0];
  116. return canonicalType(this.types);
  117. },
  118. toString: function(maxDepth, parent) {
  119. if (this.types.length == 0) return toString(this.makeupType(), maxDepth, parent);
  120. if (this.types.length == 1) return toString(this.types[0], maxDepth, parent);
  121. var simplified = simplifyTypes(this.types);
  122. if (simplified.length > 2) return "?";
  123. return simplified.map(function(tp) { return toString(tp, maxDepth, parent); }).join("|");
  124. },
  125. computedPropType: function() {
  126. if (!this.propertyOf) return null;
  127. if (this.propertyOf.hasProp("<i>")) {
  128. var computedProp = this.propertyOf.getProp("<i>");
  129. if (computedProp == this) return null;
  130. return computedProp.getType();
  131. } else if (this.propertyOf.maybeProps && this.propertyOf.maybeProps["<i>"] == this) {
  132. for (var prop in this.propertyOf.props) {
  133. var val = this.propertyOf.props[prop];
  134. if (!val.isEmpty()) return val;
  135. }
  136. return null;
  137. }
  138. },
  139. makeupType: function() {
  140. var computed = this.computedPropType();
  141. if (computed) return computed;
  142. if (!this.forward) return null;
  143. for (var i = this.forward.length - 1; i >= 0; --i) {
  144. var hint = this.forward[i].typeHint();
  145. if (hint && !hint.isEmpty()) {guessing = true; return hint;}
  146. }
  147. var props = Object.create(null), foundProp = null;
  148. for (var i = 0; i < this.forward.length; ++i) {
  149. var prop = this.forward[i].propHint();
  150. if (prop && prop != "length" && prop != "<i>" && prop != "✖" && prop != cx.completingProperty) {
  151. props[prop] = true;
  152. foundProp = prop;
  153. }
  154. }
  155. if (!foundProp) return null;
  156. var objs = objsWithProp(foundProp);
  157. if (objs) {
  158. var matches = [];
  159. search: for (var i = 0; i < objs.length; ++i) {
  160. var obj = objs[i];
  161. for (var prop in props) if (!obj.hasProp(prop)) continue search;
  162. if (obj.hasCtor) obj = getInstance(obj);
  163. matches.push(obj);
  164. }
  165. var canon = canonicalType(matches);
  166. if (canon) {guessing = true; return canon;}
  167. }
  168. },
  169. typeHint: function() { return this.types.length ? this.getType() : null; },
  170. propagatesTo: function() { return this; },
  171. gatherProperties: function(f, depth) {
  172. for (var i = 0; i < this.types.length; ++i)
  173. this.types[i].gatherProperties(f, depth);
  174. },
  175. guessProperties: function(f) {
  176. if (this.forward) for (var i = 0; i < this.forward.length; ++i) {
  177. var prop = this.forward[i].propHint();
  178. if (prop) f(prop, null, 0);
  179. }
  180. var guessed = this.makeupType();
  181. if (guessed) guessed.gatherProperties(f);
  182. }
  183. });
  184. function similarAVal(a, b, depth) {
  185. var typeA = a.getType(false), typeB = b.getType(false);
  186. if (!typeA || !typeB) return true;
  187. return similarType(typeA, typeB, depth);
  188. }
  189. function similarType(a, b, depth) {
  190. if (!a || depth >= 5) return b;
  191. if (a == b) return a;
  192. if (!b) return a;
  193. if (a.constructor != b.constructor) return false;
  194. if (a.constructor == Arr) {
  195. var innerA = a.getProp("<i>").getType(false);
  196. if (!innerA) return b;
  197. var innerB = b.getProp("<i>").getType(false);
  198. if (!innerB || similarType(innerA, innerB, depth + 1)) return b;
  199. } else if (a.constructor == Obj) {
  200. var propsA = 0, propsB = 0, same = 0;
  201. for (var prop in a.props) {
  202. propsA++;
  203. if (prop in b.props && similarAVal(a.props[prop], b.props[prop], depth + 1))
  204. same++;
  205. }
  206. for (var prop in b.props) propsB++;
  207. if (propsA && propsB && same < Math.max(propsA, propsB) / 2) return false;
  208. return propsA > propsB ? a : b;
  209. } else if (a.constructor == Fn) {
  210. if (a.args.length != b.args.length ||
  211. !a.args.every(function(tp, i) { return similarAVal(tp, b.args[i], depth + 1); }) ||
  212. !similarAVal(a.retval, b.retval, depth + 1) || !similarAVal(a.self, b.self, depth + 1))
  213. return false;
  214. return a;
  215. } else {
  216. return false;
  217. }
  218. }
  219. var simplifyTypes = exports.simplifyTypes = function(types) {
  220. var found = [];
  221. outer: for (var i = 0; i < types.length; ++i) {
  222. var tp = types[i];
  223. for (var j = 0; j < found.length; j++) {
  224. var similar = similarType(tp, found[j], 0);
  225. if (similar) {
  226. found[j] = similar;
  227. continue outer;
  228. }
  229. }
  230. found.push(tp);
  231. }
  232. return found;
  233. };
  234. function canonicalType(types) {
  235. var arrays = 0, fns = 0, objs = 0, prim = null;
  236. for (var i = 0; i < types.length; ++i) {
  237. var tp = types[i];
  238. if (tp instanceof Arr) ++arrays;
  239. else if (tp instanceof Fn) ++fns;
  240. else if (tp instanceof Obj) ++objs;
  241. else if (tp instanceof Prim) {
  242. if (prim && tp.name != prim.name) return null;
  243. prim = tp;
  244. }
  245. }
  246. var kinds = (arrays && 1) + (fns && 1) + (objs && 1) + (prim && 1);
  247. if (kinds > 1) return null;
  248. if (prim) return prim;
  249. var maxScore = 0, maxTp = null;
  250. for (var i = 0; i < types.length; ++i) {
  251. var tp = types[i], score = 0;
  252. if (arrays) {
  253. score = tp.getProp("<i>").isEmpty() ? 1 : 2;
  254. } else if (fns) {
  255. score = 1;
  256. for (var j = 0; j < tp.args.length; ++j) if (!tp.args[j].isEmpty()) ++score;
  257. if (!tp.retval.isEmpty()) ++score;
  258. } else if (objs) {
  259. score = tp.name ? 100 : 2;
  260. }
  261. if (score >= maxScore) { maxScore = score; maxTp = tp; }
  262. }
  263. return maxTp;
  264. }
  265. // PROPAGATION STRATEGIES
  266. function Constraint() {}
  267. Constraint.prototype = extend(ANull, {
  268. init: function() { this.origin = cx.curOrigin; }
  269. });
  270. var constraint = exports.constraint = function(props, methods) {
  271. var body = "this.init();";
  272. props = props ? props.split(", ") : [];
  273. for (var i = 0; i < props.length; ++i)
  274. body += "this." + props[i] + " = " + props[i] + ";";
  275. var ctor = Function.apply(null, props.concat([body]));
  276. ctor.prototype = Object.create(Constraint.prototype);
  277. for (var m in methods) if (methods.hasOwnProperty(m)) ctor.prototype[m] = methods[m];
  278. return ctor;
  279. };
  280. var PropIsSubset = constraint("prop, target", {
  281. addType: function(type, weight) {
  282. if (type.getProp)
  283. type.getProp(this.prop).propagate(this.target, weight);
  284. },
  285. propHint: function() { return this.prop; },
  286. propagatesTo: function() {
  287. if (this.prop == "<i>" || !/[^\w_]/.test(this.prop))
  288. return {target: this.target, pathExt: "." + this.prop};
  289. }
  290. });
  291. var PropHasSubset = exports.PropHasSubset = constraint("prop, type, originNode", {
  292. addType: function(type, weight) {
  293. if (!(type instanceof Obj)) return;
  294. var prop = type.defProp(this.prop, this.originNode);
  295. if (!prop.origin) prop.origin = this.origin;
  296. this.type.propagate(prop, weight);
  297. },
  298. propHint: function() { return this.prop; }
  299. });
  300. var ForAllProps = constraint("c", {
  301. addType: function(type) {
  302. if (!(type instanceof Obj)) return;
  303. type.forAllProps(this.c);
  304. }
  305. });
  306. function withDisabledComputing(fn, body) {
  307. cx.disabledComputing = {fn: fn, prev: cx.disabledComputing};
  308. try {
  309. return body();
  310. } finally {
  311. cx.disabledComputing = cx.disabledComputing.prev;
  312. }
  313. }
  314. var IsCallee = exports.IsCallee = constraint("self, args, argNodes, retval", {
  315. init: function() {
  316. Constraint.prototype.init.call(this);
  317. this.disabled = cx.disabledComputing;
  318. },
  319. addType: function(fn, weight) {
  320. if (!(fn instanceof Fn)) return;
  321. for (var i = 0; i < this.args.length; ++i) {
  322. if (i < fn.args.length) this.args[i].propagate(fn.args[i], weight);
  323. if (fn.arguments) this.args[i].propagate(fn.arguments, weight);
  324. }
  325. this.self.propagate(fn.self, this.self == cx.topScope ? WG_GLOBAL_THIS : weight);
  326. var compute = fn.computeRet;
  327. if (compute) for (var d = this.disabled; d; d = d.prev)
  328. if (d.fn == fn || fn.originNode && d.fn.originNode == fn.originNode) compute = null;
  329. if (compute)
  330. compute(this.self, this.args, this.argNodes).propagate(this.retval, weight);
  331. else
  332. fn.retval.propagate(this.retval, weight);
  333. },
  334. typeHint: function() {
  335. var names = [];
  336. for (var i = 0; i < this.args.length; ++i) names.push("?");
  337. return new Fn(null, this.self, this.args, names, ANull);
  338. },
  339. propagatesTo: function() {
  340. return {target: this.retval, pathExt: ".!ret"};
  341. }
  342. });
  343. var HasMethodCall = constraint("propName, args, argNodes, retval", {
  344. init: function() {
  345. Constraint.prototype.init.call(this);
  346. this.disabled = cx.disabledComputing;
  347. },
  348. addType: function(obj, weight) {
  349. var callee = new IsCallee(obj, this.args, this.argNodes, this.retval);
  350. callee.disabled = this.disabled;
  351. obj.getProp(this.propName).propagate(callee, weight);
  352. },
  353. propHint: function() { return this.propName; }
  354. });
  355. var IsCtor = exports.IsCtor = constraint("target, noReuse", {
  356. addType: function(f, weight) {
  357. if (!(f instanceof Fn)) return;
  358. if (cx.parent && !cx.parent.options.reuseInstances) this.noReuse = true;
  359. f.getProp("prototype").propagate(new IsProto(this.noReuse ? false : f, this.target), weight);
  360. }
  361. });
  362. var getInstance = exports.getInstance = function(obj, ctor) {
  363. if (ctor === false) return new Obj(obj);
  364. if (!ctor) ctor = obj.hasCtor;
  365. if (!obj.instances) obj.instances = [];
  366. for (var i = 0; i < obj.instances.length; ++i) {
  367. var cur = obj.instances[i];
  368. if (cur.ctor == ctor) return cur.instance;
  369. }
  370. var instance = new Obj(obj, ctor && ctor.name);
  371. instance.origin = obj.origin;
  372. obj.instances.push({ctor: ctor, instance: instance});
  373. return instance;
  374. };
  375. var IsProto = exports.IsProto = constraint("ctor, target", {
  376. addType: function(o, _weight) {
  377. if (!(o instanceof Obj)) return;
  378. if ((this.count = (this.count || 0) + 1) > 8) return;
  379. if (o == cx.protos.Array)
  380. this.target.addType(new Arr);
  381. else
  382. this.target.addType(getInstance(o, this.ctor));
  383. }
  384. });
  385. var FnPrototype = constraint("fn", {
  386. addType: function(o, _weight) {
  387. if (o instanceof Obj && !o.hasCtor) {
  388. o.hasCtor = this.fn;
  389. var adder = new SpeculativeThis(o, this.fn);
  390. adder.addType(this.fn);
  391. o.forAllProps(function(_prop, val, local) {
  392. if (local) val.propagate(adder);
  393. });
  394. }
  395. }
  396. });
  397. var IsAdded = constraint("other, target", {
  398. addType: function(type, weight) {
  399. if (type == cx.str)
  400. this.target.addType(cx.str, weight);
  401. else if (type == cx.num && this.other.hasType(cx.num))
  402. this.target.addType(cx.num, weight);
  403. },
  404. typeHint: function() { return this.other; }
  405. });
  406. var IfObj = exports.IfObj = constraint("target", {
  407. addType: function(t, weight) {
  408. if (t instanceof Obj) this.target.addType(t, weight);
  409. },
  410. propagatesTo: function() { return this.target; }
  411. });
  412. var SpeculativeThis = constraint("obj, ctor", {
  413. addType: function(tp) {
  414. if (tp instanceof Fn && tp.self && tp.self.isEmpty())
  415. tp.self.addType(getInstance(this.obj, this.ctor), WG_SPECULATIVE_THIS);
  416. }
  417. });
  418. var Muffle = constraint("inner, weight", {
  419. addType: function(tp, weight) {
  420. this.inner.addType(tp, Math.min(weight, this.weight));
  421. },
  422. propagatesTo: function() { return this.inner.propagatesTo(); },
  423. typeHint: function() { return this.inner.typeHint(); },
  424. propHint: function() { return this.inner.propHint(); }
  425. });
  426. // TYPE OBJECTS
  427. var Type = exports.Type = function() {};
  428. Type.prototype = extend(ANull, {
  429. constructor: Type,
  430. propagate: function(c, w) { c.addType(this, w); },
  431. hasType: function(other) { return other == this; },
  432. isEmpty: function() { return false; },
  433. typeHint: function() { return this; },
  434. getType: function() { return this; }
  435. });
  436. var Prim = exports.Prim = function(proto, name) { this.name = name; this.proto = proto; };
  437. Prim.prototype = extend(Type.prototype, {
  438. constructor: Prim,
  439. toString: function() { return this.name; },
  440. getProp: function(prop) {return this.proto.hasProp(prop) || ANull;},
  441. gatherProperties: function(f, depth) {
  442. if (this.proto) this.proto.gatherProperties(f, depth);
  443. }
  444. });
  445. var Obj = exports.Obj = function(proto, name) {
  446. if (!this.props) this.props = Object.create(null);
  447. this.proto = proto === true ? cx.protos.Object : proto;
  448. if (proto && !name && proto.name && !(this instanceof Fn)) {
  449. var match = /^(.*)\.prototype$/.exec(this.proto.name);
  450. if (match) name = match[1];
  451. }
  452. this.name = name;
  453. this.maybeProps = null;
  454. this.origin = cx.curOrigin;
  455. };
  456. Obj.prototype = extend(Type.prototype, {
  457. constructor: Obj,
  458. toString: function(maxDepth) {
  459. if (maxDepth == null) maxDepth = 0;
  460. if (maxDepth <= 0 && this.name) return this.name;
  461. var props = [], etc = false;
  462. for (var prop in this.props) if (prop != "<i>") {
  463. if (props.length > 5) { etc = true; break; }
  464. if (maxDepth)
  465. props.push(prop + ": " + toString(this.props[prop], maxDepth - 1, this));
  466. else
  467. props.push(prop);
  468. }
  469. props.sort();
  470. if (etc) props.push("...");
  471. return "{" + props.join(", ") + "}";
  472. },
  473. hasProp: function(prop, searchProto) {
  474. var found = this.props[prop];
  475. if (searchProto !== false)
  476. for (var p = this.proto; p && !found; p = p.proto) found = p.props[prop];
  477. return found;
  478. },
  479. defProp: function(prop, originNode) {
  480. var found = this.hasProp(prop, false);
  481. if (found) {
  482. if (originNode && !found.originNode) found.originNode = originNode;
  483. return found;
  484. }
  485. if (prop == "__proto__" || prop == "✖") return ANull;
  486. var av = this.maybeProps && this.maybeProps[prop];
  487. if (av) {
  488. delete this.maybeProps[prop];
  489. this.maybeUnregProtoPropHandler();
  490. } else {
  491. av = new AVal;
  492. av.propertyOf = this;
  493. }
  494. this.props[prop] = av;
  495. av.originNode = originNode;
  496. av.origin = cx.curOrigin;
  497. this.broadcastProp(prop, av, true);
  498. return av;
  499. },
  500. getProp: function(prop) {
  501. var found = this.hasProp(prop, true) || (this.maybeProps && this.maybeProps[prop]);
  502. if (found) return found;
  503. if (prop == "__proto__" || prop == "✖") return ANull;
  504. var av = this.ensureMaybeProps()[prop] = new AVal;
  505. av.propertyOf = this;
  506. return av;
  507. },
  508. broadcastProp: function(prop, val, local) {
  509. if (local) {
  510. this.signal("addProp", prop, val);
  511. // If this is a scope, it shouldn't be registered
  512. if (!(this instanceof Scope)) registerProp(prop, this);
  513. }
  514. if (this.onNewProp) for (var i = 0; i < this.onNewProp.length; ++i) {
  515. var h = this.onNewProp[i];
  516. h.onProtoProp ? h.onProtoProp(prop, val, local) : h(prop, val, local);
  517. }
  518. },
  519. onProtoProp: function(prop, val, _local) {
  520. var maybe = this.maybeProps && this.maybeProps[prop];
  521. if (maybe) {
  522. delete this.maybeProps[prop];
  523. this.maybeUnregProtoPropHandler();
  524. this.proto.getProp(prop).propagate(maybe);
  525. }
  526. this.broadcastProp(prop, val, false);
  527. },
  528. ensureMaybeProps: function() {
  529. if (!this.maybeProps) {
  530. if (this.proto) this.proto.forAllProps(this);
  531. this.maybeProps = Object.create(null);
  532. }
  533. return this.maybeProps;
  534. },
  535. removeProp: function(prop) {
  536. var av = this.props[prop];
  537. delete this.props[prop];
  538. this.ensureMaybeProps()[prop] = av;
  539. av.types.length = 0;
  540. },
  541. forAllProps: function(c) {
  542. if (!this.onNewProp) {
  543. this.onNewProp = [];
  544. if (this.proto) this.proto.forAllProps(this);
  545. }
  546. this.onNewProp.push(c);
  547. for (var o = this; o; o = o.proto) for (var prop in o.props) {
  548. if (c.onProtoProp)
  549. c.onProtoProp(prop, o.props[prop], o == this);
  550. else
  551. c(prop, o.props[prop], o == this);
  552. }
  553. },
  554. maybeUnregProtoPropHandler: function() {
  555. if (this.maybeProps) {
  556. for (var _n in this.maybeProps) return;
  557. this.maybeProps = null;
  558. }
  559. if (!this.proto || this.onNewProp && this.onNewProp.length) return;
  560. this.proto.unregPropHandler(this);
  561. },
  562. unregPropHandler: function(handler) {
  563. for (var i = 0; i < this.onNewProp.length; ++i)
  564. if (this.onNewProp[i] == handler) { this.onNewProp.splice(i, 1); break; }
  565. this.maybeUnregProtoPropHandler();
  566. },
  567. gatherProperties: function(f, depth) {
  568. for (var prop in this.props) if (prop != "<i>")
  569. f(prop, this, depth);
  570. if (this.proto) this.proto.gatherProperties(f, depth + 1);
  571. },
  572. getObjType: function() { return this; }
  573. });
  574. var Fn = exports.Fn = function(name, self, args, argNames, retval) {
  575. Obj.call(this, cx.protos.Function, name);
  576. this.self = self;
  577. this.args = args;
  578. this.argNames = argNames;
  579. this.retval = retval;
  580. };
  581. Fn.prototype = extend(Obj.prototype, {
  582. constructor: Fn,
  583. toString: function(maxDepth) {
  584. if (maxDepth == null) maxDepth = 0;
  585. var str = "fn(";
  586. for (var i = 0; i < this.args.length; ++i) {
  587. if (i) str += ", ";
  588. var name = this.argNames[i];
  589. if (name && name != "?") str += name + ": ";
  590. str += maxDepth > -3 ? toString(this.args[i], maxDepth - 1, this) : "?";
  591. }
  592. str += ")";
  593. if (!this.retval.isEmpty())
  594. str += " -> " + (maxDepth > -3 ? toString(this.retval, maxDepth - 1, this) : "?");
  595. return str;
  596. },
  597. getProp: function(prop) {
  598. if (prop == "prototype") {
  599. var known = this.hasProp(prop, false);
  600. if (!known) {
  601. known = this.defProp(prop);
  602. var proto = new Obj(true, this.name && this.name + ".prototype");
  603. proto.origin = this.origin;
  604. known.addType(proto, WG_MADEUP_PROTO);
  605. }
  606. return known;
  607. }
  608. return Obj.prototype.getProp.call(this, prop);
  609. },
  610. defProp: function(prop, originNode) {
  611. if (prop == "prototype") {
  612. var found = this.hasProp(prop, false);
  613. if (found) return found;
  614. found = Obj.prototype.defProp.call(this, prop, originNode);
  615. found.origin = this.origin;
  616. found.propagate(new FnPrototype(this));
  617. return found;
  618. }
  619. return Obj.prototype.defProp.call(this, prop, originNode);
  620. },
  621. getFunctionType: function() { return this; }
  622. });
  623. var Arr = exports.Arr = function(contentType) {
  624. Obj.call(this, cx.protos.Array);
  625. var content = this.defProp("<i>");
  626. if (contentType) contentType.propagate(content);
  627. };
  628. Arr.prototype = extend(Obj.prototype, {
  629. constructor: Arr,
  630. toString: function(maxDepth) {
  631. if (maxDepth == null) maxDepth = 0;
  632. return "[" + (maxDepth > -3 ? toString(this.getProp("<i>"), maxDepth - 1, this) : "?") + "]";
  633. }
  634. });
  635. // THE PROPERTY REGISTRY
  636. function registerProp(prop, obj) {
  637. var data = cx.props[prop] || (cx.props[prop] = []);
  638. data.push(obj);
  639. }
  640. function objsWithProp(prop) {
  641. return cx.props[prop];
  642. }
  643. // INFERENCE CONTEXT
  644. exports.Context = function(defs, parent) {
  645. this.parent = parent;
  646. this.props = Object.create(null);
  647. this.protos = Object.create(null);
  648. this.origins = [];
  649. this.curOrigin = "ecma5";
  650. this.paths = Object.create(null);
  651. this.definitions = Object.create(null);
  652. this.purgeGen = 0;
  653. this.workList = null;
  654. this.disabledComputing = null;
  655. exports.withContext(this, function() {
  656. cx.protos.Object = new Obj(null, "Object.prototype");
  657. cx.topScope = new Scope();
  658. cx.topScope.name = "<top>";
  659. cx.protos.Array = new Obj(true, "Array.prototype");
  660. cx.protos.Function = new Obj(true, "Function.prototype");
  661. cx.protos.RegExp = new Obj(true, "RegExp.prototype");
  662. cx.protos.String = new Obj(true, "String.prototype");
  663. cx.protos.Number = new Obj(true, "Number.prototype");
  664. cx.protos.Boolean = new Obj(true, "Boolean.prototype");
  665. cx.str = new Prim(cx.protos.String, "string");
  666. cx.bool = new Prim(cx.protos.Boolean, "bool");
  667. cx.num = new Prim(cx.protos.Number, "number");
  668. cx.curOrigin = null;
  669. if (defs) for (var i = 0; i < defs.length; ++i)
  670. def.load(defs[i]);
  671. });
  672. };
  673. var cx = null;
  674. exports.cx = function() { return cx; };
  675. exports.withContext = function(context, f) {
  676. var old = cx;
  677. cx = context;
  678. try { return f(); }
  679. finally { cx = old; }
  680. };
  681. exports.TimedOut = function() {
  682. this.message = "Timed out";
  683. this.stack = (new Error()).stack;
  684. };
  685. exports.TimedOut.prototype = Object.create(Error.prototype);
  686. exports.TimedOut.prototype.name = "infer.TimedOut";
  687. var timeout;
  688. exports.withTimeout = function(ms, f) {
  689. var end = +new Date + ms;
  690. var oldEnd = timeout;
  691. if (oldEnd && oldEnd < end) return f();
  692. timeout = end;
  693. try { return f(); }
  694. finally { timeout = oldEnd; }
  695. };
  696. exports.addOrigin = function(origin) {
  697. if (cx.origins.indexOf(origin) < 0) cx.origins.push(origin);
  698. };
  699. var baseMaxWorkDepth = 20, reduceMaxWorkDepth = 0.0001;
  700. function withWorklist(f) {
  701. if (cx.workList) return f(cx.workList);
  702. var list = [], depth = 0;
  703. var add = cx.workList = function(type, target, weight) {
  704. if (depth < baseMaxWorkDepth - reduceMaxWorkDepth * list.length)
  705. list.push(type, target, weight, depth);
  706. };
  707. try {
  708. var ret = f(add);
  709. for (var i = 0; i < list.length; i += 4) {
  710. if (timeout && +new Date >= timeout)
  711. throw new exports.TimedOut();
  712. depth = list[i + 3] + 1;
  713. list[i + 1].addType(list[i], list[i + 2]);
  714. }
  715. return ret;
  716. } finally {
  717. cx.workList = null;
  718. }
  719. }
  720. // SCOPES
  721. var Scope = exports.Scope = function(prev) {
  722. Obj.call(this, prev || true);
  723. this.prev = prev;
  724. };
  725. Scope.prototype = extend(Obj.prototype, {
  726. constructor: Scope,
  727. defVar: function(name, originNode) {
  728. for (var s = this; ; s = s.proto) {
  729. var found = s.props[name];
  730. if (found) return found;
  731. if (!s.prev) return s.defProp(name, originNode);
  732. }
  733. }
  734. });
  735. // RETVAL COMPUTATION HEURISTICS
  736. function maybeInstantiate(scope, score) {
  737. if (scope.fnType)
  738. scope.fnType.instantiateScore = (scope.fnType.instantiateScore || 0) + score;
  739. }
  740. var NotSmaller = {};
  741. function nodeSmallerThan(node, n) {
  742. try {
  743. walk.simple(node, {Expression: function() { if (--n <= 0) throw NotSmaller; }});
  744. return true;
  745. } catch(e) {
  746. if (e == NotSmaller) return false;
  747. throw e;
  748. }
  749. }
  750. function maybeTagAsInstantiated(node, scope) {
  751. var score = scope.fnType.instantiateScore;
  752. if (!cx.disabledComputing && score && scope.fnType.args.length && nodeSmallerThan(node, score * 5)) {
  753. maybeInstantiate(scope.prev, score / 2);
  754. setFunctionInstantiated(node, scope);
  755. return true;
  756. } else {
  757. scope.fnType.instantiateScore = null;
  758. }
  759. }
  760. function setFunctionInstantiated(node, scope) {
  761. var fn = scope.fnType;
  762. // Disconnect the arg avals, so that we can add info to them without side effects
  763. for (var i = 0; i < fn.args.length; ++i) fn.args[i] = new AVal;
  764. fn.self = new AVal;
  765. fn.computeRet = function(self, args) {
  766. // Prevent recursion
  767. return withDisabledComputing(fn, function() {
  768. var oldOrigin = cx.curOrigin;
  769. cx.curOrigin = fn.origin;
  770. var scopeCopy = new Scope(scope.prev);
  771. scopeCopy.originNode = scope.originNode;
  772. for (var v in scope.props) {
  773. var local = scopeCopy.defProp(v, scope.props[v].originNode);
  774. for (var i = 0; i < args.length; ++i) if (fn.argNames[i] == v && i < args.length)
  775. args[i].propagate(local);
  776. }
  777. var argNames = fn.argNames.length != args.length ? fn.argNames.slice(0, args.length) : fn.argNames;
  778. while (argNames.length < args.length) argNames.push("?");
  779. scopeCopy.fnType = new Fn(fn.name, self, args, argNames, ANull);
  780. scopeCopy.fnType.originNode = fn.originNode;
  781. if (fn.arguments) {
  782. var argset = scopeCopy.fnType.arguments = new AVal;
  783. scopeCopy.defProp("arguments").addType(new Arr(argset));
  784. for (var i = 0; i < args.length; ++i) args[i].propagate(argset);
  785. }
  786. node.body.scope = scopeCopy;
  787. walk.recursive(node.body, scopeCopy, null, scopeGatherer);
  788. walk.recursive(node.body, scopeCopy, null, inferWrapper);
  789. cx.curOrigin = oldOrigin;
  790. return scopeCopy.fnType.retval;
  791. });
  792. };
  793. }
  794. function maybeTagAsGeneric(scope) {
  795. var fn = scope.fnType, target = fn.retval;
  796. if (target == ANull) return;
  797. var targetInner, asArray;
  798. if (!target.isEmpty() && (targetInner = target.getType()) instanceof Arr)
  799. target = asArray = targetInner.getProp("<i>");
  800. function explore(aval, path, depth) {
  801. if (depth > 3 || !aval.forward) return;
  802. for (var i = 0; i < aval.forward.length; ++i) {
  803. var prop = aval.forward[i].propagatesTo();
  804. if (!prop) continue;
  805. var newPath = path, dest;
  806. if (prop instanceof AVal) {
  807. dest = prop;
  808. } else if (prop.target instanceof AVal) {
  809. newPath += prop.pathExt;
  810. dest = prop.target;
  811. } else continue;
  812. if (dest == target) return newPath;
  813. var found = explore(dest, newPath, depth + 1);
  814. if (found) return found;
  815. }
  816. }
  817. var foundPath = explore(fn.self, "!this", 0);
  818. for (var i = 0; !foundPath && i < fn.args.length; ++i)
  819. foundPath = explore(fn.args[i], "!" + i, 0);
  820. if (foundPath) {
  821. if (asArray) foundPath = "[" + foundPath + "]";
  822. var p = new def.TypeParser(foundPath);
  823. var parsed = p.parseType(true);
  824. fn.computeRet = parsed.apply ? parsed : function() { return parsed; };
  825. fn.computeRetSource = foundPath;
  826. return true;
  827. }
  828. }
  829. // SCOPE GATHERING PASS
  830. function addVar(scope, nameNode) {
  831. return scope.defProp(nameNode.name, nameNode);
  832. }
  833. var scopeGatherer = walk.make({
  834. Function: function(node, scope, c) {
  835. var inner = node.body.scope = new Scope(scope);
  836. inner.originNode = node;
  837. var argVals = [], argNames = [];
  838. for (var i = 0; i < node.params.length; ++i) {
  839. var param = node.params[i];
  840. argNames.push(param.name);
  841. argVals.push(addVar(inner, param));
  842. }
  843. inner.fnType = new Fn(node.id && node.id.name, new AVal, argVals, argNames, ANull);
  844. inner.fnType.originNode = node;
  845. if (node.id) {
  846. var decl = node.type == "FunctionDeclaration";
  847. addVar(decl ? scope : inner, node.id);
  848. }
  849. c(node.body, inner, "ScopeBody");
  850. },
  851. TryStatement: function(node, scope, c) {
  852. c(node.block, scope, "Statement");
  853. if (node.handler) {
  854. var v = addVar(scope, node.handler.param);
  855. c(node.handler.body, scope, "ScopeBody");
  856. var e5 = cx.definitions.ecma5;
  857. if (e5 && v.isEmpty()) getInstance(e5["Error.prototype"]).propagate(v, WG_CATCH_ERROR);
  858. }
  859. if (node.finalizer) c(node.finalizer, scope, "Statement");
  860. },
  861. VariableDeclaration: function(node, scope, c) {
  862. for (var i = 0; i < node.declarations.length; ++i) {
  863. var decl = node.declarations[i];
  864. addVar(scope, decl.id);
  865. if (decl.init) c(decl.init, scope, "Expression");
  866. }
  867. }
  868. });
  869. // CONSTRAINT GATHERING PASS
  870. function propName(node, scope, c) {
  871. var prop = node.property;
  872. if (!node.computed) return prop.name;
  873. if (prop.type == "Literal" && typeof prop.value == "string") return prop.value;
  874. if (c) infer(prop, scope, c, ANull);
  875. return "<i>";
  876. }
  877. function unopResultType(op) {
  878. switch (op) {
  879. case "+": case "-": case "~": return cx.num;
  880. case "!": return cx.bool;
  881. case "typeof": return cx.str;
  882. case "void": case "delete": return ANull;
  883. }
  884. }
  885. function binopIsBoolean(op) {
  886. switch (op) {
  887. case "==": case "!=": case "===": case "!==": case "<": case ">": case ">=": case "<=":
  888. case "in": case "instanceof": return true;
  889. }
  890. }
  891. function literalType(node) {
  892. if (node.regex) return getInstance(cx.protos.RegExp);
  893. switch (typeof node.value) {
  894. case "boolean": return cx.bool;
  895. case "number": return cx.num;
  896. case "string": return cx.str;
  897. case "object":
  898. case "function":
  899. if (!node.value) return ANull;
  900. return getInstance(cx.protos.RegExp);
  901. }
  902. }
  903. function ret(f) {
  904. return function(node, scope, c, out, name) {
  905. var r = f(node, scope, c, name);
  906. if (out) r.propagate(out);
  907. return r;
  908. };
  909. }
  910. function fill(f) {
  911. return function(node, scope, c, out, name) {
  912. if (!out) out = new AVal;
  913. f(node, scope, c, out, name);
  914. return out;
  915. };
  916. }
  917. var inferExprVisitor = {
  918. ArrayExpression: ret(function(node, scope, c) {
  919. var eltval = new AVal;
  920. for (var i = 0; i < node.elements.length; ++i) {
  921. var elt = node.elements[i];
  922. if (elt) infer(elt, scope, c, eltval);
  923. }
  924. return new Arr(eltval);
  925. }),
  926. ObjectExpression: ret(function(node, scope, c, name) {
  927. var obj = node.objType = new Obj(true, name);
  928. obj.originNode = node;
  929. for (var i = 0; i < node.properties.length; ++i) {
  930. var prop = node.properties[i], key = prop.key, name;
  931. if (prop.value.name == "✖") continue;
  932. if (key.type == "Identifier") {
  933. name = key.name;
  934. } else if (typeof key.value == "string") {
  935. name = key.value;
  936. }
  937. if (!name || prop.kind == "set") {
  938. infer(prop.value, scope, c, ANull);
  939. continue;
  940. }
  941. var val = obj.defProp(name, key), out = val;
  942. val.initializer = true;
  943. if (prop.kind == "get")
  944. out = new IsCallee(obj, [], null, val);
  945. infer(prop.value, scope, c, out, name);
  946. }
  947. return obj;
  948. }),
  949. FunctionExpression: ret(function(node, scope, c, name) {
  950. var inner = node.body.scope, fn = inner.fnType;
  951. if (name && !fn.name) fn.name = name;
  952. c(node.body, scope, "ScopeBody");
  953. maybeTagAsInstantiated(node, inner) || maybeTagAsGeneric(inner);
  954. if (node.id) inner.getProp(node.id.name).addType(fn);
  955. return fn;
  956. }),
  957. SequenceExpression: ret(function(node, scope, c) {
  958. for (var i = 0, l = node.expressions.length - 1; i < l; ++i)
  959. infer(node.expressions[i], scope, c, ANull);
  960. return infer(node.expressions[l], scope, c);
  961. }),
  962. UnaryExpression: ret(function(node, scope, c) {
  963. infer(node.argument, scope, c, ANull);
  964. return unopResultType(node.operator);
  965. }),
  966. UpdateExpression: ret(function(node, scope, c) {
  967. infer(node.argument, scope, c, ANull);
  968. return cx.num;
  969. }),
  970. BinaryExpression: ret(function(node, scope, c) {
  971. if (node.operator == "+") {
  972. var lhs = infer(node.left, scope, c);
  973. var rhs = infer(node.right, scope, c);
  974. if (lhs.hasType(cx.str) || rhs.hasType(cx.str)) return cx.str;
  975. if (lhs.hasType(cx.num) && rhs.hasType(cx.num)) return cx.num;
  976. var result = new AVal;
  977. lhs.propagate(new IsAdded(rhs, result));
  978. rhs.propagate(new IsAdded(lhs, result));
  979. return result;
  980. } else {
  981. infer(node.left, scope, c, ANull);
  982. infer(node.right, scope, c, ANull);
  983. return binopIsBoolean(node.operator) ? cx.bool : cx.num;
  984. }
  985. }),
  986. AssignmentExpression: ret(function(node, scope, c) {
  987. var rhs, name, pName;
  988. if (node.left.type == "MemberExpression") {
  989. pName = propName(node.left, scope, c);
  990. if (node.left.object.type == "Identifier")
  991. name = node.left.object.name + "." + pName;
  992. } else {
  993. name = node.left.name;
  994. }
  995. if (node.operator != "=" && node.operator != "+=") {
  996. infer(node.right, scope, c, ANull);
  997. rhs = cx.num;
  998. } else {
  999. rhs = infer(node.right, scope, c, null, name);
  1000. }
  1001. if (node.left.type == "MemberExpression") {
  1002. var obj = infer(node.left.object, scope, c);
  1003. if (pName == "prototype") maybeInstantiate(scope, 20);
  1004. if (pName == "<i>") {
  1005. // This is a hack to recognize for/in loops that copy
  1006. // properties, and do the copying ourselves, insofar as we
  1007. // manage, because such loops tend to be relevant for type
  1008. // information.
  1009. var v = node.left.property.name, local = scope.props[v], over = local && local.iteratesOver;
  1010. if (over) {
  1011. maybeInstantiate(scope, 20);
  1012. var fromRight = node.right.type == "MemberExpression" && node.right.computed && node.right.property.name == v;
  1013. over.forAllProps(function(prop, val, local) {
  1014. if (local && prop != "prototype" && prop != "<i>")
  1015. obj.propagate(new PropHasSubset(prop, fromRight ? val : ANull));
  1016. });
  1017. return rhs;
  1018. }
  1019. }
  1020. obj.propagate(new PropHasSubset(pName, rhs, node.left.property));
  1021. } else { // Identifier
  1022. rhs.propagate(scope.defVar(node.left.name, node.left));
  1023. }
  1024. return rhs;
  1025. }),
  1026. LogicalExpression: fill(function(node, scope, c, out) {
  1027. infer(node.left, scope, c, out);
  1028. infer(node.right, scope, c, out);
  1029. }),
  1030. ConditionalExpression: fill(function(node, scope, c, out) {
  1031. infer(node.test, scope, c, ANull);
  1032. infer(node.consequent, scope, c, out);
  1033. infer(node.alternate, scope, c, out);
  1034. }),
  1035. NewExpression: fill(function(node, scope, c, out, name) {
  1036. if (node.callee.type == "Identifier" && node.callee.name in scope.props)
  1037. maybeInstantiate(scope, 20);
  1038. for (var i = 0, args = []; i < node.arguments.length; ++i)
  1039. args.push(infer(node.arguments[i], scope, c));
  1040. var callee = infer(node.callee, scope, c);
  1041. var self = new AVal;
  1042. callee.propagate(new IsCtor(self, name && /\.prototype$/.test(name)));
  1043. self.propagate(out, WG_NEW_INSTANCE);
  1044. callee.propagate(new IsCallee(self, args, node.arguments, new IfObj(out)));
  1045. }),
  1046. CallExpression: fill(function(node, scope, c, out) {
  1047. for (var i = 0, args = []; i < node.arguments.length; ++i)
  1048. args.push(infer(node.arguments[i], scope, c));
  1049. if (node.callee.type == "MemberExpression") {
  1050. var self = infer(node.callee.object, scope, c);
  1051. var pName = propName(node.callee, scope, c);
  1052. if ((pName == "call" || pName == "apply") &&
  1053. scope.fnType && scope.fnType.args.indexOf(self) > -1)
  1054. maybeInstantiate(scope, 30);
  1055. self.propagate(new HasMethodCall(pName, args, node.arguments, out));
  1056. } else {
  1057. var callee = infer(node.callee, scope, c);
  1058. if (scope.fnType && scope.fnType.args.indexOf(callee) > -1)
  1059. maybeInstantiate(scope, 30);
  1060. var knownFn = callee.getFunctionType();
  1061. if (knownFn && knownFn.instantiateScore && scope.fnType)
  1062. maybeInstantiate(scope, knownFn.instantiateScore / 5);
  1063. callee.propagate(new IsCallee(cx.topScope, args, node.arguments, out));
  1064. }
  1065. }),
  1066. MemberExpression: fill(function(node, scope, c, out) {
  1067. var name = propName(node, scope);
  1068. var obj = infer(node.object, scope, c);
  1069. var prop = obj.getProp(name);
  1070. if (name == "<i>") {
  1071. var propType = infer(node.property, scope, c);
  1072. if (!propType.hasType(cx.num))
  1073. return prop.propagate(out, WG_MULTI_MEMBER);
  1074. }
  1075. prop.propagate(out);
  1076. }),
  1077. Identifier: ret(function(node, scope) {
  1078. if (node.name == "arguments" && scope.fnType && !(node.name in scope.props))
  1079. scope.defProp(node.name, scope.fnType.originNode)
  1080. .addType(new Arr(scope.fnType.arguments = new AVal));
  1081. return scope.getProp(node.name);
  1082. }),
  1083. ThisExpression: ret(function(_node, scope) {
  1084. return scope.fnType ? scope.fnType.self : cx.topScope;
  1085. }),
  1086. Literal: ret(function(node) {
  1087. return literalType(node);
  1088. })
  1089. };
  1090. function infer(node, scope, c, out, name) {
  1091. return inferExprVisitor[node.type](node, scope, c, out, name);
  1092. }
  1093. var inferWrapper = walk.make({
  1094. Expression: function(node, scope, c) {
  1095. infer(node, scope, c, ANull);
  1096. },
  1097. FunctionDeclaration: function(node, scope, c) {
  1098. var inner = node.body.scope, fn = inner.fnType;
  1099. c(node.body, scope, "ScopeBody");
  1100. maybeTagAsInstantiated(node, inner) || maybeTagAsGeneric(inner);
  1101. var prop = scope.getProp(node.id.name);
  1102. prop.addType(fn);
  1103. },
  1104. VariableDeclaration: function(node, scope, c) {
  1105. for (var i = 0; i < node.declarations.length; ++i) {
  1106. var decl = node.declarations[i], prop = scope.getProp(decl.id.name);
  1107. if (decl.init)
  1108. infer(decl.init, scope, c, prop, decl.id.name);
  1109. }
  1110. },
  1111. ReturnStatement: function(node, scope, c) {
  1112. if (!node.argument) return;
  1113. var output = ANull;
  1114. if (scope.fnType) {
  1115. if (scope.fnType.retval == ANull) scope.fnType.retval = new AVal;
  1116. output = scope.fnType.retval;
  1117. }
  1118. infer(node.argument, scope, c, output);
  1119. },
  1120. ForInStatement: function(node, scope, c) {
  1121. var source = infer(node.right, scope, c);
  1122. if ((node.right.type == "Identifier" && node.right.name in scope.props) ||
  1123. (node.right.type == "MemberExpression" && node.right.property.name == "prototype")) {
  1124. maybeInstantiate(scope, 5);
  1125. var varName;
  1126. if (node.left.type == "Identifier") {
  1127. varName = node.left.name;
  1128. } else if (node.left.type == "VariableDeclaration") {
  1129. varName = node.left.declarations[0].id.name;
  1130. }
  1131. if (varName && varName in scope.props)
  1132. scope.getProp(varName).iteratesOver = source;
  1133. }
  1134. c(node.body, scope, "Statement");
  1135. },
  1136. ScopeBody: function(node, scope, c) { c(node, node.scope || scope); }
  1137. });
  1138. // PARSING
  1139. function runPasses(passes, pass) {
  1140. var arr = passes && passes[pass];
  1141. var args = Array.prototype.slice.call(arguments, 2);
  1142. if (arr) for (var i = 0; i < arr.length; ++i) arr[i].apply(null, args);
  1143. }
  1144. var parse = exports.parse = function(text, passes, options) {
  1145. var ast;
  1146. try { ast = acorn.parse(text, options); }
  1147. catch(e) { ast = acorn_loose.parse_dammit(text, options); }
  1148. runPasses(passes, "postParse", ast, text);
  1149. return ast;
  1150. };
  1151. // ANALYSIS INTERFACE
  1152. exports.analyze = function(ast, name, scope, passes) {
  1153. if (typeof ast == "string") ast = parse(ast);
  1154. if (!name) name = "file#" + cx.origins.length;
  1155. exports.addOrigin(cx.curOrigin = name);
  1156. if (!scope) scope = cx.topScope;
  1157. walk.recursive(ast, scope, null, scopeGatherer);
  1158. runPasses(passes, "preInfer", ast, scope);
  1159. walk.recursive(ast, scope, null, inferWrapper);
  1160. runPasses(passes, "postInfer", ast, scope);
  1161. cx.curOrigin = null;
  1162. };
  1163. // PURGING
  1164. exports.purge = function(origins, start, end) {
  1165. var test = makePredicate(origins, start, end);
  1166. ++cx.purgeGen;
  1167. cx.topScope.purge(test);
  1168. for (var prop in cx.props) {
  1169. var list = cx.props[prop];
  1170. for (var i = 0; i < list.length; ++i) {
  1171. var obj = list[i], av = obj.props[prop];
  1172. if (!av || test(av, av.originNode)) list.splice(i--, 1);
  1173. }
  1174. if (!list.length) delete cx.props[prop];
  1175. }
  1176. };
  1177. function makePredicate(origins, start, end) {
  1178. var arr = Array.isArray(origins);
  1179. if (arr && origins.length == 1) { origins = origins[0]; arr = false; }
  1180. if (arr) {
  1181. if (end == null) return function(n) { return origins.indexOf(n.origin) > -1; };
  1182. return function(n, pos) { return pos && pos.start >= start && pos.end <= end && origins.indexOf(n.origin) > -1; };
  1183. } else {
  1184. if (end == null) return function(n) { return n.origin == origins; };
  1185. return function(n, pos) { return pos && pos.start >= start && pos.end <= end && n.origin == origins; };
  1186. }
  1187. }
  1188. AVal.prototype.purge = function(test) {
  1189. if (this.purgeGen == cx.purgeGen) return;
  1190. this.purgeGen = cx.purgeGen;
  1191. for (var i = 0; i < this.types.length; ++i) {
  1192. var type = this.types[i];
  1193. if (test(type, type.originNode))
  1194. this.types.splice(i--, 1);
  1195. else
  1196. type.purge(test);
  1197. }
  1198. if (this.forward) for (var i = 0; i < this.forward.length; ++i) {
  1199. var f = this.forward[i];
  1200. if (test(f)) {
  1201. this.forward.splice(i--, 1);
  1202. if (this.props) this.props = null;
  1203. } else if (f.purge) {
  1204. f.purge(test);
  1205. }
  1206. }
  1207. };
  1208. ANull.purge = function() {};
  1209. Obj.prototype.purge = function(test) {
  1210. if (this.purgeGen == cx.purgeGen) return true;
  1211. this.purgeGen = cx.purgeGen;
  1212. for (var p in this.props) {
  1213. var av = this.props[p];
  1214. if (test(av, av.originNode))
  1215. this.removeProp(p);
  1216. av.purge(test);
  1217. }
  1218. };
  1219. Fn.prototype.purge = function(test) {
  1220. if (Obj.prototype.purge.call(this, test)) return;
  1221. this.self.purge(test);
  1222. this.retval.purge(test);
  1223. for (var i = 0; i < this.args.length; ++i) this.args[i].purge(test);
  1224. };
  1225. // EXPRESSION TYPE DETERMINATION
  1226. function findByPropertyName(name) {
  1227. guessing = true;
  1228. var found = objsWithProp(name);
  1229. if (found) for (var i = 0; i < found.length; ++i) {
  1230. var val = found[i].getProp(name);
  1231. if (!val.isEmpty()) return val;
  1232. }
  1233. return ANull;
  1234. }
  1235. var typeFinder = {
  1236. ArrayExpression: function(node, scope) {
  1237. var eltval = new AVal;
  1238. for (var i = 0; i < node.elements.length; ++i) {
  1239. var elt = node.elements[i];
  1240. if (elt) findType(elt, scope).propagate(eltval);
  1241. }
  1242. return new Arr(eltval);
  1243. },
  1244. ObjectExpression: function(node) {
  1245. return node.objType;
  1246. },
  1247. FunctionExpression: function(node) {
  1248. return node.body.scope.fnType;
  1249. },
  1250. SequenceExpression: function(node, scope) {
  1251. return findType(node.expressions[node.expressions.length-1], scope);
  1252. },
  1253. UnaryExpression: function(node) {
  1254. return unopResultType(node.operator);
  1255. },
  1256. UpdateExpression: function() {
  1257. return cx.num;
  1258. },
  1259. BinaryExpression: function(node, scope) {
  1260. if (binopIsBoolean(node.operator)) return cx.bool;
  1261. if (node.operator == "+") {
  1262. var lhs = findType(node.left, scope);
  1263. var rhs = findType(node.right, scope);
  1264. if (lhs.hasType(cx.str) || rhs.hasType(cx.str)) return cx.str;
  1265. }
  1266. return cx.num;
  1267. },
  1268. AssignmentExpression: function(node, scope) {
  1269. return findType(node.right, scope);
  1270. },
  1271. LogicalExpression: function(node, scope) {
  1272. var lhs = findType(node.left, scope);
  1273. return lhs.isEmpty() ? findType(node.right, scope) : lhs;
  1274. },
  1275. ConditionalExpression: function(node, scope) {
  1276. var lhs = findType(node.consequent, scope);
  1277. return lhs.isEmpty() ? findType(node.alternate, scope) : lhs;
  1278. },
  1279. NewExpression: function(node, scope) {
  1280. var f = findType(node.callee, scope).getFunctionType();
  1281. var proto = f && f.getProp("prototype").getObjType();
  1282. if (!proto) return ANull;
  1283. return getInstance(proto, f);
  1284. },
  1285. CallExpression: function(node, scope) {
  1286. var f = findType(node.callee, scope).getFunctionType();
  1287. if (!f) return ANull;
  1288. if (f.computeRet) {
  1289. for (var i = 0, args = []; i < node.arguments.length; ++i)
  1290. args.push(findType(node.arguments[i], scope));
  1291. var self = ANull;
  1292. if (node.callee.type == "MemberExpression")
  1293. self = findType(node.callee.object, scope);
  1294. return f.computeRet(self, args, node.arguments);
  1295. } else {
  1296. return f.retval;
  1297. }
  1298. },
  1299. MemberExpression: function(node, scope) {
  1300. var propN = propName(node, scope), obj = findType(node.object, scope).getType();
  1301. if (obj) return obj.getProp(propN);
  1302. if (propN == "<i>") return ANull;
  1303. return findByPropertyName(propN);
  1304. },
  1305. Identifier: function(node, scope) {
  1306. return scope.hasProp(node.name) || ANull;
  1307. },
  1308. ThisExpression: function(_node, scope) {
  1309. return scope.fnType ? scope.fnType.self : cx.topScope;
  1310. },
  1311. Literal: function(node) {
  1312. return literalType(node);
  1313. }
  1314. };
  1315. function findType(node, scope) {
  1316. return typeFinder[node.type](node, scope);
  1317. }
  1318. var searchVisitor = exports.searchVisitor = walk.make({
  1319. Function: function(node, _st, c) {
  1320. var scope = node.body.scope;
  1321. if (node.id) c(node.id, scope);
  1322. for (var i = 0; i < node.params.length; ++i)
  1323. c(node.params[i], scope);
  1324. c(node.body, scope, "ScopeBody");
  1325. },
  1326. TryStatement: function(node, st, c) {
  1327. if (node.handler)
  1328. c(node.handler.param, st);
  1329. walk.base.TryStatement(node, st, c);
  1330. },
  1331. VariableDeclaration: function(node, st, c) {
  1332. for (var i = 0; i < node.declarations.length; ++i) {
  1333. var decl = node.declarations[i];
  1334. c(decl.id, st);
  1335. if (decl.init) c(decl.init, st, "Expression");
  1336. }
  1337. }
  1338. });
  1339. exports.fullVisitor = walk.make({
  1340. MemberExpression: function(node, st, c) {
  1341. c(node.object, st, "Expression");
  1342. c(node.property, st, node.computed ? "Expression" : null);
  1343. },
  1344. ObjectExpression: function(node, st, c) {
  1345. for (var i = 0; i < node.properties.length; ++i) {
  1346. c(node.properties[i].value, st, "Expression");
  1347. c(node.properties[i].key, st);
  1348. }
  1349. }
  1350. }, searchVisitor);
  1351. exports.findExpressionAt = function(ast, start, end, defaultScope, filter) {
  1352. var test = filter || function(_t, node) {
  1353. if (node.type == "Identifier" && node.name == "✖") return false;
  1354. return typeFinder.hasOwnProperty(node.type);
  1355. };
  1356. return walk.findNodeAt(ast, start, end, test, searchVisitor, defaultScope || cx.topScope);
  1357. };
  1358. exports.findExpressionAround = function(ast, start, end, defaultScope, filter) {
  1359. var test = filter || function(_t, node) {
  1360. if (start != null && node.start > start) return false;
  1361. if (node.type == "Identifier" && node.name == "✖") return false;
  1362. return typeFinder.hasOwnProperty(node.type);
  1363. };
  1364. return walk.findNodeAround(ast, end, test, searchVisitor, defaultScope || cx.topScope);
  1365. };
  1366. exports.expressionType = function(found) {
  1367. return findType(found.node, found.state);
  1368. };
  1369. // Finding the expected type of something, from context
  1370. exports.parentNode = function(child, ast) {
  1371. var stack = [];
  1372. function c(node, st, override) {
  1373. if (node.start <= child.start && node.end >= child.end) {
  1374. var top = stack[stack.length - 1];
  1375. if (node == child) throw {found: top};
  1376. if (top != node) stack.push(node);
  1377. walk.base[override || node.type](node, st, c);
  1378. if (top != node) stack.pop();
  1379. }
  1380. }
  1381. try {
  1382. c(ast, null);
  1383. } catch (e) {
  1384. if (e.found) return e.found;
  1385. throw e;
  1386. }
  1387. };
  1388. var findTypeFromContext = {
  1389. ArrayExpression: function(parent, _, get) { return get(parent, true).getProp("<i>"); },
  1390. ObjectExpression: function(parent, node, get) {
  1391. for (var i = 0; i < parent.properties.length; ++i) {
  1392. var prop = node.properties[i];
  1393. if (prop.value == node)
  1394. return get(parent, true).getProp(prop.key.name);
  1395. }
  1396. },
  1397. UnaryExpression: function(parent) { return unopResultType(parent.operator); },
  1398. UpdateExpression: function() { return cx.num; },
  1399. BinaryExpression: function(parent) { return binopIsBoolean(parent.operator) ? cx.bool : cx.num; },
  1400. AssignmentExpression: function(parent, _, get) { return get(parent.left); },
  1401. LogicalExpression: function(parent, _, get) { return get(parent, true); },
  1402. ConditionalExpression: function(parent, node, get) {
  1403. if (parent.consequent == node || parent.alternate == node) return get(parent, true);
  1404. },
  1405. NewExpression: function(parent, node, get) {
  1406. return this.CallExpression(parent, node, get);
  1407. },
  1408. CallExpression: function(parent, node, get) {
  1409. for (var i = 0; i < parent.arguments.length; i++) {
  1410. var arg = parent.arguments[i];
  1411. if (arg == node) {
  1412. var calleeType = get(parent.callee).getFunctionType();
  1413. if (calleeType instanceof Fn)
  1414. return calleeType.args[i];
  1415. break;
  1416. }
  1417. }
  1418. },
  1419. ReturnStatement: function(_parent, node, get) {
  1420. var fnNode = walk.findNodeAround(node.sourceFile.ast, node.start, "Function");
  1421. if (fnNode) {
  1422. var fnType = fnNode.node.type == "FunctionExpression"
  1423. ? get(fnNode.node, true).getFunctionType()
  1424. : fnNode.node.body.scope.fnType;
  1425. if (fnType) return fnType.retval.getType();
  1426. }
  1427. },
  1428. VariableDeclaration: function(parent, node, get) {
  1429. for (var i = 0; i < parent.declarations.length; i++) {
  1430. var decl = parent.declarations[i];
  1431. if (decl.init == node) return get(decl.id);
  1432. }
  1433. }
  1434. };
  1435. exports.typeFromContext = function(ast, found) {
  1436. var parent = exports.parentNode(found.node, ast);
  1437. var type = null;
  1438. if (findTypeFromContext.hasOwnProperty(parent.type)) {
  1439. type = findTypeFromContext[parent.type](parent, found.node, function(node, fromContext) {
  1440. var obj = {node: node, state: found.state};
  1441. var tp = fromContext ? exports.typeFromContext(ast, obj) : exports.expressionType(obj);
  1442. return tp || ANull;
  1443. });
  1444. }
  1445. return type || exports.expressionType(found);
  1446. };
  1447. // Flag used to indicate that some wild guessing was used to produce
  1448. // a type or set of completions.
  1449. var guessing = false;
  1450. exports.resetGuessing = function(val) { guessing = val; };
  1451. exports.didGuess = function() { return guessing; };
  1452. exports.forAllPropertiesOf = function(type, f) {
  1453. type.gatherProperties(f, 0);
  1454. };
  1455. var refFindWalker = walk.make({}, searchVisitor);
  1456. exports.findRefs = function(ast, baseScope, name, refScope, f) {
  1457. refFindWalker.Identifier = function(node, scope) {
  1458. if (node.name != name) return;
  1459. for (var s = scope; s; s = s.prev) {
  1460. if (s == refScope) f(node, scope);
  1461. if (name in s.props) return;
  1462. }
  1463. };
  1464. walk.recursive(ast, baseScope, null, refFindWalker);
  1465. };
  1466. var simpleWalker = walk.make({
  1467. Function: function(node, _st, c) { c(node.body, node.body.scope, "ScopeBody"); }
  1468. });
  1469. exports.findPropRefs = function(ast, scope, objType, propName, f) {
  1470. walk.simple(ast, {
  1471. MemberExpression: function(node, scope) {
  1472. if (node.computed || node.property.name != propName) return;
  1473. if (findType(node.object, scope).getType() == objType) f(node.property);
  1474. },
  1475. ObjectExpression: function(node, scope) {
  1476. if (findType(node, scope).getType() != objType) return;
  1477. for (var i = 0; i < node.properties.length; ++i)
  1478. if (node.properties[i].key.name == propName) f(node.properties[i].key);
  1479. }
  1480. }, simpleWalker, scope);
  1481. };
  1482. // LOCAL-VARIABLE QUERIES
  1483. var scopeAt = exports.scopeAt = function(ast, pos, defaultScope) {
  1484. var found = walk.findNodeAround(ast, pos, function(tp, node) {
  1485. return tp == "ScopeBody" && node.scope;
  1486. });
  1487. if (found) return found.node.scope;
  1488. else return defaultScope || cx.topScope;
  1489. };
  1490. exports.forAllLocalsAt = function(ast, pos, defaultScope, f) {
  1491. var scope = scopeAt(ast, pos, defaultScope);
  1492. scope.gatherProperties(f, 0);
  1493. };
  1494. // INIT DEF MODULE
  1495. // Delayed initialization because of cyclic dependencies.
  1496. def = exports.def = def.init({}, exports);
  1497. });