UNPKG

roblox-ts

Version:

A TypeScript-to-Luau Compiler for Roblox

371 lines 19 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.transformForStatement = transformForStatement; const luau_ast_1 = __importDefault(require("@roblox-ts/luau-ast")); const diagnostics_1 = require("../../../Shared/diagnostics"); const assert_1 = require("../../../Shared/util/assert"); const DiagnosticService_1 = require("../../classes/DiagnosticService"); const transformExpression_1 = require("../expressions/transformExpression"); const transformIdentifier_1 = require("../expressions/transformIdentifier"); const transformExpressionStatement_1 = require("./transformExpressionStatement"); const transformVariableStatement_1 = require("./transformVariableStatement"); const transformStatementList_1 = require("../transformStatementList"); const createTruthinessChecks_1 = require("../../util/createTruthinessChecks"); const getDeclaredVariables_1 = require("../../util/getDeclaredVariables"); const getStatements_1 = require("../../util/getStatements"); const offset_1 = require("../../util/offset"); const traversal_1 = require("../../util/traversal"); const types_1 = require("../../util/types"); const typescript_1 = __importDefault(require("typescript")); function addFinalizersToIfStatement(node, finalizers) { if (luau_ast_1.default.list.isNonEmpty(node.statements)) { addFinalizers(node.statements, node.statements.head, finalizers); } if (luau_ast_1.default.list.isList(node.elseBody)) { if (luau_ast_1.default.list.isNonEmpty(node.elseBody)) { addFinalizers(node.elseBody, node.elseBody.head, finalizers); } } else { addFinalizersToIfStatement(node.elseBody, finalizers); } } function addFinalizers(list, node, finalizers) { (0, assert_1.assert)(!luau_ast_1.default.list.isEmpty(list)); const statement = node.value; if (luau_ast_1.default.isContinueStatement(statement)) { const finalizersClone = luau_ast_1.default.list.clone(finalizers); luau_ast_1.default.list.forEach(finalizersClone, node => (node.parent = statement.parent)); if (node.prev) { node.prev.next = finalizersClone.head; } else if (node === list.head) { list.head = finalizersClone.head; } node.prev = finalizersClone.tail; finalizersClone.tail.next = node; } if (luau_ast_1.default.isDoStatement(statement)) { if (luau_ast_1.default.list.isNonEmpty(statement.statements)) { addFinalizers(statement.statements, statement.statements.head, finalizers); } } else if (luau_ast_1.default.isIfStatement(statement)) { addFinalizersToIfStatement(statement, finalizers); } if (node.next) { addFinalizers(list, node.next, finalizers); } } function canSkipClone(state, initializer, id) { return !typescript_1.default.FindAllReferences.Core.isSymbolReferencedInFile(id, state.typeChecker, id.getSourceFile(), initializer); } function isIdWriteOrAsyncRead(state, forStatement, id) { return typescript_1.default.FindAllReferences.Core.eachSymbolReferenceInFile(id, state.typeChecker, id.getSourceFile(), token => { if (typescript_1.default.isWriteAccess(token) && (!forStatement.incrementor || !(0, traversal_1.isAncestorOf)(forStatement.incrementor, token))) { return true; } const ancestor = (0, traversal_1.getAncestor)(token, v => v === forStatement || typescript_1.default.isFunctionLike(v)); if (ancestor && ancestor !== forStatement) { return true; } }, forStatement); } function transformForStatementFallback(state, node) { const { initializer, condition, incrementor, statement } = node; const result = luau_ast_1.default.list.make(); const whileStatements = luau_ast_1.default.list.make(); const finalizerStatements = luau_ast_1.default.list.make(); const variables = initializer && typescript_1.default.isVariableDeclarationList(initializer) ? (0, getDeclaredVariables_1.getDeclaredVariables)(initializer) : []; const hasWriteOrAsyncRead = new Set(); const skipClone = new Set(); if (initializer && typescript_1.default.isVariableDeclarationList(initializer)) { for (const id of variables) { const symbol = state.typeChecker.getSymbolAtLocation(id); (0, assert_1.assert)(symbol); if (isIdWriteOrAsyncRead(state, node, id)) { hasWriteOrAsyncRead.add(symbol); } if (canSkipClone(state, initializer, id)) { skipClone.add(symbol); } } } if (initializer) { if (typescript_1.default.isVariableDeclarationList(initializer)) { if ((0, transformVariableStatement_1.isVarDeclaration)(initializer)) { DiagnosticService_1.DiagnosticService.addDiagnostic(diagnostics_1.errors.noVar(node)); } for (const id of variables) { const symbol = state.typeChecker.getSymbolAtLocation(id); (0, assert_1.assert)(symbol); if (hasWriteOrAsyncRead.has(symbol)) { if (skipClone.has(symbol)) { state.symbolToIdMap.set(symbol, luau_ast_1.default.tempId(id.getText())); } else { const copyId = luau_ast_1.default.tempId(`${id.getText()}Copy`); state.symbolToIdMap.set(symbol, copyId); } } } for (const declaration of initializer.declarations) { const [decStatements, decPrereqs] = state.capture(() => { const result = luau_ast_1.default.list.make(); const [decStatements, decPrereqs] = state.capture(() => (0, transformVariableStatement_1.transformVariableDeclaration)(state, declaration)); luau_ast_1.default.list.pushList(result, decPrereqs); luau_ast_1.default.list.pushList(result, decStatements); return result; }); luau_ast_1.default.list.pushList(result, decPrereqs); luau_ast_1.default.list.pushList(result, decStatements); } for (const id of variables) { const symbol = state.typeChecker.getSymbolAtLocation(id); (0, assert_1.assert)(symbol); if (hasWriteOrAsyncRead.has(symbol)) { let tempId; if (skipClone.has(symbol)) { tempId = state.symbolToIdMap.get(symbol); (0, assert_1.assert)(tempId); } else { tempId = luau_ast_1.default.tempId(id.getText()); const copyId = state.symbolToIdMap.get(symbol); (0, assert_1.assert)(copyId); luau_ast_1.default.list.push(result, luau_ast_1.default.create(luau_ast_1.default.SyntaxKind.VariableDeclaration, { left: tempId, right: copyId, })); } state.symbolToIdMap.delete(symbol); const realId = (0, transformIdentifier_1.transformIdentifierDefined)(state, id); luau_ast_1.default.list.push(whileStatements, luau_ast_1.default.create(luau_ast_1.default.SyntaxKind.VariableDeclaration, { left: realId, right: tempId, })); luau_ast_1.default.list.push(finalizerStatements, luau_ast_1.default.create(luau_ast_1.default.SyntaxKind.Assignment, { left: tempId, operator: "=", right: realId, })); } } } else { const [statements, prereqs] = state.capture(() => (0, transformExpressionStatement_1.transformExpressionStatementInner)(state, initializer)); luau_ast_1.default.list.pushList(result, prereqs); luau_ast_1.default.list.pushList(result, statements); } } if (incrementor) { const shouldIncrement = luau_ast_1.default.tempId("shouldIncrement"); luau_ast_1.default.list.push(result, luau_ast_1.default.create(luau_ast_1.default.SyntaxKind.VariableDeclaration, { left: shouldIncrement, right: luau_ast_1.default.bool(false), })); const incrementorStatements = luau_ast_1.default.list.make(); const [statements, prereqs] = state.capture(() => (0, transformExpressionStatement_1.transformExpressionStatementInner)(state, incrementor)); luau_ast_1.default.list.pushList(incrementorStatements, prereqs); luau_ast_1.default.list.pushList(incrementorStatements, statements); luau_ast_1.default.list.push(whileStatements, luau_ast_1.default.create(luau_ast_1.default.SyntaxKind.IfStatement, { condition: shouldIncrement, statements: incrementorStatements, elseBody: luau_ast_1.default.list.make(luau_ast_1.default.create(luau_ast_1.default.SyntaxKind.Assignment, { left: shouldIncrement, operator: "=", right: luau_ast_1.default.bool(true), })), })); } let [conditionExp, conditionPrereqs] = state.capture(() => { if (condition) { return (0, createTruthinessChecks_1.createTruthinessChecks)(state, (0, transformExpression_1.transformExpression)(state, condition), condition); } else { return luau_ast_1.default.bool(true); } }); luau_ast_1.default.list.pushList(whileStatements, conditionPrereqs); if (!luau_ast_1.default.list.isEmpty(whileStatements)) { if (condition) { luau_ast_1.default.list.push(whileStatements, luau_ast_1.default.create(luau_ast_1.default.SyntaxKind.IfStatement, { condition: luau_ast_1.default.unary("not", conditionExp), statements: luau_ast_1.default.list.make(luau_ast_1.default.create(luau_ast_1.default.SyntaxKind.BreakStatement, {})), elseBody: luau_ast_1.default.list.make(), })); } conditionExp = luau_ast_1.default.bool(true); } luau_ast_1.default.list.pushList(whileStatements, (0, transformStatementList_1.transformStatementList)(state, statement, (0, getStatements_1.getStatements)(statement))); if (luau_ast_1.default.list.isNonEmpty(whileStatements) && luau_ast_1.default.list.isNonEmpty(finalizerStatements)) { addFinalizers(whileStatements, whileStatements.head, finalizerStatements); } if (!whileStatements.tail || !luau_ast_1.default.isFinalStatement(whileStatements.tail.value)) { luau_ast_1.default.list.pushList(whileStatements, finalizerStatements); } luau_ast_1.default.list.push(result, luau_ast_1.default.create(luau_ast_1.default.SyntaxKind.WhileStatement, { condition: conditionExp, statements: whileStatements, })); return result.head === result.tail ? result : luau_ast_1.default.list.make(luau_ast_1.default.create(luau_ast_1.default.SyntaxKind.DoStatement, { statements: result })); } function getOptimizedIncrementorStepValue(state, incrementor, idSymbol) { if (typescript_1.default.isBinaryExpression(incrementor) && typescript_1.default.isIdentifier(incrementor.left) && state.typeChecker.getSymbolAtLocation(incrementor.left) === idSymbol && incrementor.operatorToken.kind === typescript_1.default.SyntaxKind.PlusEqualsToken && typescript_1.default.isNumericLiteral(incrementor.right) && isProbablyInteger(state, incrementor.right)) { return Number(incrementor.right.getText()); } else if (typescript_1.default.isBinaryExpression(incrementor) && incrementor.operatorToken.kind === typescript_1.default.SyntaxKind.MinusEqualsToken && typescript_1.default.isNumericLiteral(incrementor.right) && isProbablyInteger(state, incrementor.right)) { return -Number(incrementor.right.getText()); } else if ((typescript_1.default.isPostfixUnaryExpression(incrementor) || typescript_1.default.isPrefixUnaryExpression(incrementor)) && typescript_1.default.isIdentifier(incrementor.operand) && state.typeChecker.getSymbolAtLocation(incrementor.operand) === idSymbol && incrementor.operator === typescript_1.default.SyntaxKind.PlusPlusToken) { return 1; } else if ((typescript_1.default.isPostfixUnaryExpression(incrementor) || typescript_1.default.isPrefixUnaryExpression(incrementor)) && typescript_1.default.isIdentifier(incrementor.operand) && state.typeChecker.getSymbolAtLocation(incrementor.operand) === idSymbol && incrementor.operator === typescript_1.default.SyntaxKind.MinusMinusToken) { return -1; } return undefined; } function isSizeMacro(state, expression) { if (typescript_1.default.isCallExpression(expression)) { const expType = state.typeChecker.getNonOptionalType(state.getType(expression.expression)); const symbol = (0, types_1.getFirstDefinedSymbol)(state, expType); if (symbol) { const macro = state.services.macroManager.getPropertyCallMacro(symbol); if (macro && symbol.name === "size") { return true; } } } return false; } function isMutatedInBody(state, identifier, body) { return (typescript_1.default.FindAllReferences.Core.eachSymbolReferenceInFile(identifier, state.typeChecker, identifier.getSourceFile(), token => { const parent = (0, traversal_1.skipUpwards)(token).parent; if (typescript_1.default.isAssignmentExpression(parent) && (0, traversal_1.skipDownwards)(parent.left) === token) { return true; } else if (typescript_1.default.isUnaryExpressionWithWrite(parent) && (0, traversal_1.skipDownwards)(parent.operand) === token) { return true; } return false; }, body) === true); } function isProbablyInteger(state, expression) { if (typescript_1.default.isNumericLiteral(expression)) { return Number.isInteger(Number(expression.getText())); } else if (typescript_1.default.isBinaryExpression(expression)) { if (expression.operatorToken.kind === typescript_1.default.SyntaxKind.PlusToken || expression.operatorToken.kind === typescript_1.default.SyntaxKind.MinusToken || expression.operatorToken.kind === typescript_1.default.SyntaxKind.AsteriskToken || expression.operatorToken.kind === typescript_1.default.SyntaxKind.AsteriskAsteriskToken) { return isProbablyInteger(state, expression.left) && isProbablyInteger(state, expression.right); } } else if (typescript_1.default.isPrefixUnaryExpression(expression)) { if (expression.operator === typescript_1.default.SyntaxKind.PlusToken || expression.operator === typescript_1.default.SyntaxKind.MinusToken) { return isProbablyInteger(state, expression.operand); } } else if (isSizeMacro(state, expression)) { return true; } else if ((0, types_1.isDefinitelyType)(state.getType(expression), t => t.isNumberLiteral() && Number.isInteger(t.value))) { return true; } return false; } function transformForStatementOptimized(state, node) { const { initializer, condition, incrementor, statement } = node; if (!initializer || !typescript_1.default.isVariableDeclarationList(initializer) || initializer.declarations.length !== 1) { return undefined; } const { name: decName, initializer: decInit } = initializer.declarations[0]; if (!typescript_1.default.isIdentifier(decName) || decInit === undefined) { return undefined; } const idSymbol = state.typeChecker.getSymbolAtLocation(decName); if (!idSymbol) { return undefined; } if (!isProbablyInteger(state, decInit)) { return undefined; } if (!incrementor) { return undefined; } const stepValue = getOptimizedIncrementorStepValue(state, incrementor, idSymbol); if (stepValue === undefined) { return undefined; } if (!condition || !typescript_1.default.isBinaryExpression(condition)) { return undefined; } if (condition.operatorToken.kind === typescript_1.default.SyntaxKind.LessThanToken || condition.operatorToken.kind === typescript_1.default.SyntaxKind.LessThanEqualsToken) { if (stepValue < 0) { return undefined; } } else if (condition.operatorToken.kind === typescript_1.default.SyntaxKind.GreaterThanToken || condition.operatorToken.kind === typescript_1.default.SyntaxKind.GreaterThanEqualsToken) { if (stepValue > 0) { return undefined; } } else { return undefined; } if (!isProbablyInteger(state, condition.right)) { return undefined; } if (isMutatedInBody(state, decName, statement)) { return undefined; } const result = luau_ast_1.default.list.make(); const id = (0, transformIdentifier_1.transformIdentifierDefined)(state, decName); const [start, startPrereqs] = state.capture(() => (0, transformExpression_1.transformExpression)(state, decInit)); luau_ast_1.default.list.pushList(result, startPrereqs); let [end, endPrereqs] = state.capture(() => (0, transformExpression_1.transformExpression)(state, condition.right)); luau_ast_1.default.list.pushList(result, endPrereqs); const step = luau_ast_1.default.number(stepValue); const statements = (0, transformStatementList_1.transformStatementList)(state, statement, (0, getStatements_1.getStatements)(statement)); if (condition.operatorToken.kind === typescript_1.default.SyntaxKind.LessThanToken) { end = (0, offset_1.offset)(end, -1); } else if (condition.operatorToken.kind === typescript_1.default.SyntaxKind.GreaterThanToken) { end = (0, offset_1.offset)(end, 1); } luau_ast_1.default.list.push(result, luau_ast_1.default.create(luau_ast_1.default.SyntaxKind.NumericForStatement, { id, start, end, step, statements })); return result; } function transformForStatement(state, node) { if (state.data.projectOptions.optimizedLoops) { const optimized = transformForStatementOptimized(state, node); if (optimized) { return optimized; } } return transformForStatementFallback(state, node); } //# sourceMappingURL=transformForStatement.js.map