def.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589
  1. // Type description parser
  2. //
  3. // Type description JSON files (such as ecma5.json and browser.json)
  4. // are used to
  5. //
  6. // A) describe types that come from native code
  7. //
  8. // B) to cheaply load the types for big libraries, or libraries that
  9. // can't be inferred well
  10. (function(mod) {
  11. if (typeof exports == "object" && typeof module == "object") // CommonJS
  12. return exports.init = mod;
  13. if (typeof define == "function" && define.amd) // AMD
  14. return define({init: mod});
  15. tern.def = {init: mod};
  16. })(function(exports, infer) {
  17. "use strict";
  18. function hop(obj, prop) {
  19. return Object.prototype.hasOwnProperty.call(obj, prop);
  20. }
  21. var TypeParser = exports.TypeParser = function(spec, start, base, forceNew) {
  22. this.pos = start || 0;
  23. this.spec = spec;
  24. this.base = base;
  25. this.forceNew = forceNew;
  26. };
  27. function unwrapType(type, self, args) {
  28. return type.call ? type(self, args) : type;
  29. }
  30. function extractProp(type, prop) {
  31. if (prop == "!ret") {
  32. if (type.retval) return type.retval;
  33. var rv = new infer.AVal;
  34. type.propagate(new infer.IsCallee(infer.ANull, [], null, rv));
  35. return rv;
  36. } else {
  37. return type.getProp(prop);
  38. }
  39. }
  40. function computedFunc(args, retType) {
  41. return function(self, cArgs) {
  42. var realArgs = [];
  43. for (var i = 0; i < args.length; i++) realArgs.push(unwrapType(args[i], self, cArgs));
  44. return new infer.Fn(name, infer.ANull, realArgs, unwrapType(retType, self, cArgs));
  45. };
  46. }
  47. function computedUnion(types) {
  48. return function(self, args) {
  49. var union = new infer.AVal;
  50. for (var i = 0; i < types.length; i++) unwrapType(types[i], self, args).propagate(union);
  51. return union;
  52. };
  53. }
  54. function computedArray(inner) {
  55. return function(self, args) {
  56. return new infer.Arr(inner(self, args));
  57. };
  58. }
  59. TypeParser.prototype = {
  60. eat: function(str) {
  61. if (str.length == 1 ? this.spec.charAt(this.pos) == str : this.spec.indexOf(str, this.pos) == this.pos) {
  62. this.pos += str.length;
  63. return true;
  64. }
  65. },
  66. word: function(re) {
  67. var word = "", ch, re = re || /[\w$]/;
  68. while ((ch = this.spec.charAt(this.pos)) && re.test(ch)) { word += ch; ++this.pos; }
  69. return word;
  70. },
  71. error: function() {
  72. throw new Error("Unrecognized type spec: " + this.spec + " (at " + this.pos + ")");
  73. },
  74. parseFnType: function(comp, name, top) {
  75. var args = [], names = [], computed = false;
  76. if (!this.eat(")")) for (var i = 0; ; ++i) {
  77. var colon = this.spec.indexOf(": ", this.pos), argname;
  78. if (colon != -1) {
  79. argname = this.spec.slice(this.pos, colon);
  80. if (/^[$\w?]+$/.test(argname))
  81. this.pos = colon + 2;
  82. else
  83. argname = null;
  84. }
  85. names.push(argname);
  86. var argType = this.parseType(comp);
  87. if (argType.call) computed = true;
  88. args.push(argType);
  89. if (!this.eat(", ")) {
  90. this.eat(")") || this.error();
  91. break;
  92. }
  93. }
  94. var retType, computeRet, computeRetStart, fn;
  95. if (this.eat(" -> ")) {
  96. var retStart = this.pos;
  97. retType = this.parseType(true);
  98. if (retType.call) {
  99. if (top) {
  100. computeRet = retType;
  101. retType = infer.ANull;
  102. computeRetStart = retStart;
  103. } else {
  104. computed = true;
  105. }
  106. }
  107. } else {
  108. retType = infer.ANull;
  109. }
  110. if (computed) return computedFunc(args, retType);
  111. if (top && (fn = this.base))
  112. infer.Fn.call(this.base, name, infer.ANull, args, names, retType);
  113. else
  114. fn = new infer.Fn(name, infer.ANull, args, names, retType);
  115. if (computeRet) fn.computeRet = computeRet;
  116. if (computeRetStart != null) fn.computeRetSource = this.spec.slice(computeRetStart, this.pos);
  117. return fn;
  118. },
  119. parseType: function(comp, name, top) {
  120. var main = this.parseTypeMaybeProp(comp, name, top);
  121. if (!this.eat("|")) return main;
  122. var types = [main], computed = main.call;
  123. for (;;) {
  124. var next = this.parseTypeMaybeProp(comp, name, top);
  125. types.push(next);
  126. if (next.call) computed = true;
  127. if (!this.eat("|")) break;
  128. }
  129. if (computed) return computedUnion(types);
  130. var union = new infer.AVal;
  131. for (var i = 0; i < types.length; i++) types[i].propagate(union);
  132. return union;
  133. },
  134. parseTypeMaybeProp: function(comp, name, top) {
  135. var result = this.parseTypeInner(comp, name, top);
  136. while (comp && this.eat(".")) result = this.extendWithProp(result);
  137. return result;
  138. },
  139. extendWithProp: function(base) {
  140. var propName = this.word(/[\w<>$!]/) || this.error();
  141. if (base.apply) return function(self, args) {
  142. return extractProp(base(self, args), propName);
  143. };
  144. return extractProp(base, propName);
  145. },
  146. parseTypeInner: function(comp, name, top) {
  147. if (this.eat("fn(")) {
  148. return this.parseFnType(comp, name, top);
  149. } else if (this.eat("[")) {
  150. var inner = this.parseType(comp);
  151. this.eat("]") || this.error();
  152. if (inner.call) return computedArray(inner);
  153. if (top && this.base) {
  154. infer.Arr.call(this.base, inner);
  155. return this.base;
  156. }
  157. return new infer.Arr(inner);
  158. } else if (this.eat("+")) {
  159. var path = this.word(/[\w$<>\.!]/);
  160. var base = parsePath(path + ".prototype");
  161. var type;
  162. if (!(base instanceof infer.Obj)) base = parsePath(path);
  163. if (!(base instanceof infer.Obj)) return base;
  164. if (comp && this.eat("[")) return this.parsePoly(base);
  165. if (top && this.forceNew) return new infer.Obj(base);
  166. return infer.getInstance(base);
  167. } else if (comp && this.eat("!")) {
  168. var arg = this.word(/\d/);
  169. if (arg) {
  170. arg = Number(arg);
  171. return function(_self, args) {return args[arg] || infer.ANull;};
  172. } else if (this.eat("this")) {
  173. return function(self) {return self;};
  174. } else if (this.eat("custom:")) {
  175. var fname = this.word(/[\w$]/);
  176. return customFunctions[fname] || function() { return infer.ANull; };
  177. } else {
  178. return this.fromWord("!" + this.word(/[\w$<>\.!]/));
  179. }
  180. } else if (this.eat("?")) {
  181. return infer.ANull;
  182. } else {
  183. return this.fromWord(this.word(/[\w$<>\.!`]/));
  184. }
  185. },
  186. fromWord: function(spec) {
  187. var cx = infer.cx();
  188. switch (spec) {
  189. case "number": return cx.num;
  190. case "string": return cx.str;
  191. case "bool": return cx.bool;
  192. case "<top>": return cx.topScope;
  193. }
  194. if (cx.localDefs && spec in cx.localDefs) return cx.localDefs[spec];
  195. return parsePath(spec);
  196. },
  197. parsePoly: function(base) {
  198. var propName = "<i>", match;
  199. if (match = this.spec.slice(this.pos).match(/^\s*(\w+)\s*=\s*/)) {
  200. propName = match[1];
  201. this.pos += match[0].length;
  202. }
  203. var value = this.parseType(true);
  204. if (!this.eat("]")) this.error();
  205. if (value.call) return function(self, args) {
  206. var instance = infer.getInstance(base);
  207. value(self, args).propagate(instance.defProp(propName));
  208. return instance;
  209. };
  210. var instance = infer.getInstance(base);
  211. value.propagate(instance.defProp(propName));
  212. return instance;
  213. }
  214. };
  215. function parseType(spec, name, base, forceNew) {
  216. var type = new TypeParser(spec, null, base, forceNew).parseType(false, name, true);
  217. if (/^fn\(/.test(spec)) for (var i = 0; i < type.args.length; ++i) (function(i) {
  218. var arg = type.args[i];
  219. if (arg instanceof infer.Fn && arg.args && arg.args.length) addEffect(type, function(_self, fArgs) {
  220. var fArg = fArgs[i];
  221. if (fArg) fArg.propagate(new infer.IsCallee(infer.cx().topScope, arg.args, null, infer.ANull));
  222. });
  223. })(i);
  224. return type;
  225. }
  226. function addEffect(fn, handler, replaceRet) {
  227. var oldCmp = fn.computeRet, rv = fn.retval;
  228. fn.computeRet = function(self, args, argNodes) {
  229. var handled = handler(self, args, argNodes);
  230. var old = oldCmp ? oldCmp(self, args, argNodes) : rv;
  231. return replaceRet ? handled : old;
  232. };
  233. }
  234. var parseEffect = exports.parseEffect = function(effect, fn) {
  235. var m;
  236. if (effect.indexOf("propagate ") == 0) {
  237. var p = new TypeParser(effect, 10);
  238. var origin = p.parseType(true);
  239. if (!p.eat(" ")) p.error();
  240. var target = p.parseType(true);
  241. addEffect(fn, function(self, args) {
  242. unwrapType(origin, self, args).propagate(unwrapType(target, self, args));
  243. });
  244. } else if (effect.indexOf("call ") == 0) {
  245. var andRet = effect.indexOf("and return ", 5) == 5;
  246. var p = new TypeParser(effect, andRet ? 16 : 5);
  247. var getCallee = p.parseType(true), getSelf = null, getArgs = [];
  248. if (p.eat(" this=")) getSelf = p.parseType(true);
  249. while (p.eat(" ")) getArgs.push(p.parseType(true));
  250. addEffect(fn, function(self, args) {
  251. var callee = unwrapType(getCallee, self, args);
  252. var slf = getSelf ? unwrapType(getSelf, self, args) : infer.ANull, as = [];
  253. for (var i = 0; i < getArgs.length; ++i) as.push(unwrapType(getArgs[i], self, args));
  254. var result = andRet ? new infer.AVal : infer.ANull;
  255. callee.propagate(new infer.IsCallee(slf, as, null, result));
  256. return result;
  257. }, andRet);
  258. } else if (m = effect.match(/^custom (\S+)\s*(.*)/)) {
  259. var customFunc = customFunctions[m[1]];
  260. if (customFunc) addEffect(fn, m[2] ? customFunc(m[2]) : customFunc);
  261. } else if (effect.indexOf("copy ") == 0) {
  262. var p = new TypeParser(effect, 5);
  263. var getFrom = p.parseType(true);
  264. p.eat(" ");
  265. var getTo = p.parseType(true);
  266. addEffect(fn, function(self, args) {
  267. var from = unwrapType(getFrom, self, args), to = unwrapType(getTo, self, args);
  268. from.forAllProps(function(prop, val, local) {
  269. if (local && prop != "<i>")
  270. to.propagate(new infer.PropHasSubset(prop, val));
  271. });
  272. });
  273. } else {
  274. throw new Error("Unknown effect type: " + effect);
  275. }
  276. };
  277. var currentTopScope;
  278. var parsePath = exports.parsePath = function(path, scope) {
  279. var cx = infer.cx(), cached = cx.paths[path], origPath = path;
  280. if (cached != null) return cached;
  281. cx.paths[path] = infer.ANull;
  282. var base = scope || currentTopScope || cx.topScope;
  283. if (cx.localDefs) for (var name in cx.localDefs) {
  284. if (path.indexOf(name) == 0) {
  285. if (path == name) return cx.paths[path] = cx.localDefs[path];
  286. if (path.charAt(name.length) == ".") {
  287. base = cx.localDefs[name];
  288. path = path.slice(name.length + 1);
  289. break;
  290. }
  291. }
  292. }
  293. var parts = path.split(".");
  294. for (var i = 0; i < parts.length && base != infer.ANull; ++i) {
  295. var prop = parts[i];
  296. if (prop.charAt(0) == "!") {
  297. if (prop == "!proto") {
  298. base = (base instanceof infer.Obj && base.proto) || infer.ANull;
  299. } else {
  300. var fn = base.getFunctionType();
  301. if (!fn) {
  302. base = infer.ANull;
  303. } else if (prop == "!ret") {
  304. base = fn.retval && fn.retval.getType(false) || infer.ANull;
  305. } else {
  306. var arg = fn.args && fn.args[Number(prop.slice(1))];
  307. base = (arg && arg.getType(false)) || infer.ANull;
  308. }
  309. }
  310. } else if (base instanceof infer.Obj) {
  311. var propVal = (prop == "prototype" && base instanceof infer.Fn) ? base.getProp(prop) : base.props[prop];
  312. if (!propVal || propVal.isEmpty())
  313. base = infer.ANull;
  314. else
  315. base = propVal.types[0];
  316. }
  317. }
  318. // Uncomment this to get feedback on your poorly written .json files
  319. // if (base == infer.ANull) console.error("bad path: " + origPath + " (" + cx.curOrigin + ")");
  320. cx.paths[origPath] = base == infer.ANull ? null : base;
  321. return base;
  322. };
  323. function emptyObj(ctor) {
  324. var empty = Object.create(ctor.prototype);
  325. empty.props = Object.create(null);
  326. empty.isShell = true;
  327. return empty;
  328. }
  329. function isSimpleAnnotation(spec) {
  330. if (!spec["!type"] || /^(fn\(|\[)/.test(spec["!type"])) return false;
  331. for (var prop in spec)
  332. if (prop != "!type" && prop != "!doc" && prop != "!url" && prop != "!span" && prop != "!data")
  333. return false;
  334. return true;
  335. }
  336. function passOne(base, spec, path) {
  337. if (!base) {
  338. var tp = spec["!type"];
  339. if (tp) {
  340. if (/^fn\(/.test(tp)) base = emptyObj(infer.Fn);
  341. else if (tp.charAt(0) == "[") base = emptyObj(infer.Arr);
  342. else throw new Error("Invalid !type spec: " + tp);
  343. } else if (spec["!stdProto"]) {
  344. base = infer.cx().protos[spec["!stdProto"]];
  345. } else {
  346. base = emptyObj(infer.Obj);
  347. }
  348. base.name = path;
  349. }
  350. for (var name in spec) if (hop(spec, name) && name.charCodeAt(0) != 33) {
  351. var inner = spec[name];
  352. if (typeof inner == "string" || isSimpleAnnotation(inner)) continue;
  353. var prop = base.defProp(name);
  354. passOne(prop.getObjType(), inner, path ? path + "." + name : name).propagate(prop);
  355. }
  356. return base;
  357. }
  358. function passTwo(base, spec, path) {
  359. if (base.isShell) {
  360. delete base.isShell;
  361. var tp = spec["!type"];
  362. if (tp) {
  363. parseType(tp, path, base);
  364. } else {
  365. var proto = spec["!proto"] && parseType(spec["!proto"]);
  366. infer.Obj.call(base, proto instanceof infer.Obj ? proto : true, path);
  367. }
  368. }
  369. var effects = spec["!effects"];
  370. if (effects && base instanceof infer.Fn) for (var i = 0; i < effects.length; ++i)
  371. parseEffect(effects[i], base);
  372. copyInfo(spec, base);
  373. for (var name in spec) if (hop(spec, name) && name.charCodeAt(0) != 33) {
  374. var inner = spec[name], known = base.defProp(name), innerPath = path ? path + "." + name : name;
  375. if (typeof inner == "string") {
  376. if (known.isEmpty()) parseType(inner, innerPath).propagate(known);
  377. } else {
  378. if (!isSimpleAnnotation(inner))
  379. passTwo(known.getObjType(), inner, innerPath);
  380. else if (known.isEmpty())
  381. parseType(inner["!type"], innerPath, null, true).propagate(known);
  382. else
  383. continue;
  384. if (inner["!doc"]) known.doc = inner["!doc"];
  385. if (inner["!url"]) known.url = inner["!url"];
  386. if (inner["!span"]) known.span = inner["!span"];
  387. }
  388. }
  389. return base;
  390. }
  391. function copyInfo(spec, type) {
  392. if (spec["!doc"]) type.doc = spec["!doc"];
  393. if (spec["!url"]) type.url = spec["!url"];
  394. if (spec["!span"]) type.span = spec["!span"];
  395. if (spec["!data"]) type.metaData = spec["!data"];
  396. }
  397. function runPasses(type, arg) {
  398. var parent = infer.cx().parent, pass = parent && parent.passes && parent.passes[type];
  399. if (pass) for (var i = 0; i < pass.length; i++) pass[i](arg);
  400. }
  401. function doLoadEnvironment(data, scope) {
  402. var cx = infer.cx();
  403. infer.addOrigin(cx.curOrigin = data["!name"] || "env#" + cx.origins.length);
  404. cx.localDefs = cx.definitions[cx.curOrigin] = Object.create(null);
  405. runPasses("preLoadDef", data);
  406. passOne(scope, data);
  407. var def = data["!define"];
  408. if (def) {
  409. for (var name in def) {
  410. var spec = def[name];
  411. cx.localDefs[name] = typeof spec == "string" ? parsePath(spec) : passOne(null, spec, name);
  412. }
  413. for (var name in def) {
  414. var spec = def[name];
  415. if (typeof spec != "string") passTwo(cx.localDefs[name], def[name], name);
  416. }
  417. }
  418. passTwo(scope, data);
  419. runPasses("postLoadDef", data);
  420. cx.curOrigin = cx.localDefs = null;
  421. }
  422. exports.load = function(data, scope) {
  423. if (!scope) scope = infer.cx().topScope;
  424. var oldScope = currentTopScope;
  425. currentTopScope = scope;
  426. try {
  427. doLoadEnvironment(data, scope);
  428. } finally {
  429. currentTopScope = oldScope;
  430. }
  431. };
  432. exports.parse = function(data, origin, path) {
  433. var cx = infer.cx();
  434. if (origin) {
  435. cx.origin = origin;
  436. cx.localDefs = cx.definitions[origin];
  437. }
  438. try {
  439. if (typeof data == "string")
  440. return parseType(data, path);
  441. else
  442. return passTwo(passOne(null, data, path), data, path);
  443. } finally {
  444. if (origin) cx.origin = cx.localDefs = null;
  445. }
  446. };
  447. // Used to register custom logic for more involved effect or type
  448. // computation.
  449. var customFunctions = Object.create(null);
  450. infer.registerFunction = function(name, f) { customFunctions[name] = f; };
  451. var IsCreated = infer.constraint("created, target, spec", {
  452. addType: function(tp) {
  453. if (tp instanceof infer.Obj && this.created++ < 5) {
  454. var derived = new infer.Obj(tp), spec = this.spec;
  455. if (spec instanceof infer.AVal) spec = spec.getObjType(false);
  456. if (spec instanceof infer.Obj) for (var prop in spec.props) {
  457. var cur = spec.props[prop].types[0];
  458. var p = derived.defProp(prop);
  459. if (cur && cur instanceof infer.Obj && cur.props.value) {
  460. var vtp = cur.props.value.getType(false);
  461. if (vtp) p.addType(vtp);
  462. }
  463. }
  464. this.target.addType(derived);
  465. }
  466. }
  467. });
  468. infer.registerFunction("Object_create", function(_self, args, argNodes) {
  469. if (argNodes && argNodes.length && argNodes[0].type == "Literal" && argNodes[0].value == null)
  470. return new infer.Obj();
  471. var result = new infer.AVal;
  472. if (args[0]) args[0].propagate(new IsCreated(0, result, args[1]));
  473. return result;
  474. });
  475. var PropSpec = infer.constraint("target", {
  476. addType: function(tp) {
  477. if (!(tp instanceof infer.Obj)) return;
  478. if (tp.hasProp("value"))
  479. tp.getProp("value").propagate(this.target);
  480. else if (tp.hasProp("get"))
  481. tp.getProp("get").propagate(new infer.IsCallee(infer.ANull, [], null, this.target));
  482. }
  483. });
  484. infer.registerFunction("Object_defineProperty", function(_self, args, argNodes) {
  485. if (argNodes && argNodes.length >= 3 && argNodes[1].type == "Literal" &&
  486. typeof argNodes[1].value == "string") {
  487. var obj = args[0], connect = new infer.AVal;
  488. obj.propagate(new infer.PropHasSubset(argNodes[1].value, connect, argNodes[1]));
  489. args[2].propagate(new PropSpec(connect));
  490. }
  491. return infer.ANull;
  492. });
  493. infer.registerFunction("Object_defineProperties", function(_self, args, argNodes) {
  494. if (args.length >= 2) {
  495. var obj = args[0];
  496. args[1].forAllProps(function(prop, val, local) {
  497. if (!local) return;
  498. var connect = new infer.AVal;
  499. obj.propagate(new infer.PropHasSubset(prop, connect, argNodes && argNodes[1]));
  500. val.propagate(new PropSpec(connect));
  501. });
  502. }
  503. return infer.ANull;
  504. });
  505. var IsBound = infer.constraint("self, args, target", {
  506. addType: function(tp) {
  507. if (!(tp instanceof infer.Fn)) return;
  508. this.target.addType(new infer.Fn(tp.name, infer.ANull, tp.args.slice(this.args.length),
  509. tp.argNames.slice(this.args.length), tp.retval));
  510. this.self.propagate(tp.self);
  511. for (var i = 0; i < Math.min(tp.args.length, this.args.length); ++i)
  512. this.args[i].propagate(tp.args[i]);
  513. }
  514. });
  515. infer.registerFunction("Function_bind", function(self, args) {
  516. if (!args.length) return infer.ANull;
  517. var result = new infer.AVal;
  518. self.propagate(new IsBound(args[0], args.slice(1), result));
  519. return result;
  520. });
  521. infer.registerFunction("Array_ctor", function(_self, args) {
  522. var arr = new infer.Arr;
  523. if (args.length != 1 || !args[0].hasType(infer.cx().num)) {
  524. var content = arr.getProp("<i>");
  525. for (var i = 0; i < args.length; ++i) args[i].propagate(content);
  526. }
  527. return arr;
  528. });
  529. infer.registerFunction("Promise_ctor", function(_self, args, argNodes) {
  530. if (args.length < 1) return infer.ANull;
  531. var self = new infer.Obj(infer.cx().definitions.ecma6["Promise.prototype"]);
  532. var valProp = self.defProp("value", argNodes && argNodes[0]);
  533. var valArg = new infer.AVal;
  534. valArg.propagate(valProp);
  535. var exec = new infer.Fn("execute", infer.ANull, [valArg], ["value"], infer.ANull);
  536. var reject = infer.cx().definitions.ecma6.promiseReject;
  537. args[0].propagate(new infer.IsCallee(infer.ANull, [exec, reject], null, infer.ANull));
  538. return self;
  539. });
  540. return exports;
  541. });