pytifa.js 56 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612
  1. /**
  2. * Python Type Inferencer and Flow Analyzer
  3. * TIFA
  4. *
  5. * Depends on Skulpt
  6. *
  7. * TIFA uses a number of simplifications of the Python language.
  8. * * Variables cannot change type
  9. * * Variables cannot be deleted
  10. * * Complex types have to be homogenous
  11. * * No introspection or reflective characteristics
  12. * * No dunder methods
  13. * * No closures (maybe?)
  14. * * You cannot write a variable out of scope
  15. * * You cannot read a mutable variable out of scope
  16. * * No multiple inheritance
  17. *
  18. * Additionally, it reads the following as issues:
  19. * * Cannot read a variable without having first written to it.
  20. * * Cannot rewrite a variable unless it has been read.
  21. *
  22. * @constructor
  23. * @this {Tifa}
  24. */
  25. function Tifa() {
  26. NodeVisitor.apply(this, Array.prototype.slice.call(arguments));
  27. }
  28. Tifa.prototype = new NodeVisitor();
  29. /**
  30. * Processes the AST of the given source code to generate a report.
  31. *
  32. * @param {string} code - The Python source code
  33. * @param {string} filename - Optional, the filename (defaults to __main__)
  34. */
  35. Tifa.prototype.processCode = function(code, filename) {
  36. // Code
  37. this.source = code !== "" ? code.split("\n") : [];
  38. filename = filename || '__main__';
  39. // Attempt parsing - might fail!
  40. var parse, ast;
  41. try {
  42. parse = Sk.parse(filename, code);
  43. ast = Sk.astFromParse(parse.cst, filename, parse.flags);
  44. } catch (error) {
  45. this.report = {"success": false,
  46. "error": error,
  47. "issues": {},
  48. "variables": {}};
  49. return this.report;
  50. }
  51. try {
  52. return this.processAst(ast);
  53. } catch (error) {
  54. this.report = {"success": false,
  55. "error": error,
  56. "issues": {},
  57. "variables": {}};
  58. return this.report;
  59. }
  60. }
  61. /**
  62. * Given an AST, actually performs the type and flow analyses to return a
  63. * report.
  64. *
  65. * @constructor
  66. * @this {BlockPyEditor}
  67. * @param {Object} main - The main BlockPy instance
  68. * @param {HTMLElement} tag - The HTML object this is attached to.
  69. */
  70. Tifa.prototype.processAst = function(ast) {
  71. // Unique Global IDs
  72. this.PathId = 0;
  73. this.ScopeId = 0;
  74. this.AstId = 0;
  75. // Human readable names
  76. this.pathNames = ['*Module'];
  77. this.scopeNames = ['*Module'];
  78. this.astNames = [];
  79. // Complete record of all Names
  80. this.scopeChain = [this.ScopeId];
  81. this.pathChain = [this.PathId];
  82. this.nameMap = {};
  83. this.nameMap[this.PathId] = {};
  84. // Initialize a new, empty report
  85. this.initializeReport();
  86. // Traverse every node
  87. this.visit(ast);
  88. this.report.variables = this.nameMap;
  89. // Check afterwards
  90. this.finishScope();
  91. this.report.topLevelVariables = {};
  92. var mainPathVars = this.nameMap[this.pathChain[0]]
  93. for (var fullName in mainPathVars) {
  94. var splitName = fullName.split("/");
  95. if (splitName.length == 2 && splitName[0] == this.scopeChain[0]) {
  96. this.report.topLevelVariables[splitName[1]] = mainPathVars[fullName];
  97. }
  98. }
  99. return this.report;
  100. }
  101. Tifa.prototype.initializeReport = function() {
  102. this.report= {
  103. success: true,
  104. variables: {},
  105. issues: {
  106. "Parser Failure": [], // Complete failure to parse the code
  107. "Unconnected blocks": [], // Any names with ____
  108. "Empty Body": [], // Any use of pass on its own
  109. "Malformed Conditional": [], // An if/else with empty else or if
  110. "Unnecessary Pass": [], // Any use of pass
  111. "Unread variables": [], // A variable was not read after it was defined
  112. "Undefined variables": [], // A variable was read before it was defined
  113. "Possibly undefined variables": [], // A variable was read but was not defined in every branch
  114. "Overwritten variables": [], // A written variable was written to again before being read
  115. "Append to non-list": [], // Attempted to use the append method on a non-list
  116. "Used iteration list": [], //
  117. "Unused iteration variable": [], //
  118. "Non-list iterations": [], //
  119. "Empty iterations": [], //
  120. "Type changes": [], //
  121. "Iteration variable is iteration list": [], //
  122. "Unknown functions": [], //
  123. "Not a function": [], // Attempt to call non-function as function
  124. "Action after return": [],
  125. "Incompatible types": [], //
  126. "Return outside function": [], //
  127. "Read out of scope": [], //
  128. "Write out of scope": [], // Attempted to modify a variable in a higher scope
  129. "Aliased built-in": [], //
  130. "Method not in Type": [], // A method was used that didn't exist for that type
  131. "Submodule not found": [],
  132. "Module not found": []
  133. }
  134. }
  135. return this.report;
  136. }
  137. Tifa.prototype.reportIssue = function(issue, data) {
  138. this.report.issues[issue].push(data)
  139. }
  140. /*
  141. https://github.com/python/typeshed/blob/master/stdlib/3/builtins.pyi
  142. List
  143. append
  144. index
  145. */
  146. /*
  147. Important concepts:
  148. Assign
  149. AugAssign
  150. Import
  151. With
  152. Attribute lookup
  153. Scope:
  154. ID
  155. CallStack:
  156. Functions
  157. Lambdas
  158. Return/yield
  159. Ifs/While : Branch
  160. For loops
  161. Classes
  162. Try/except
  163. Break/continue
  164. PathId: int
  165. AstId: int
  166. ScopeId: int
  167. NameMap
  168. 0 (top) =>
  169. bid =>
  170. fully-scoped-name: State
  171. State:
  172. name: str
  173. type: Type
  174. set, read, overwrite: str {yes, no, maybe}
  175. trace: list of TraceNode
  176. TraceNode:
  177. type: Type
  178. set, read, overwrite: str {yes, no, maybe}
  179. Type:
  180. ast: str {Unknown, Num, Str, List, Tuple, Set, Dict, Bool, None}
  181. empty?: boolean
  182. literals?: boolean
  183. subtype?: Type
  184. keys?: Type
  185. values?: Type
  186. subtypes?: list of Type
  187. definition?: JS function object
  188. Report:
  189. success: bool
  190. issues: Issue => list of IssueData
  191. variables: State
  192. IssueData:
  193. name?: str
  194. scope?: str
  195. position?: int
  196. type?: Type
  197. */
  198. Tifa.prototype.visit = function(node) {
  199. // Actions after return?
  200. if (this.scopeChain.length > 1) {
  201. var returnState = this.findVariablesScope("*return");
  202. if (returnState.exists && returnState.inScope) {
  203. if (returnState.state.set == "yes") {
  204. this.reportIssue("Action after return", {"position": Tifa.locate(node)});
  205. }
  206. }
  207. }
  208. // No? All good.
  209. this.astNames.push(node._astname);
  210. this.AstId += 1;
  211. var result = NodeVisitor.prototype.visit.call(this, node);
  212. this.AstId -= 1;
  213. this.astNames.pop();
  214. if (result === undefined) {
  215. return Tifa._UNKNOWN_TYPE();
  216. } else {
  217. return result;
  218. }
  219. }
  220. Tifa.prototype.visit_Assign = function(node) {
  221. // Handle value
  222. var valueType = this.visit(node.value);
  223. // Handle targets
  224. this.visitList(node.targets);
  225. var position = Tifa.locate(node);
  226. var that = this;
  227. // TODO: Properly handle assignments with subscripts
  228. this.walkTargets(node.targets, valueType, (function action(target, type) {
  229. if (target._astname === 'Name') {
  230. that.storeVariable(target.id.v, type, position);
  231. } else if (target._astname == 'Tuple' || target._astname == 'List') {
  232. for (var i = 0, len = target.elts.length; i < len; i++) {
  233. var elt = target.elts[i];
  234. var eltType = Tifa.indexSequenceType(type, i);
  235. action(elt, eltType);
  236. }
  237. }
  238. }));
  239. }
  240. Tifa.prototype.visit_AugAssign = function(node) {
  241. // Handle value
  242. var right = this.visit(node.value);
  243. // Handle target
  244. var left = this.visit(node.target);
  245. var name = this.identifyCaller(node.target);
  246. // Handle op
  247. var position = Tifa.locate(node);
  248. // Handle operation
  249. this.loadVariable(name, position);
  250. var opLookup = Tifa.VALID_BINOP_TYPES[node.op.name];
  251. if (left.name == "Unknown" || right.name == "Unknown") {
  252. return Tifa._UNKNOWN_TYPE();
  253. } else if (opLookup) {
  254. opLookup = opLookup[left.name];
  255. if (opLookup) {
  256. opLookup = opLookup[right.name];
  257. if (opLookup) {
  258. var resultType = opLookup(left, right);
  259. this.storeVariable(name, resultType, position);
  260. return resultType;
  261. }
  262. }
  263. }
  264. this.reportIssue("Incompatible types",
  265. {"left": left, "right": right,
  266. "operation": node.op.name,
  267. "position": position});
  268. return Tifa._UNKNOWN_TYPE();
  269. }
  270. Tifa.prototype.visit_Import = function(node) {
  271. // Handle names
  272. var position = Tifa.locate(node);
  273. for (var i = 0, len = node.names.length; i < len; i++) {
  274. var module = node.names[i];
  275. var asname = module.asname === null ? module.name : module.asname;
  276. var moduleType = this.loadModule(module.name.v, position);
  277. this.storeVariable(asname.v, moduleType, position);
  278. }
  279. }
  280. Tifa.prototype.visit_ImportFrom = function(node) {
  281. // Handle names
  282. var position = Tifa.locate(node);
  283. for (var i = 0, len = node.names.length; i < len; i++) {
  284. if (node.module === null) {
  285. var alias = node.names[i];
  286. var asname = alias.asname === null ? alias.name : alias.asname;
  287. var moduleType = this.loadModule(alias.name.v, position);
  288. var nameType = this.loadBuiltinAttr(moduleType, null, alias.name.v, position);
  289. this.storeVariable(asname.v, nameType, position);
  290. } else {
  291. var moduleName = node.module.v;
  292. var alias = node.names[i];
  293. var asname = alias.asname === null ? alias.name : alias.asname;
  294. var moduleType = this.loadModule(moduleName, position);
  295. var nameType = this.loadBuiltinAttr(moduleType, null, alias.name.v, position);
  296. this.storeVariable(asname.v, nameType, position);
  297. }
  298. }
  299. }
  300. Tifa.prototype.visit_BinOp = function(node) {
  301. // Handle left and right
  302. var left = this.visit(node.left);
  303. var right = this.visit(node.right);
  304. // Handle operation
  305. var opLookup = Tifa.VALID_BINOP_TYPES[node.op.name];
  306. if (left.name == "Unknown" || right.name == "Unknown") {
  307. return Tifa._UNKNOWN_TYPE();
  308. } else if (opLookup) {
  309. opLookup = opLookup[left.name];
  310. if (opLookup) {
  311. opLookup = opLookup[right.name];
  312. if (opLookup) {
  313. return opLookup(left, right);
  314. }
  315. }
  316. }
  317. this.reportIssue("Incompatible types",
  318. {"left": left, "right": right,
  319. "operation": node.op.name,
  320. "position": Tifa.locate(node)});
  321. return Tifa._UNKNOWN_TYPE();
  322. }
  323. Tifa.prototype.visit_UnaryOp = function(node) {
  324. // Handle operand
  325. var operand = this.visit(node.operand);
  326. if (node.op.name == "Not") {
  327. return Tifa._BOOL_TYPE();
  328. } else if (operand.name == "Unknown") {
  329. return Tifa._UNKNOWN_TYPE();
  330. } else {
  331. var opLookup = Tifa.VALID_UNARYOP_TYPES[node.op.name];
  332. if (opLookup) {
  333. opLookup = opLookup[operand.name];
  334. if (opLookup) {
  335. return opLookup(operand);
  336. }
  337. }
  338. }
  339. return Tifa._UNKNOWN_TYPE();
  340. }
  341. Tifa.prototype.visit_BoolOp = function(node) {
  342. // Handle left and right
  343. var values = [];
  344. for (var i=0, len=node.values.length; i < len; i+= 1) {
  345. values[i] = this.visit(node.values[i]);
  346. }
  347. // TODO: Truthiness is not supported! Probably need a Union type
  348. // Handle operation
  349. return Tifa._BOOL_TYPE();
  350. }
  351. Tifa.prototype.visit_Compare = function(node) {
  352. // Handle left and right
  353. var left = this.visit(node.left);
  354. var comparators = [];
  355. for (var i=0, len=node.comparators.length; i < len; i+= 1) {
  356. comparators[i] = this.visit(node.comparators[i]);
  357. }
  358. // Handle ops
  359. for (var i=0, len=comparators.length; i < len; i+= 1) {
  360. var op = node.ops[i];
  361. var right = comparators[i];
  362. switch (op.name) {
  363. case "Eq": case "NotEq": case "Is": case "IsNot":
  364. break;
  365. case "Lt": case "LtE": case "GtE": case "Gt":
  366. if (left.name != right.name ||
  367. (left.name != "Num" && left.name != "Bool" &&
  368. left.name != "Str" && left.name != "List" &&
  369. left.name != "Set" && left.name != "Tuple" )) {
  370. this.reportIssue("Incompatible types",
  371. {"left": left, "right": right,
  372. "operation": op.name,
  373. "position": Tifa.locate(node)});
  374. }
  375. break;
  376. case "In": case "NotIn":
  377. if (right.name != "Str" && right.name != "List" &&
  378. right.name != "Set" && right.name != "Tuple" &&
  379. right.name != "Dict") {
  380. this.reportIssue("Incompatible types",
  381. {"left": left, "right": right,
  382. "operation": op.name,
  383. "position": Tifa.locate(node)});
  384. }
  385. break;
  386. }
  387. }
  388. return Tifa._BOOL_TYPE();
  389. }
  390. Tifa.prototype.visit_Call = function(node) {
  391. var position = Tifa.locate(node);
  392. // Handle func part (Name or Attribute)
  393. var functionType = this.visit(node.func);
  394. var calleeName = this.identifyCaller(node);
  395. // Handle args
  396. var arguments = [];
  397. for (var i = 0, len = node.args.length; i < len; i++) {
  398. var arg = this.visit(node.args[i]);
  399. arguments.push(arg);
  400. }
  401. // Handle keywords
  402. // Handle starargs
  403. // Handle kwargs
  404. if (functionType.name == 'Function') {
  405. var result = functionType.definition(this, functionType, calleeName, arguments, position);
  406. return result;
  407. }
  408. this.reportIssue("Not a function", {"position": position});
  409. return Tifa._UNKNOWN_TYPE();
  410. }
  411. Tifa.prototype.IfExp = function(node) {
  412. // Visit the conditional
  413. this.visit(node.test);
  414. // Visit the body
  415. var body = this.visit(node.body);
  416. // Visit the orelse
  417. var orelse = this.visit(node.orelse);
  418. if (body.name != orelse.name) {
  419. // TODO: Union type?
  420. return Tifa._UNKNOWN_TYPE();
  421. } else {
  422. return body;
  423. }
  424. }
  425. Tifa.prototype.visit_If = function(node) {
  426. var position = Tifa.locate(node);
  427. // Visit the conditional
  428. this.visit(node.test);
  429. if (node.orelse.length == 1 && node.orelse[0]._astname == "Pass") {
  430. this.reportIssue("Malformed Conditional", {"position": position});
  431. } else if (node.body.length == 1 && node.orelse.length &&
  432. node.body[0]._astname == "Pass") {
  433. this.reportIssue("Malformed Conditional", {"position": position});
  434. }
  435. // Visit the bodies
  436. var thisPathId = this.PathId;
  437. this.PathId += 1;
  438. var ifPathId = this.PathId;
  439. this.pathNames.push(ifPathId+'i');
  440. this.pathChain.unshift(ifPathId);
  441. this.nameMap[ifPathId] = {};
  442. for (var i = 0, len = node.body.length; i < len; i++) {
  443. this.visit(node.body[i]);
  444. }
  445. this.pathNames.pop()
  446. this.pathChain.shift();
  447. this.PathId += 1;
  448. var elsePathId = this.PathId;
  449. this.pathNames.push(elsePathId+'e');
  450. this.pathChain.unshift(elsePathId);
  451. this.nameMap[elsePathId] = {};
  452. for (var i = 0, len = node.orelse.length; i < len; i++) {
  453. this.visit(node.orelse[i]);
  454. }
  455. this.pathNames.pop()
  456. this.pathChain.shift();
  457. // Combine two paths into one
  458. for (var ifName in this.nameMap[ifPathId]) {
  459. if (ifName in this.nameMap[elsePathId]) {
  460. var combined = this.combineStates(this.nameMap[ifPathId][ifName],
  461. this.nameMap[elsePathId][ifName],
  462. position)
  463. } else {
  464. var combined = this.combineStates(this.nameMap[ifPathId][ifName],
  465. this.nameMap[thisPathId][ifName],
  466. position)
  467. }
  468. this.nameMap[thisPathId][ifName] = combined;
  469. }
  470. for (var elseName in this.nameMap[elsePathId]) {
  471. if (!(ifName in this.nameMap[elsePathId])) {
  472. var combined = this.combineStates(this.nameMap[elsePathId][elseName],
  473. this.nameMap[thisPathId][elseName],
  474. position)
  475. this.nameMap[thisPathId][elseName] = combined;
  476. }
  477. }
  478. }
  479. Tifa.prototype.visit_While = function(node) {
  480. // Visit the conditional
  481. this.visit(node.test);
  482. // Visit the bodies
  483. var thisPathId = this.PathId;
  484. this.PathId += 1;
  485. var ifPathId = this.PathId;
  486. this.pathNames.push(ifPathId+'i');
  487. this.pathChain.unshift(ifPathId);
  488. this.nameMap[ifPathId] = {};
  489. this.pathNames.pop()
  490. this.pathChain.shift();
  491. this.PathId += 1;
  492. var elsePathId = this.PathId;
  493. this.pathNames.push(elsePathId+'e');
  494. this.pathChain.unshift(elsePathId);
  495. this.nameMap[elsePathId] = {};
  496. for (var i = 0, len = node.orelse.length; i < len; i++) {
  497. this.visit(node.orelse[i]);
  498. }
  499. this.visit(node.test);
  500. this.pathNames.pop()
  501. this.pathChain.shift();
  502. // Combine two paths into one
  503. for (var ifName in this.nameMap[ifPathId]) {
  504. if (ifName in this.nameMap[elsePathId]) {
  505. var combined = this.combineStates(this.nameMap[ifPathId][ifName],
  506. this.nameMap[elsePathId][ifName],
  507. Tifa.locate(node))
  508. } else {
  509. var combined = this.combineStates(this.nameMap[ifPathId][ifName],
  510. this.nameMap[elsePathId][thisPathId],
  511. Tifa.locate(node))
  512. }
  513. this.nameMap[thisPathId][ifName] = combined;
  514. }
  515. for (var elseName in this.nameMap[elsePathId]) {
  516. if (!(ifName in this.nameMap[elsePathId])) {
  517. var combined = this.combineStates(this.nameMap[elsePathId][elseName],
  518. this.nameMap[elsePathId][thisPathId],
  519. Tifa.locate(node))
  520. this.nameMap[thisPathId][elseName] = combined;
  521. }
  522. }
  523. }
  524. Tifa.prototype.visit_ListComp = function(node) {
  525. // TODO: Handle comprehension scope
  526. var generators = node.generators;
  527. for (var i = 0, len = generators.length; i < len; i++) {
  528. this.visit(generators[i]);
  529. }
  530. var elt = node.elt;
  531. return Tifa._LIST_OF_TYPE(this.visit(elt));
  532. }
  533. Tifa.prototype.visit_SetComp = function(node) {
  534. // TODO: Handle comprehension scope
  535. var generators = node.generators;
  536. for (var i = 0, len = generators.length; i < len; i++) {
  537. this.visit(generators[i]);
  538. }
  539. var elt = node.elt;
  540. return Tifa._SET_OF_TYPE(this.visit(elt));
  541. }
  542. Tifa.prototype.visit_SetComp = function(node) {
  543. // TODO: Handle comprehension scope
  544. var generators = node.generators;
  545. for (var i = 0, len = generators.length; i < len; i++) {
  546. this.visit(generators[i]);
  547. }
  548. var key = node.key;
  549. var value = node.value;
  550. return Tifa._DICT_OF_TYPE(this.visit(key), this.visit(value));
  551. }
  552. Tifa.prototype.visit_GeneratorExp = function(node) {
  553. // TODO: Handle comprehension scope
  554. var generators = node.generators;
  555. for (var i = 0, len = generators.length; i < len; i++) {
  556. this.visit(generators[i]);
  557. }
  558. var elt = node.elt;
  559. return Tifa._GENERATOR_OF_TYPE(this.visit(elt));
  560. }
  561. Tifa.prototype.visit_comprehension = function(node) {
  562. // Handle the iteration list
  563. var position = Tifa.locate(node);
  564. var iter = node.iter;
  565. var iterType;
  566. var iterListName = null;
  567. if (iter._astname === "Name") {
  568. iterListName = iter.id.v;
  569. if (iterListName == "___") {
  570. this.reportIssue("Unconnected blocks", {"position": position})
  571. }
  572. var state = this.iterateVariable(iterListName, Tifa.locate(node));
  573. iterType = state.type;
  574. } else {
  575. iterType = this.visit(iter);
  576. }
  577. if (Tifa.isTypeEmptyList(iterType)) {
  578. this.reportIssue("Empty iterations", {"position": position, "name": iterListName});
  579. }
  580. if (!(Tifa.isTypeSequence(iterType))) {
  581. this.reportIssue("Non-list iterations", {"position": position, "name": iterListName});
  582. }
  583. var iterSubtype = null;
  584. if (iterType !== null) {
  585. iterSubtype = Tifa.indexSequenceType(iterType, 0);
  586. }
  587. // Handle the iteration variable
  588. var iterVariableName = null;
  589. var that = this;
  590. (function walkTarget(target, type) {
  591. if (target._astname === 'Name') {
  592. if (iterVariableName == null) {
  593. iterVariableName = target.id.v;
  594. }
  595. that.storeIterVariable(target.id.v, type, position);
  596. } else if (target._astname == 'Tuple' || target._astname == 'List') {
  597. for (var i = 0, len = target.elts.length; i < len; i++) {
  598. var elt = target.elts[i];
  599. var eltType = Tifa.indexSequenceType(type, i);
  600. walkTarget(elt, eltType, position);
  601. }
  602. }
  603. })(node.target, iterSubtype);
  604. if (iterVariableName && iterListName && iterListName == iterVariableName) {
  605. this.reportIssue("Iteration variable is iteration list",
  606. {"name": iterVariableName, "position": position});
  607. }
  608. // Handle the bodies
  609. for (var i = 0, len = node.ifs.length; i < len; i++) {
  610. this.visit(node.ifs[i]);
  611. }
  612. }
  613. Tifa.prototype.visit_For = function(node) {
  614. // Handle the iteration list
  615. var position = Tifa.locate(node);
  616. var iter = node.iter;
  617. var iterType;
  618. var iterListName = null;
  619. if (iter._astname === "Name") {
  620. iterListName = iter.id.v;
  621. if (iterListName == "___") {
  622. this.reportIssue("Unconnected blocks", {"position": position})
  623. }
  624. var state = this.iterateVariable(iterListName, Tifa.locate(node));
  625. iterType = state.type;
  626. } else {
  627. iterType = this.visit(iter);
  628. }
  629. if (Tifa.isTypeEmptyList(iterType)) {
  630. this.reportIssue("Empty iterations", {"position": position, "name": iterListName});
  631. }
  632. if (!(Tifa.isTypeSequence(iterType))) {
  633. this.reportIssue("Non-list iterations", {"position": position, "name": iterListName});
  634. }
  635. var iterSubtype = null;
  636. if (iterType !== null) {
  637. iterSubtype = Tifa.indexSequenceType(iterType, 0);
  638. }
  639. // Handle the iteration variable
  640. var iterVariableName = null;
  641. var that = this;
  642. (function walkTarget(target, type) {
  643. if (target._astname === 'Name') {
  644. if (iterVariableName == null) {
  645. iterVariableName = target.id.v;
  646. }
  647. that.storeIterVariable(target.id.v, type, position);
  648. } else if (target._astname == 'Tuple' || target._astname == 'List') {
  649. for (var i = 0, len = target.elts.length; i < len; i++) {
  650. var elt = target.elts[i];
  651. var eltType = Tifa.indexSequenceType(type, i);
  652. walkTarget(elt, eltType, position);
  653. }
  654. }
  655. })(node.target, iterSubtype);
  656. if (iterVariableName && iterListName && iterListName == iterVariableName) {
  657. this.reportIssue("Iteration variable is iteration list",
  658. {"name": iterVariableName, "position": position});
  659. }
  660. // Handle the bodies
  661. for (var i = 0, len = node.body.length; i < len; i++) {
  662. this.visit(node.body[i]);
  663. }
  664. for (var i = 0, len = node.orelse.length; i < len; i++) {
  665. this.visit(node.orelse[i]);
  666. }
  667. }
  668. Tifa.prototype.visit_ClassDef = function(node) {
  669. var className = node.name.v;
  670. var position = Tifa.locate(node);
  671. this.storeVariable(className, {"type": "Class"}, position)
  672. // TODO: Define a new scope definition that executes the body
  673. // TODO: find __init__, execute that
  674. this.generic_visit(node);
  675. }
  676. Tifa.prototype.visit_FunctionDef = function(node) {
  677. // Name
  678. var functionName = node.name.v;
  679. var position = Tifa.locate(node);
  680. var state = this.storeVariable(functionName, {"name": "Function"}, position);
  681. var definitionsScope = this.scopeChain.slice(0);
  682. state.type.definition = function(analyzer, callType, callName, parameters, callPosition) {
  683. // Manage scope
  684. analyzer.ScopeId += 1;
  685. var oldScope = analyzer.scopeChain.slice(0);
  686. analyzer.scopeChain = definitionsScope.slice(0);
  687. analyzer.scopeChain.unshift(analyzer.ScopeId);
  688. // Process arguments
  689. var args = node.args.args;
  690. for (var i = 0; i < args.length; i++) {
  691. var arg = args[i];
  692. var name = Sk.ffi.remapToJs(arg.id);
  693. var parameter = Tifa.copyType(parameters[i]);
  694. analyzer.storeVariable(name, parameter, position)
  695. }
  696. for (var i = 0, len = node.body.length; i < len; i++) {
  697. analyzer.visit(node.body[i]);
  698. }
  699. var returnState = analyzer.findVariablesScope("*return");
  700. var returnValue = Tifa._NONE_TYPE();
  701. if (returnState.exists && returnState.inScope) {
  702. returnValue = analyzer.loadVariable("*return", callPosition).type;
  703. }
  704. // Return scope
  705. analyzer.finishScope();
  706. analyzer.scopeChain.shift();
  707. analyzer.scopeChain = oldScope;
  708. return returnValue;
  709. }
  710. return state.type;
  711. }
  712. Tifa.prototype.visit_Lambda = function(node) {
  713. // Name
  714. var position = Tifa.locate(node);
  715. var definitionsScope = this.scopeChain.slice(0);
  716. var functionType = { "name": "Function"};
  717. functionType.definition = function(analyzer, callType, callName, parameters, callPosition) {
  718. // Manage scope
  719. analyzer.ScopeId += 1;
  720. var oldScope = analyzer.scopeChain.slice(0);
  721. analyzer.scopeChain = definitionsScope.slice(0);
  722. analyzer.scopeChain.unshift(analyzer.ScopeId);
  723. // Process arguments
  724. var args = node.args.args;
  725. for (var i = 0; i < args.length; i++) {
  726. var arg = args[i];
  727. var name = Sk.ffi.remapToJs(arg.id);
  728. var parameter = Tifa.copyType(parameters[i]);
  729. analyzer.storeVariable(name, parameter, position)
  730. }
  731. var returnValue = analyzer.visit(node.body);
  732. // Return scope
  733. analyzer.finishScope();
  734. analyzer.scopeChain.shift();
  735. analyzer.scopeChain = oldScope;
  736. return returnValue;
  737. }
  738. return functionType;
  739. }
  740. Tifa.prototype.visit_Return = function(node) {
  741. if (this.scopeChain.length == 1) {
  742. this.reportIssue("Return outside function", {"position": Tifa.locate(node)});
  743. }
  744. var valueType = this.visit(node.value);
  745. var position = Tifa.locate(node);
  746. this.returnVariable(valueType, position)
  747. }
  748. Tifa.prototype.visit_Attribute = function(node) {
  749. // Handle value
  750. var valueType = this.visit(node.value);
  751. // Handle attr
  752. var position = Tifa.locate(node);
  753. var attrType = this.loadBuiltinAttr(valueType, node.value, node.attr.v, position);
  754. // Handle ctx
  755. //TODO: Handling contexts
  756. return attrType;
  757. }
  758. Tifa.prototype.visit_Subscript = function(node) {
  759. // Handle value
  760. var valueType = this.visit(node.value);
  761. // Handle slice
  762. switch (node.slice._astname) {
  763. case "Index": return Tifa.indexSequenceType(valueType, 0);
  764. case "Slice": return valueType;
  765. }
  766. }
  767. Tifa.prototype.visit_Name = function(node) {
  768. var name = node.id.v;
  769. if (name == "___") {
  770. this.reportIssue("Unconnected blocks", {"position": Tifa.locate(node)})
  771. }
  772. if (node.ctx.prototype._astname === "Load") {
  773. if (name == "True" || name == "False") {
  774. return Tifa._BOOL_TYPE();
  775. } else if (name == "None") {
  776. return Tifa._NONE_TYPE();
  777. } else {
  778. var variable = this.findVariablesScope(name);
  779. var builtin = Tifa.loadBuiltin(name);
  780. if (!variable.exists && builtin) {
  781. return builtin;
  782. } else {
  783. var state = this.loadVariable(name, Tifa.locate(node));
  784. return state.type;
  785. }
  786. }
  787. } else {
  788. var variable = this.findVariablesScope(name);
  789. if (variable.exists) {
  790. return variable.state.type;
  791. } else {
  792. return Tifa._UNKNOWN_TYPE();
  793. }
  794. }
  795. }
  796. Tifa.prototype.visit_Num = function(node) {
  797. return Tifa._NUM_TYPE();
  798. }
  799. Tifa.prototype.visit_Bool = function(node) {
  800. return Tifa._BOOL_TYPE();
  801. }
  802. Tifa.prototype.visit_Str = function(node) {
  803. return Tifa._STR_TYPE();
  804. }
  805. Tifa.prototype.visit_List = function(node) {
  806. var type = Tifa._LIST_TYPE();
  807. if (node.elts.length == 0) {
  808. type.empty = true;
  809. } else {
  810. type.empty = false;
  811. // TODO: confirm homogenous subtype
  812. for (var i = 0, len = node.elts.length; i < len; i++) {
  813. type.subtype = this.visit(node.elts[i]);
  814. }
  815. }
  816. return type;
  817. }
  818. Tifa.prototype.visit_Dict = function(node) {
  819. var type = Tifa._DICT_TYPE();
  820. if (node.keys.length == 0) {
  821. type.empty = true;
  822. } else {
  823. type.empty = false;
  824. for (var i = 0, len = node.keys.length; i < len; i++) {
  825. type.keys = this.visit(node.keys[i]);
  826. type.values = this.visit(node.values[i]);
  827. }
  828. }
  829. return type;
  830. }
  831. Tifa.prototype.visit_Tuple = function(node) {
  832. var type = Tifa._TUPLE_TYPE();
  833. if (node.elts.length == 0) {
  834. type.empty = true;
  835. } else {
  836. type.empty = false;
  837. type.subtypes = [];
  838. // TODO: confirm homogenous subtype
  839. for (var i = 0, len = node.elts.length; i < len; i++) {
  840. type.subtypes.push(this.visit(node.elts[i]));
  841. }
  842. }
  843. return type;
  844. }
  845. Tifa.prototype.visit_With = function(node) {
  846. var typeValue = this.visit(node.context_expr),
  847. position = Tifa.locate(node),
  848. that = this;
  849. this.visitList(node.optional_vars);
  850. (function walkTarget(target, type) {
  851. if (target._astname === 'Name') {
  852. that.storeVariable(target.id.v, type, position);
  853. } else if (target._astname == 'Tuple' || target._astname == 'List') {
  854. for (var i = 0, len = target.elts.length; i < len; i++) {
  855. var elt = target.elts[i];
  856. var eltType = Tifa.indexSequenceType(type, i);
  857. walkTarget(elt, eltType, position);
  858. }
  859. }
  860. })(node.optional_vars, typeValue);
  861. // Handle the bodies
  862. for (var i = 0, len = node.body.length; i < len; i++) {
  863. this.visit(node.body[i]);
  864. }
  865. }
  866. Tifa.prototype.identifyCaller = function (node) {
  867. switch (node._astname) {
  868. case "Name":
  869. return node.id.v;
  870. case "Call":
  871. return this.identifyCaller(node.func);
  872. case "Attribute": case "Subscript":
  873. return this.identifyCaller(node.value);
  874. default:
  875. return null;
  876. }
  877. }
  878. Tifa.prototype.findVariableOutOfScope = function (name) {
  879. for (var pathId in this.nameMap) {
  880. var path = this.nameMap[pathId];
  881. for (var fullName in path) {
  882. var unscopedName = /[^/]*$/.exec(fullName)[0];
  883. if (unscopedName == name) {
  884. return {'exists': true, 'inScope': false, 'scopedName': unscopedName, 'state': path[fullName]};
  885. }
  886. }
  887. }
  888. return {'exists': false, 'inScope': false, 'state': path[fullName]}
  889. }
  890. Tifa.prototype.findVariablesScope = function (name) {
  891. for (var j = 0, slen = this.scopeChain.length; j < slen; j++) {
  892. for (var i = 0, plen = this.pathChain.length; i < plen; i++) {
  893. var pathId = this.pathChain[i];
  894. var path = this.nameMap[pathId];
  895. var fullName = this.scopeChain.slice(j).join("/") + "/" + name;
  896. if (fullName in path) {
  897. if (j == 0) {
  898. return {'exists': true, 'inScope': true, 'scopedName': fullName, 'state': path[fullName]};
  899. } else {
  900. return {'exists': true, 'inScope': false, 'scopedName': fullName, 'state': path[fullName]};
  901. }
  902. }
  903. }
  904. }
  905. return {'exists': false};
  906. }
  907. Tifa.prototype.storeVariable = function(name, type, position) {
  908. var fullName = this.scopeChain.join("/") + "/" + name;
  909. var currentPath = this.pathChain[0];
  910. var variable = this.findVariablesScope(name);
  911. if (!variable.exists) {
  912. // Create a new instance of the variable on the current path
  913. newState = {'name': name, 'trace': [], 'type': type,
  914. 'read': 'no', 'set': 'yes', 'over': 'no'};
  915. this.nameMap[currentPath][fullName] = newState;
  916. } else {
  917. newState = Tifa.traceState(variable.state, "store");
  918. if (!variable.inScope) {
  919. this.reportIssue("Write out of scope",
  920. {'name': name, 'position':position})
  921. }
  922. // Type change?
  923. if (!Tifa.areTypesEqual(type, variable.state.type)) {
  924. this.reportIssue("Type changes",
  925. {'name': name, 'position':position,
  926. 'old': variable.state.type, 'new': type})
  927. }
  928. newState.type = type;
  929. // Overwritten?
  930. if (variable.state.set == 'yes' && variable.state.read == 'no') {
  931. newState.over = 'yes';
  932. } else {
  933. newState.set = 'yes';
  934. newState.read = 'no';
  935. }
  936. this.nameMap[currentPath][fullName] = newState;
  937. }
  938. return newState;
  939. }
  940. Tifa.prototype.storeIterVariable = function(name, type, position) {
  941. var state = this.storeVariable(name, type, position);
  942. state.read = 'yes';
  943. return state;
  944. }
  945. Tifa.prototype.appendVariable = function(name, type, position) {
  946. var state = this.storeVariable(name, type, position);
  947. return state;
  948. }
  949. Tifa.prototype.returnVariable = function(type, position) {
  950. return this.storeVariable("*return", type, position);
  951. }
  952. Tifa.prototype.loadVariable = function(name, position) {
  953. var fullName = this.scopeChain.join("/") + "/" + name;
  954. var currentPath = this.pathChain[0];
  955. var variable = this.findVariablesScope(name);
  956. var outOfScopeVar = this.findVariableOutOfScope(name);
  957. if (!variable.exists) {
  958. // Create a new instance of the variable on the current path
  959. if (outOfScopeVar.exists) {
  960. this.reportIssue("Read out of scope",
  961. {'name': name, 'position':position})
  962. } else {
  963. this.reportIssue("Undefined variables",
  964. {'name': name, 'position':position})
  965. }
  966. state = {'name': name, 'trace': [], 'type': Tifa._UNKNOWN_TYPE(),
  967. 'read': 'yes', 'set': 'no', 'over': 'no'};
  968. this.nameMap[currentPath][fullName] = state;
  969. return state;
  970. } else {
  971. var newState = Tifa.traceState(variable.state, "load");
  972. if (variable.state.set == 'no') {
  973. this.reportIssue("Undefined variables",
  974. {'name': name, 'position':position})
  975. }
  976. if (variable.state.set == 'maybe') {
  977. this.reportIssue("Possibly undefined variables",
  978. {'name': name, 'position':position})
  979. }
  980. newState.read = 'yes';
  981. if (!variable.inScope) {
  982. this.nameMap[currentPath][variable.scopedName] = newState;
  983. } else {
  984. this.nameMap[currentPath][fullName] = newState;
  985. }
  986. return newState;
  987. }
  988. }
  989. Tifa.prototype.iterateVariable = function(name, position) {
  990. return this.loadVariable(name, position);
  991. }
  992. Tifa.prototype.finishScope = function() {
  993. var pathId = this.pathChain[0];
  994. for (var name in this.nameMap[pathId]) {
  995. if (Tifa.sameScope(name, this.scopeChain)) {
  996. var state = this.nameMap[pathId][name];
  997. if (state.over == 'yes') {
  998. this.reportIssue('Overwritten variables',
  999. {'name': state.name, 'position': 0}) // TODO position
  1000. }
  1001. if (state.read == 'no') {
  1002. this.reportIssue('Unread variables',
  1003. {'name': state.name, 'type': state.type})
  1004. }
  1005. }
  1006. }
  1007. }
  1008. Tifa.prototype.walkTargets = function(targets, type, walker) {
  1009. for (var i = 0, len = targets.length; i < len; i++) {
  1010. walker(targets[i], type);
  1011. }
  1012. }
  1013. Tifa.cloneType = function (aType){
  1014. switch (aType.name) {
  1015. case "List": case "Set": case "Generator":
  1016. return Tifa._LIST_OF_TYPE(Tifa.cloneType(aType.subtype));
  1017. case "Dict":
  1018. return Tifa._DICT_OF_TYPE(Tifa.cloneType(aType.keys),
  1019. Tifa.cloneType(aType.values));
  1020. case "Tuple":
  1021. var newTuple = Tifa._TUPLE_TYPE();
  1022. newTuple.subtypes = aType.subtypes.map(t => Tifa.cloneType(t));
  1023. return newTuple;
  1024. case "Module":
  1025. var newModule = Tifa._MODULE_TYPE();
  1026. for (var name in aType.submodules) {
  1027. newModule.submodules[name] = Tifa.cloneType(aType.submodules[name]);
  1028. }
  1029. for (var name in aType.fields) {
  1030. newModule.fields[name] = Tifa.cloneType(aType.fields[name]);
  1031. }
  1032. return newModule;
  1033. case "Bool": case "Num": case "Str": case "File":
  1034. case "None": case "*Unknown":
  1035. return {"name": aType.name};
  1036. }
  1037. }
  1038. // Type Definitions
  1039. Tifa._BOOL_TYPE = function() { return {'name': 'Bool'} };
  1040. Tifa._NUM_TYPE = function() { return {'name': 'Num'} };
  1041. Tifa._MODULE_TYPE = function() { return {'name': 'Module', 'submodules': {}, 'fields': {} }};
  1042. Tifa._STR_TYPE = function() { return {'name': 'Str'} };
  1043. Tifa._FILE_TYPE = function() { return {'name': 'File'} };
  1044. Tifa._SET_TYPE = function() { return {'name': 'Str', "subtype": Tifa._UNKNOWN_TYPE(), "empty": false} };
  1045. Tifa._LIST_TYPE = function(isEmpty) { return {'name': 'List', "subtype": Tifa._UNKNOWN_TYPE(), "empty": !!isEmpty} };
  1046. Tifa._DICT_TYPE = function() { return {'name': 'Dict', "empty": false, "keys": Tifa._UNKNOWN_TYPE(), "values": Tifa._UNKNOWN_TYPE()} };
  1047. Tifa._GENERATOR_OF_TYPE = function(subtype) { return {'name': 'Generator', "empty": false, "subtype": subtype} };
  1048. Tifa._LIST_OF_TYPE = function(subtype) { return {'name': 'List', "empty": false, "subtype": subtype} };
  1049. Tifa._SET_OF_TYPE = function(subtype) { return {'name': 'Set', "empty": false, "subtype": subtype} };
  1050. Tifa._DICT_OF_TYPE = function(keytype, valuetype) {
  1051. return {'name': 'Set', "empty": false, "keys": keytype, "values": valuetype} };
  1052. Tifa._TUPLE_TYPE = function() { return {'name': 'Tuple', "subtypes": []} };
  1053. Tifa._NONE_TYPE = function() { return {'name': 'None'} };
  1054. Tifa._UNKNOWN_TYPE = function() { return {'name': '*Unknown'} };
  1055. Tifa.VALID_BINOP_TYPES = {
  1056. 'Add': {'Num': {'Num': Tifa._NUM_TYPE},
  1057. 'Str' :{'Str': Tifa._STR_TYPE},
  1058. 'List': {'List': Tifa.mergeTypes},
  1059. 'Tuple': {'Tuple': Tifa.mergeTypes}},
  1060. 'Sub': {'Num': {'Num': Tifa._NUM_TYPE},
  1061. 'Set': {'Set': Tifa.mergeTypes}},
  1062. 'Div': {'Num': {'Num': Tifa._NUM_TYPE}},
  1063. 'FloorDiv': {'Num': {'Num': Tifa._NUM_TYPE}},
  1064. 'Mult': {'Num': {'Num': Tifa._NUM_TYPE,
  1065. 'Str': Tifa._STR_TYPE,
  1066. 'List': (l, r) => r,
  1067. 'Tuple': (l, r) => r},
  1068. 'Str': {'Num': Tifa._STR_TYPE},
  1069. 'List': {'Num': (l, r) => l},
  1070. 'Tuple': {'Num': (l, r) => l}},
  1071. 'Pow': {'Num': {'Num': Tifa._NUM_TYPE}},
  1072. // Should we allow old-fashioned string interpolation?
  1073. // Currently, I vote no because it makes the code harder and is bad form.
  1074. 'Mod': {'Num': {'Num': Tifa._NUM_TYPE}},
  1075. 'LShift': {'Num': {'Num': Tifa._NUM_TYPE}},
  1076. 'RShift': {'Num': {'Num': Tifa._NUM_TYPE}},
  1077. 'BitOr': {'Num': {'Num': Tifa._NUM_TYPE},
  1078. 'Bool': {'Num': Tifa._NUM_TYPE,
  1079. 'Bool': Tifa._BOOL_TYPE},
  1080. 'Set': {'Set': Tifa.mergeTypes}},
  1081. 'BitXor': {'Num': {'Num': Tifa._NUM_TYPE},
  1082. 'Bool': {'Num': Tifa._NUM_TYPE,
  1083. 'Bool': Tifa._BOOL_TYPE},
  1084. 'Set': {'Set': Tifa.mergeTypes}},
  1085. 'BitAnd': {'Num': {'Num': Tifa._NUM_TYPE},
  1086. 'Bool': {'Num': Tifa._NUM_TYPE,
  1087. 'Bool': Tifa._BOOL_TYPE},
  1088. 'Set': {'Set': Tifa.mergeTypes}},
  1089. }
  1090. Tifa.VALID_UNARYOP_TYPES = {
  1091. 'UAdd': {'Num': Tifa._NUM_TYPE},
  1092. 'USub': {'Num': Tifa._NUM_TYPE},
  1093. 'Invert': {'Num': Tifa._NUM_TYPE}
  1094. }
  1095. Tifa.locate = function(node) {
  1096. return {"column": node.col_offset, "line": node.lineno};
  1097. }
  1098. Tifa.indexSequenceType= function(type, i) {
  1099. if (type.name == "Tuple") {
  1100. return Tifa.cloneType(type.subtypes[i]);
  1101. } else if (type.name == "List") {
  1102. return Tifa.cloneType(type.subtype);
  1103. } else if (type.name == "Generator") {
  1104. return Tifa.cloneType(type.subtype);
  1105. } else if (type.name == "Str") {
  1106. return Tifa._STR_TYPE();
  1107. } else if (type.name == "File") {
  1108. return Tifa._STR_TYPE();
  1109. } else if (type.name == "Dict") {
  1110. return Tifa.cloneType(type.keys);
  1111. } else {
  1112. return Tifa._UNKNOWN_TYPE();
  1113. }
  1114. }
  1115. Tifa.areTypesEqual = function(left, right) {
  1116. if (left === null || right === null) {
  1117. return false;
  1118. } else if (left.name === "Unknown" || right.type === "Unknown") {
  1119. return false;
  1120. } else if (left.name === "List" && right.name === "List") {
  1121. if (left.empty || right.empty) {
  1122. return true;
  1123. } else {
  1124. return Tifa.areTypesEqual(left.subtype, right.subtype);
  1125. }
  1126. } else {
  1127. return left.name == right.name;
  1128. }
  1129. }
  1130. Tifa.isTypeEmptyList = function(type) {
  1131. return (type.name === "List" && type.empty);
  1132. }
  1133. Tifa.isTypeSequence = function(type) {
  1134. return arrayContains(type.name, ["List", "Set", "Tuple", "Str", "File", "Dict"]);
  1135. }
  1136. Tifa.sameScope = function(fullName, scopeChain) {
  1137. var nameScopes = fullName.split("/").slice(0, -1);
  1138. var checkingScopes = scopeChain.slice().reverse();
  1139. if (nameScopes.length != checkingScopes.length) {
  1140. return false;
  1141. }
  1142. for (var i = 0, len = checkingScopes.length; i < len; i++) {
  1143. if (nameScopes[i] != checkingScopes[i]) {
  1144. return false;
  1145. }
  1146. }
  1147. return true;
  1148. }
  1149. Tifa.prototype.combineStates = function(left, right, position) {
  1150. var state = {'name': left.name, 'trace': left.trace, 'type': left.type,
  1151. 'read': left.read, 'set': left.set, 'over': left.over};
  1152. if (right == null) {
  1153. state.read = left.read == 'no' ? 'no' : 'maybe';
  1154. state.set = left.set == 'no' ? 'no' : 'maybe';
  1155. state.over = left.over == 'no' ? 'no' : 'maybe';
  1156. } else {
  1157. if (!Tifa.areTypesEqual(left.type, right.type)) {
  1158. this.reportIssue("Type changes",
  1159. {'name': left.name, 'position':position,
  1160. 'old': left.type, 'new': right.type})
  1161. }
  1162. state.read = Tifa.matchRSO(left.read, right.read);
  1163. state.set = Tifa.matchRSO(left.set, right.set);
  1164. state.over = Tifa.matchRSO(left.over, right.over);
  1165. }
  1166. return state;
  1167. }
  1168. Tifa.matchRSO = function(left, right) {
  1169. if (left == right) {
  1170. return left;
  1171. } else {
  1172. return "maybe";
  1173. }
  1174. }
  1175. Tifa.traceState = function(state, method) {
  1176. var newState = {
  1177. 'type': state.type, 'method': method, 'trace': [],//state.trace.slice(0),
  1178. 'set': state.set, 'read': state.read, 'over': state.over,
  1179. 'name': state.name
  1180. };
  1181. newState.trace.push(state);
  1182. return newState;
  1183. }
  1184. /**
  1185. * Correctly clones a type, returning mutable types unchanged.
  1186. */
  1187. Tifa.copyType = function(type) {
  1188. switch (type.name) {
  1189. // Immutable types:
  1190. case "Str": return Tifa._STR_TYPE();
  1191. case "Num": return Tifa._NUM_TYPE();
  1192. case "Tuple": return Tifa._TUPLE_TYPE();
  1193. // Mutable types:
  1194. default: return type;
  1195. }
  1196. }
  1197. Tifa.mergeTypes = function(left, right) {
  1198. // TODO: Check that lists/sets have the same subtypes
  1199. switch (left.name) {
  1200. case "List": return (left.empty ? right.subtype : Tifa.cloneType(left.subtype));
  1201. case "Set": return (left.empty ? right.subtype : Tifa.cloneType(left.subtype));
  1202. case "Tuple": return left.subtypes.concat(right.subtypes);
  1203. }
  1204. }
  1205. Tifa.defineSupplier = function(returnType) {
  1206. return { "name": "Function",
  1207. "definition": function (analyzer, type, name, args, position) {
  1208. return returnType;
  1209. }
  1210. };
  1211. }
  1212. Tifa.defineIdentity = function(returnType) {
  1213. return { "name": "Function",
  1214. "definition": function (analyzer, type, name, args, position) {
  1215. if (args.length) {
  1216. return args[0];
  1217. }
  1218. return Tifa._UNKNOWN_TYPE();
  1219. }
  1220. };
  1221. }
  1222. Tifa.defineFunction = function(definition) {
  1223. return { "name": "Function", "definition": definition};
  1224. }
  1225. Tifa.loadBuiltin = function(name) {
  1226. switch (name) {
  1227. // Void functions
  1228. case "print": return Tifa.defineSupplier(Tifa._NONE_TYPE());
  1229. // Math functions
  1230. case "int": case "abs": case "float": case "len":
  1231. case "ord": case "pow": case "round": case "sum":
  1232. return Tifa.defineSupplier(Tifa._NUM_TYPE());
  1233. // Boolean functions
  1234. case "bool": case "all": case "any": case "isinstance":
  1235. return Tifa.defineSupplier(Tifa._BOOL_TYPE());
  1236. // String functions
  1237. case "input": case "str": case "chr": case "repr":
  1238. return Tifa.defineSupplier(Tifa._STR_TYPE());
  1239. // File functions
  1240. case "open":
  1241. return Tifa.defineSupplier(Tifa._FILE_TYPE());
  1242. // List functions
  1243. case "map": return Tifa.defineSupplier(Tifa._LIST_TYPE());
  1244. case "list":
  1245. return Tifa.defineFunction(
  1246. function (analyzer, functionType, callee, args, position) {
  1247. var returnType = Tifa._LIST_TYPE();
  1248. if (args.length) {
  1249. returnType.subtype = Tifa.indexSequenceType(args[0], 0);
  1250. // TODO: Should inherit the emptiness too
  1251. returnType.empty = true;
  1252. } else {
  1253. returnType.empty = true;
  1254. }
  1255. return returnType;
  1256. }
  1257. );
  1258. // Set functions
  1259. case "set":
  1260. return Tifa.defineFunction(
  1261. function (analyzer, functionType, callee, args, position) {
  1262. var returnType = Tifa._SET_TYPE();
  1263. if (args.length) {
  1264. returnType.subtype = Tifa.indexSequenceType(args[0], 0);
  1265. // TODO: Should inherit the emptiness too
  1266. returnType.empty = true;
  1267. } else {
  1268. returnType.empty = true;
  1269. }
  1270. return returnType;
  1271. }
  1272. );
  1273. // Dict functions
  1274. case "dict":
  1275. return Tifa.defineSupplier(Tifa._DICT_TYPE());
  1276. // Pass through
  1277. case "sorted": case "reversed": case "filter":
  1278. return Tifa.defineIdentity();
  1279. // Special functions
  1280. case "range": return Tifa.defineSupplier(Tifa._LIST_OF_TYPE(Tifa._NUM_TYPE()));
  1281. case "dir": return Tifa.defineSupplier(Tifa._LIST_OF_TYPE(Tifa._STR_TYPE()));
  1282. case "max": case "min":
  1283. return Tifa.defineFunction(
  1284. function (analyzer, functionType, callee, args, position) {
  1285. if (args.length) {
  1286. return Tifa.indexSequenceType(args[0], 0);
  1287. }
  1288. return Tifa._UNKNOWN_TYPE();
  1289. }
  1290. );
  1291. case "zip":
  1292. return Tifa.defineFunction(
  1293. function (analyzer, functionType, callee, args, position) {
  1294. if (args.length) {
  1295. var tupledTypes = Tifa._TUPLE_TYPE();
  1296. tupledTypes.subtypes = [];
  1297. for (var i=0, len=args.length; i<len; i+=1) {
  1298. tupledTypes.subtypes[i] = Tifa.indexSequenceType(args[i],0);
  1299. }
  1300. return Tifa._LIST_OF_TYPE(tupledTypes);
  1301. } else {
  1302. var emptyList = Tifa._LIST_TYPE(true);
  1303. return emptyList;
  1304. }
  1305. }
  1306. );
  1307. }
  1308. }
  1309. Tifa.MODULES = {
  1310. 'matplotlib': {
  1311. 'name': 'Module',
  1312. 'submodules': {
  1313. 'pyplot': {
  1314. 'name': 'Module',
  1315. 'fields': {
  1316. 'plot': Tifa.defineSupplier(Tifa._NONE_TYPE()),
  1317. 'hist': Tifa.defineSupplier(Tifa._NONE_TYPE()),
  1318. 'scatter': Tifa.defineSupplier(Tifa._NONE_TYPE()),
  1319. 'show': Tifa.defineSupplier(Tifa._NONE_TYPE()),
  1320. 'xlabel': Tifa.defineSupplier(Tifa._NONE_TYPE()),
  1321. 'ylabel': Tifa.defineSupplier(Tifa._NONE_TYPE()),
  1322. 'title': Tifa.defineSupplier(Tifa._NONE_TYPE()),
  1323. }
  1324. }
  1325. }
  1326. },
  1327. 'pprint': {
  1328. 'name': "Module",
  1329. 'fields': {
  1330. 'pprint': Tifa.defineSupplier(Tifa._NONE_TYPE())
  1331. }
  1332. },
  1333. 'random': {
  1334. 'name': "Module",
  1335. 'fields': {
  1336. 'randint': Tifa.defineSupplier(Tifa._NUM_TYPE())
  1337. }
  1338. },
  1339. 'turtle': {
  1340. 'name': "Module",
  1341. 'fields': {
  1342. 'forward': Tifa.defineSupplier(Tifa._NONE_TYPE()),
  1343. 'backward': Tifa.defineSupplier(Tifa._NONE_TYPE()),
  1344. 'color': Tifa.defineSupplier(Tifa._NONE_TYPE()),
  1345. 'right': Tifa.defineSupplier(Tifa._NONE_TYPE()),
  1346. 'left': Tifa.defineSupplier(Tifa._NONE_TYPE()),
  1347. }
  1348. },
  1349. 'math': {
  1350. 'name': "Module",
  1351. 'fields': {
  1352. 'ceil': Tifa.defineSupplier(Tifa._NUM_TYPE()),
  1353. 'copysign': Tifa.defineSupplier(Tifa._NUM_TYPE()),
  1354. 'fabs': Tifa.defineSupplier(Tifa._NUM_TYPE()),
  1355. 'factorial': Tifa.defineSupplier(Tifa._NUM_TYPE()),
  1356. 'floor': Tifa.defineSupplier(Tifa._NUM_TYPE()),
  1357. 'fmod': Tifa.defineSupplier(Tifa._NUM_TYPE()),
  1358. 'frexp': Tifa.defineSupplier(Tifa._NUM_TYPE()),
  1359. 'fsum': Tifa.defineSupplier(Tifa._NUM_TYPE()),
  1360. 'gcd': Tifa.defineSupplier(Tifa._NUM_TYPE()),
  1361. 'isclose': Tifa.defineSupplier(Tifa._BOOL_TYPE()),
  1362. 'isfinite': Tifa.defineSupplier(Tifa._BOOL_TYPE()),
  1363. 'isinf': Tifa.defineSupplier(Tifa._BOOL_TYPE()),
  1364. 'isnan': Tifa.defineSupplier(Tifa._BOOL_TYPE()),
  1365. 'ldexp': Tifa.defineSupplier(Tifa._NUM_TYPE()),
  1366. 'modf': Tifa.defineSupplier(Tifa._NUM_TYPE()),
  1367. 'trunc': Tifa.defineSupplier(Tifa._NUM_TYPE()),
  1368. 'exp': Tifa.defineSupplier(Tifa._NUM_TYPE()),
  1369. 'expm1': Tifa.defineSupplier(Tifa._NUM_TYPE()),
  1370. 'log': Tifa.defineSupplier(Tifa._NUM_TYPE()),
  1371. 'log1p': Tifa.defineSupplier(Tifa._NUM_TYPE()),
  1372. 'log2': Tifa.defineSupplier(Tifa._NUM_TYPE()),
  1373. 'log10': Tifa.defineSupplier(Tifa._NUM_TYPE()),
  1374. 'pow': Tifa.defineSupplier(Tifa._NUM_TYPE()),
  1375. 'sqrt': Tifa.defineSupplier(Tifa._NUM_TYPE()),
  1376. 'acos': Tifa.defineSupplier(Tifa._NUM_TYPE()),
  1377. 'sin': Tifa.defineSupplier(Tifa._NUM_TYPE()),
  1378. 'cos': Tifa.defineSupplier(Tifa._NUM_TYPE()),
  1379. 'tan': Tifa.defineSupplier(Tifa._NUM_TYPE()),
  1380. 'asin': Tifa.defineSupplier(Tifa._NUM_TYPE()),
  1381. 'acos': Tifa.defineSupplier(Tifa._NUM_TYPE()),
  1382. 'atan': Tifa.defineSupplier(Tifa._NUM_TYPE()),
  1383. 'atan2': Tifa.defineSupplier(Tifa._NUM_TYPE()),
  1384. 'hypot': Tifa.defineSupplier(Tifa._NUM_TYPE()),
  1385. 'degrees': Tifa.defineSupplier(Tifa._NUM_TYPE()),
  1386. 'radians': Tifa.defineSupplier(Tifa._NUM_TYPE()),
  1387. 'sinh': Tifa.defineSupplier(Tifa._NUM_TYPE()),
  1388. 'cosh': Tifa.defineSupplier(Tifa._NUM_TYPE()),
  1389. 'tanh': Tifa.defineSupplier(Tifa._NUM_TYPE()),
  1390. 'asinh': Tifa.defineSupplier(Tifa._NUM_TYPE()),
  1391. 'acosh': Tifa.defineSupplier(Tifa._NUM_TYPE()),
  1392. 'atanh': Tifa.defineSupplier(Tifa._NUM_TYPE()),
  1393. 'erf': Tifa.defineSupplier(Tifa._NUM_TYPE()),
  1394. 'erfc': Tifa.defineSupplier(Tifa._NUM_TYPE()),
  1395. 'gamma': Tifa.defineSupplier(Tifa._NUM_TYPE()),
  1396. 'lgamma': Tifa.defineSupplier(Tifa._NUM_TYPE()),
  1397. 'pi': Tifa._NUM_TYPE(),
  1398. 'e': Tifa._NUM_TYPE(),
  1399. 'tau': Tifa._NUM_TYPE(),
  1400. 'inf': Tifa._NUM_TYPE(),
  1401. 'nan': Tifa._NUM_TYPE(),
  1402. }
  1403. }
  1404. }
  1405. Tifa.prototype.loadModule = function(chain, position) {
  1406. var moduleNames = chain.split('.');
  1407. if (moduleNames[0] in Tifa.MODULES) {
  1408. var baseModule = Tifa.MODULES[moduleNames[0]];
  1409. for (var i=1, len=moduleNames.length; i<len; i++) {
  1410. if (baseModule.name == "Module" &&
  1411. moduleNames[i] in baseModule.submodules) {
  1412. baseModule = baseModule.submodules[moduleNames[i]];
  1413. } else {
  1414. this.reportIssue("Submodule not found",
  1415. {"name": chain, "position": position})
  1416. }
  1417. }
  1418. return baseModule;
  1419. } else {
  1420. this.reportIssue("Module not found",
  1421. {"name": chain, "position": position});
  1422. return Tifa._MODULE_TYPE();
  1423. }
  1424. }
  1425. Tifa.prototype.loadBuiltinAttr = function(type, func, attr, position) {
  1426. switch (type.name) {
  1427. case "File":
  1428. switch (attr) {
  1429. case "close": return Tifa.defineFunction(
  1430. function (analyzer, functionType, callee, args, position) {}
  1431. );
  1432. case "read": return Tifa.defineSupplier(Tifa._STR_TYPE());
  1433. case "readlines": return Tifa.defineSupplier(Tifa._LIST_OF_TYPE(Tifa._STR_TYPE()));
  1434. }
  1435. break;
  1436. case "List":
  1437. switch (attr) {
  1438. case "append": return Tifa.defineFunction(
  1439. function (analyzer, functionType, callee, args, position) {
  1440. if (callee) {
  1441. analyzer.appendVariable(callee, Tifa._LIST_OF_TYPE(Tifa.cloneType(type.subtype)), position);
  1442. }
  1443. type.empty = false;
  1444. type.subtype = args[0];
  1445. }
  1446. );
  1447. };
  1448. break;
  1449. case "Dict":
  1450. switch (attr) {
  1451. case "items": return Tifa.defineFunction(
  1452. function (analyzer, functionType, callee, args, position) {
  1453. return Tifa._LIST_OF_TYPE({
  1454. 'name': 'Tuple', 'subtypes': [type.keys, type.values], 'empty': false
  1455. });
  1456. }
  1457. );
  1458. };
  1459. break;
  1460. case "Module":
  1461. if ("fields" in type) {
  1462. if (attr in type.fields) {
  1463. return type.fields[attr];
  1464. } else {
  1465. return Tifa._UNKNOWN_TYPE();
  1466. }
  1467. } else {
  1468. return Tifa._UNKNOWN_TYPE();
  1469. }
  1470. break;
  1471. case "Str":
  1472. switch (attr) {
  1473. // Strings
  1474. case "capitalize": case "center": case "expandtabs":
  1475. case "join": case "ljust": case "lower":
  1476. case "lstrip": case "replace": case "rjust":
  1477. case "rstrip": case "strip": case "swapcase":
  1478. case "title": case "translate": case "upper":
  1479. case "zfill":
  1480. return Tifa.defineSupplier(Tifa._STR_TYPE());
  1481. // Numbers
  1482. case "count": case "find": case "index":
  1483. case "rfind": case "rindex":
  1484. return Tifa.defineSupplier(Tifa._NUM_TYPE());
  1485. //Booleans
  1486. case "endswith": case "isalnum": case "isalpha":
  1487. case "isdigit": case "islower": case "isspace":
  1488. case "istitle": case "isupper": case "startswith":
  1489. return Tifa.defineSupplier(Tifa._BOOL_TYPE());
  1490. // Lists
  1491. case "rsplit": case "split": case "splitlines":
  1492. return Tifa.defineSupplier(Tifa._LIST_OF_TYPE(Tifa._STR_TYPE()));
  1493. }
  1494. break;
  1495. }
  1496. // Catch mistakes
  1497. if (attr == "append") {
  1498. this.reportIssue('Append to non-list',
  1499. {'name': this.identifyCaller(func),
  1500. 'position': position,
  1501. 'type': type})
  1502. }
  1503. return Tifa._NONE_TYPE();
  1504. }