From f04b6df23e0cdbac376f4918eb70acc6f76b719e Mon Sep 17 00:00:00 2001 From: Sumanth Yedoti Date: Thu, 21 Apr 2022 15:32:58 +0530 Subject: [PATCH 1/5] Refactor typeInference file --- lib/typeInference.js | 39 ++++++++++++++++++--------------------- 1 file changed, 18 insertions(+), 21 deletions(-) diff --git a/lib/typeInference.js b/lib/typeInference.js index b3d2609..e7d4f69 100644 --- a/lib/typeInference.js +++ b/lib/typeInference.js @@ -261,7 +261,9 @@ const callExprType = (expr, localTypeObj = {}, id) => { const reduceTypes = (typesArr, localTypeObj, id) => { const reducedType = typesArr .map((e) => { - if (e.type !== undefined && e.type === 'Identifier') { return { id: e.name, type: exprType(e, localTypeObj, id) } } + if (e.type !== undefined && e.type === 'Identifier') { + return { id: e.name, type: exprType(e, localTypeObj, id) } + } return { id: null, type: exprType(e, localTypeObj, id) } }) .reduce((exp1, exp2) => { @@ -278,39 +280,34 @@ const reduceTypes = (typesArr, localTypeObj, id) => { localTypeObj[exp2.id] = exp1.type } if (typeof exp1.type === 'object' && typeof exp2.type === 'object') { - return exp1.type.type === exp2.type.type ? exp1.type : false + return exp1.type.type === exp2.type.type ? exp1.type : null } - return exp1.type === exp2.type ? exp1 : false + return exp1.type === exp2.type ? exp1 : null }) - return reducedType !== false ? reducedType.type : false + return reducedType !== null ? reducedType.type : null } const switchStatementType = (body, localTypeObj, id) => { const cases = body.cases - const [initialPattern] = cases - let [returnType, acceptedType] = [ - initialPattern.consequent[0].argument, - initialPattern.test - ].map((exp) => exprType(exp, localTypeObj, id)) + const initialPattern = cases[0] + let returnType = exprType(initialPattern.consequent[0].argument, localTypeObj, id) + const acceptedType = exprType(initialPattern.test, localTypeObj, id) const paramId = body.discriminant.name localTypeObj[paramId] = acceptedType - const [caseArgArray, caseTestArray] = [ - cases.map((c) => c.consequent[0].argument), - cases.map((c) => - c.test === null - ? { type: 'Identifier', name: paramId, sType: acceptedType } - : c.test - ) - ] - const checkTestType = reduceTypes(caseTestArray, localTypeObj, id) - const checkArgsType = reduceTypes(caseArgArray, localTypeObj, id) - if (returnType === 'needsInference') returnType = checkArgsType + const caseConseqArray = cases.map((c) => c.consequent[0].argument) + const caseTestArray = cases.map((c) => c.test === null + ? { type: 'Identifier', name: paramId, sType: acceptedType } + : c.test + ) + const conseqType = reduceTypes(caseConseqArray, localTypeObj, id) + const testType = reduceTypes(caseTestArray, localTypeObj, id) + if (returnType === 'needsInference') returnType = conseqType globalTypesObj[id] = { type: 'function', paramTypes: makeParamTypeArray(localTypeObj, id), returnType } - return checkArgsType === false || checkTestType === false ? null : returnType + return (conseqType && testType) ? returnType : null } const arrayExprType = (expr, localTypeObj, id) => { From 532ce4e249e1fc7fcbc61f96f856962ff2d46379 Mon Sep 17 00:00:00 2001 From: Sumanth Yedoti Date: Fri, 22 Apr 2022 12:16:10 +0530 Subject: [PATCH 2/5] Add type checking for function calls --- lib/cli.js | 2 +- lib/typeInference.js | 125 +++++++++++++++++++++++++++++-------------- 2 files changed, 86 insertions(+), 41 deletions(-) diff --git a/lib/cli.js b/lib/cli.js index 2a0aa9d..66a7e9a 100644 --- a/lib/cli.js +++ b/lib/cli.js @@ -81,7 +81,7 @@ const evalFunction = (argsObj, infile, outfile) => { if (tree.error) showAndExit(makeErrStr(tree, infile)) if (argsObj.ast) showAndExit(JSON.stringify(tree, null, 2)) const maybeTypeErr = argsObj.t ? tree.body : inferTypes(tree.body) - if (maybeTypeErr.error) showAndExit(JSON.stringify(maybeTypeErr)) + if (maybeTypeErr.error) showAndExit(maybeTypeErr.message) const out = escodegen.generate(tree, { format, comment: true }) fs.writeFileSync(outfile, core + out + '\n', 'utf8') const commandLineArgs = argsObj._.slice(1) diff --git a/lib/typeInference.js b/lib/typeInference.js index e7d4f69..6cf5037 100644 --- a/lib/typeInference.js +++ b/lib/typeInference.js @@ -7,7 +7,9 @@ const inbuiltPropSpec = require('./inbuiltMethods').inbuiltProps const inbuiltObjects = require('./inbuiltMethods').inbuiltObjects const jsFunctions = require('./jsFunctions') const isEmptyObj = require('./utilityFunctions').isEmptyObj + const globalTypesObj = {} +const funcCallTypesObj = {} const isFunction = (id, typeObj) => typeObj[id] !== undefined && @@ -44,6 +46,7 @@ const unaryExprType = (expr) => { const getType = (expr, expectedType, localTypeObj, id) => { if (expr !== undefined) { const type = exprType(expr, localTypeObj, id) + if (expr.type === 'Identifier') return type return type === expectedType || expectedType === 'bool' ? type : type === 'needsInference' @@ -73,8 +76,12 @@ const checkTypes = (expr, expectedType, localTypeObj, id) => { const assignTypeToUninferredIdentifier = (expr, inferredType, localTypeObj) => { if (isEmptyObj(localTypeObj)) return localTypeObj const [left, right] = [expr.left.name, expr.right.name] - if (left !== undefined && localTypeObj[left] === 'needsInference') { localTypeObj[left] = inferredType } - if (right !== undefined && localTypeObj[right] === 'needsInference') { localTypeObj[right] = inferredType } + if (left !== undefined && localTypeObj[left] === 'needsInference') { + localTypeObj[left] = inferredType + } + if (right !== undefined && localTypeObj[right] === 'needsInference') { + localTypeObj[right] = inferredType + } } const binaryExprType = (expr, localTypeObj, id) => { @@ -134,13 +141,13 @@ const conditionalExprType = (expr, localTypeObj, id) => { return consequentType === alternateType ? consequentType : null } -const mapArgTypeToParams = (params, args, localTypeObj, id) => { - return params.map((param, index) => - args[index].sType === undefined - ? exprType(args[index], localTypeObj, id) - : args[index].sType - ) -} +// const mapArgTypeToParams = (params, args, localTypeObj, id) => { +// return params.map((param, index) => +// args[index].sType === undefined +// ? exprType(args[index], localTypeObj, id) +// : args[index].sType +// ) +// } const makeParamTypeArray = (localTypeObj) => { const paramTypes = [] @@ -217,32 +224,33 @@ const checkForRecursion = (callee, id, localTypeObj, args) => { const getFunctionType = (calleeName) => jsFunctions[calleeName] const callExprType = (expr, localTypeObj = {}, id) => { + const calleeName = expr.callee.name if ( expr.callee.type === 'Identifier' && - globalTypesObj[expr.callee.name] === undefined + globalTypesObj[calleeName] === undefined ) { - const calleeName = expr.callee.name globalTypesObj[calleeName] = getFunctionType(calleeName) } - const [params, body, args] = [ - expr.callee.params, - expr.callee.body, - expr.arguments - ] - if (expr.sType !== undefined) return expr.sType // if call has type already set - if ( - params !== undefined && - body !== undefined && - (!isEmptyObj(localTypeObj) || expr.callee.id === null) - ) { - const typesOfParams = mapArgTypeToParams(params, args, localTypeObj, id) - args.map((arg) => exprType(arg, localTypeObj, id)) - params.map((param, index) => { - localTypeObj[param.name] = typesOfParams[index] - return param - }) - return exprType(body, localTypeObj, id) - } + const args = expr.arguments + // const [params, body, args] = [ + // expr.callee.params, + // expr.callee.body, + // expr.arguments + // ] + // if (expr.sType !== undefined) return expr.sType // if call has type already set + // if ( + // params !== undefined && + // body !== undefined && + // (!isEmptyObj(localTypeObj) || expr.callee.id === null) + // ) { + // const typesOfParams = mapArgTypeToParams(params, args, localTypeObj, id) + // args.map((arg) => exprType(arg, localTypeObj, id)) + // params.map((param, index) => { + // localTypeObj[param.name] = typesOfParams[index] + // return param + // }) + // return exprType(body, localTypeObj, id) + // } const isRecursive = checkForRecursion(expr.callee.name, id, localTypeObj, args) let result = exprType(expr.callee, localTypeObj, id) if (result === null) return null @@ -255,7 +263,9 @@ const callExprType = (expr, localTypeObj = {}, id) => { localTypeObj, id ) - return match ? returnType : null + returnType = match ? returnType : null + funcCallTypesObj[calleeName] = returnType + return returnType } const reduceTypes = (typesArr, localTypeObj, id) => { @@ -534,7 +544,6 @@ const blockStatementType = (stmnt, localTypeObj, id) => { const exprType = (expr, localTypeObj = {}, id = null) => { if (expr !== null && expr.sType !== undefined && expr.sType === 'IO') { return 'IO' } if (expr === null) return 'needsInference' - if (expr.type === 'ExpressionStatement') expr = expr.expression const type = expr.type switch (type) { case 'Literal': @@ -576,6 +585,21 @@ const declTypeExtract = (stmnt) => { globalTypesObj[id] = type } } +const exprTypeExtract = (stmnt) => { + const expr = stmnt.expression + if (expr.sType === 'IO' && !isIOCall(expr)) { + const args = expr.arguments + args.forEach(arg => exprType(arg)) + } else if (!isIOCall(expr)) { + exprType(expr) + } +} + +const isIOCall = (expr) => { + if (!expr.callee.object) return false + const method = expr.callee.object.name + return globalTypesObj[method] === 'IO' +} const loadInbuiltObjects = () => { for (const obj in inbuiltObjects) { @@ -589,22 +613,43 @@ const delObj = (obj) => { } } -const types = (body) => { - loadInbuiltObjects() - body.forEach((expr) => { - const type = expr.type - if (type === 'VariableDeclaration') declTypeExtract(expr) - }) - const errorObj = { error: false } +const errorObj = { error: false } + +const makeTypeError = (error) => { + return `TypeError at ${error.type} '${error.id}'` +} + +const checkForErrors = () => { for (const decl in globalTypesObj) { if (globalTypesObj[decl] === null) { errorObj.error = true errorObj.id = decl + errorObj.type = 'VariableDeclaration' + break + } + } + if (errorObj.error) return + for (const call in funcCallTypesObj) { + if (funcCallTypesObj[call] === null) { + errorObj.error = true + errorObj.id = call + errorObj.type = 'CallExpression' break } } +} + +const types = (body) => { + loadInbuiltObjects() + body.forEach((expr) => { + const type = expr.type + if (type === 'VariableDeclaration') declTypeExtract(expr) + else if (type === 'ExpressionStatement') exprTypeExtract(expr) + }) + checkForErrors() delObj(globalTypesObj) - return errorObj.error ? errorObj : body + delObj(funcCallTypesObj) + return errorObj.error ? { ...errorObj, message: makeTypeError(errorObj) } : body } /* Module Exports types */ From f2ce5a86604bf94633fc048143bcec283facead5 Mon Sep 17 00:00:00 2001 From: Sumanth Yedoti Date: Fri, 22 Apr 2022 15:19:39 +0530 Subject: [PATCH 3/5] Fix test cases errors --- lib/cli.js | 2 +- lib/typeInference.js | 14 ++++++++------ test/assert/typeErrors/typeError.json | 7 ++++++- test/sourceFiles.test.js | 5 ++--- 4 files changed, 17 insertions(+), 11 deletions(-) diff --git a/lib/cli.js b/lib/cli.js index 66a7e9a..c46d4e8 100644 --- a/lib/cli.js +++ b/lib/cli.js @@ -81,7 +81,7 @@ const evalFunction = (argsObj, infile, outfile) => { if (tree.error) showAndExit(makeErrStr(tree, infile)) if (argsObj.ast) showAndExit(JSON.stringify(tree, null, 2)) const maybeTypeErr = argsObj.t ? tree.body : inferTypes(tree.body) - if (maybeTypeErr.error) showAndExit(maybeTypeErr.message) + if (maybeTypeErr !== null) showAndExit(maybeTypeErr.message) const out = escodegen.generate(tree, { format, comment: true }) fs.writeFileSync(outfile, core + out + '\n', 'utf8') const commandLineArgs = argsObj._.slice(1) diff --git a/lib/typeInference.js b/lib/typeInference.js index 6cf5037..d6b34a4 100644 --- a/lib/typeInference.js +++ b/lib/typeInference.js @@ -251,7 +251,7 @@ const callExprType = (expr, localTypeObj = {}, id) => { // }) // return exprType(body, localTypeObj, id) // } - const isRecursive = checkForRecursion(expr.callee.name, id, localTypeObj, args) + const isRecursive = checkForRecursion(calleeName, id, localTypeObj, args) let result = exprType(expr.callee, localTypeObj, id) if (result === null) return null if (expr.callee.id === null) result = [result.paramTypes, result.returnType] @@ -264,7 +264,9 @@ const callExprType = (expr, localTypeObj = {}, id) => { id ) returnType = match ? returnType : null - funcCallTypesObj[calleeName] = returnType + if (calleeName) { + funcCallTypesObj[calleeName] = returnType + } return returnType } @@ -625,21 +627,21 @@ const checkForErrors = () => { errorObj.error = true errorObj.id = decl errorObj.type = 'VariableDeclaration' - break + return } } - if (errorObj.error) return for (const call in funcCallTypesObj) { if (funcCallTypesObj[call] === null) { errorObj.error = true errorObj.id = call errorObj.type = 'CallExpression' - break + return } } } const types = (body) => { + delObj(errorObj) loadInbuiltObjects() body.forEach((expr) => { const type = expr.type @@ -649,7 +651,7 @@ const types = (body) => { checkForErrors() delObj(globalTypesObj) delObj(funcCallTypesObj) - return errorObj.error ? { ...errorObj, message: makeTypeError(errorObj) } : body + return errorObj.error ? { ...errorObj, message: makeTypeError(errorObj) } : null } /* Module Exports types */ diff --git a/test/assert/typeErrors/typeError.json b/test/assert/typeErrors/typeError.json index 4d2d1a2..dd6de37 100644 --- a/test/assert/typeErrors/typeError.json +++ b/test/assert/typeErrors/typeError.json @@ -1 +1,6 @@ -{ "error": true, "id": "a" } +{ + "error": true, + "id": "a", + "message": "TypeError at VariableDeclaration 'a'", + "type": "VariableDeclaration" +} diff --git a/test/sourceFiles.test.js b/test/sourceFiles.test.js index 3e9efdd..39e0638 100644 --- a/test/sourceFiles.test.js +++ b/test/sourceFiles.test.js @@ -20,9 +20,8 @@ const generateTree = input => { } const tree = parser(rest) if (tree.error) return tree - const newTree = typeInfer(tree.body) - if (newTree.error) return newTree - tree.body = newTree + const maybeTypeErrors = typeInfer(tree.body) + if (maybeTypeErrors !== null) return maybeTypeErrors return tree } From 346c77c0e1001a4e21d0e2035c332bfd7ae63076 Mon Sep 17 00:00:00 2001 From: Sumanth Yedoti Date: Mon, 25 Apr 2022 10:39:51 +0530 Subject: [PATCH 4/5] Add test for function call with type error --- test/assert/funcDecl.json | 18 ++++++++++++++++++ .../typeErrors/funcDeclWithTypeError.json | 7 +++++++ test/src/funcDecl.cl | 2 ++ test/src/typeErrors/funcDeclWithTypeError.cl | 5 +++++ 4 files changed, 32 insertions(+) create mode 100644 test/assert/typeErrors/funcDeclWithTypeError.json create mode 100644 test/src/typeErrors/funcDeclWithTypeError.cl diff --git a/test/assert/funcDecl.json b/test/assert/funcDecl.json index 1ca1b31..7b0efa4 100644 --- a/test/assert/funcDecl.json +++ b/test/assert/funcDecl.json @@ -121,6 +121,24 @@ } ], "kind": "const" + }, + { + "type": "ExpressionStatement", + "expression": { + "type": "CallExpression", + "callee": { + "type": "Identifier", + "name": "fact" + }, + "arguments": [ + { + "type": "Literal", + "value": 10, + "raw": "10", + "sType": "number" + } + ] + } } ], "sourceType": "script" diff --git a/test/assert/typeErrors/funcDeclWithTypeError.json b/test/assert/typeErrors/funcDeclWithTypeError.json new file mode 100644 index 0000000..1ff9502 --- /dev/null +++ b/test/assert/typeErrors/funcDeclWithTypeError.json @@ -0,0 +1,7 @@ +{ + "error": true, + "id": "fact", + "message": "TypeError at CallExpression 'fact'", + "type": "CallExpression" +} + diff --git a/test/src/funcDecl.cl b/test/src/funcDecl.cl index fcce681..62dfdac 100644 --- a/test/src/funcDecl.cl +++ b/test/src/funcDecl.cl @@ -1,3 +1,5 @@ fact 0 = 1 fact 1 = 1 fact n = n * fact (n - 1) + +fact 10 diff --git a/test/src/typeErrors/funcDeclWithTypeError.cl b/test/src/typeErrors/funcDeclWithTypeError.cl new file mode 100644 index 0000000..65d7e79 --- /dev/null +++ b/test/src/typeErrors/funcDeclWithTypeError.cl @@ -0,0 +1,5 @@ +fact 0 = 1 +fact 1 = 1 +fact n = n * fact (n - 1) + +fact 'a' From 5044e8edad9dca0fc8689ced4aa5765c3d103799 Mon Sep 17 00:00:00 2001 From: Sumanth Yedoti Date: Mon, 25 Apr 2022 10:57:48 +0530 Subject: [PATCH 5/5] Show AST with --ast option only when no type errors or -t option is given --- lib/cli.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/cli.js b/lib/cli.js index c46d4e8..14fe720 100644 --- a/lib/cli.js +++ b/lib/cli.js @@ -79,9 +79,9 @@ const evalFunction = (argsObj, infile, outfile) => { const core = libName ? importCore(libName) : '' const tree = parser(program) if (tree.error) showAndExit(makeErrStr(tree, infile)) - if (argsObj.ast) showAndExit(JSON.stringify(tree, null, 2)) - const maybeTypeErr = argsObj.t ? tree.body : inferTypes(tree.body) + const maybeTypeErr = argsObj.t ? null : inferTypes(tree.body) if (maybeTypeErr !== null) showAndExit(maybeTypeErr.message) + if (argsObj.ast) showAndExit(JSON.stringify(tree, null, 2)) const out = escodegen.generate(tree, { format, comment: true }) fs.writeFileSync(outfile, core + out + '\n', 'utf8') const commandLineArgs = argsObj._.slice(1)