Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/LuaLib.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@ export enum LuaLibFeature {
ObjectKeys = "ObjectKeys",
ObjectRest = "ObjectRest",
ObjectValues = "ObjectValues",
OptionalChainAccess = "OptionalChainAccess",
OptionalFunctionCall = "OptionalFunctionCall",
OptionalMethodCall = "OptionalMethodCall",
ParseFloat = "ParseFloat",
ParseInt = "ParseInt",
Set = "Set",
Expand Down
10 changes: 10 additions & 0 deletions src/lualib/OptionalChainAccess.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
function __TS__OptionalChainAccess<TKey extends string, TReturn>(
this: void,
table: Record<TKey, TReturn>,
key: TKey
): TReturn | undefined {
if (table) {
return table[key];
}
return undefined;
}
10 changes: 10 additions & 0 deletions src/lualib/OptionalFunctionCall.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
function __TS__OptionalFunctionCall<TArgs extends any[], TReturn>(
this: void,
f: (this: void, ...args: [...TArgs]) => TReturn,
...args: [...TArgs]
): TReturn | undefined {
if (f) {
return f(...args);
}
return undefined;
}
14 changes: 14 additions & 0 deletions src/lualib/OptionalMethodCall.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
function __TS__OptionalMethodCall<TArgs extends any[], TReturn>(
this: void,
table: Record<string, (...args: [...TArgs]) => TReturn>,
methodName: string,
...args: [...TArgs]
): TReturn | undefined {
if (table) {
const method = table[methodName];
if (method) {
return method.call(table, ...args);
}
}
return undefined;
}
2 changes: 0 additions & 2 deletions src/transformation/utils/diagnostics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,5 +143,3 @@ export const annotationDeprecated = createWarningDiagnosticFactory(
`'@${kind}' is deprecated and will be removed in a future update. Please update your code before upgrading to the next release, otherwise your project will no longer compile. ` +
`See https://typescripttolua.github.io/docs/advanced/compiler-annotations#${kind.toLowerCase()} for more information.`
);

export const optionalChainingNotSupported = createErrorDiagnosticFactory("Optional chaining is not supported yet.");
25 changes: 20 additions & 5 deletions src/transformation/visitors/access.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import * as lua from "../../LuaAST";
import { transformBuiltinPropertyAccessExpression } from "../builtins";
import { FunctionVisitor, TransformationContext } from "../context";
import { AnnotationKind, getTypeAnnotations } from "../utils/annotations";
import { annotationRemoved, invalidMultiReturnAccess, optionalChainingNotSupported } from "../utils/diagnostics";
import { annotationRemoved, invalidMultiReturnAccess } from "../utils/diagnostics";
import { addToNumericExpression } from "../utils/lua-ast";
import { LuaLibFeature, transformLuaLibFunction } from "../utils/lualib";
import { isArrayType, isNumberType, isStringType } from "../utils/typescript";
Expand Down Expand Up @@ -53,6 +53,10 @@ export const transformElementAccessExpression: FunctionVisitor<ts.ElementAccessE
return selectCall;
}

if (ts.isOptionalChain(node)) {
return transformLuaLibFunction(context, LuaLibFeature.OptionalChainAccess, node, table, accessExpression);
}

return lua.createTableIndexExpression(table, accessExpression, node);
};

Expand All @@ -66,10 +70,6 @@ export const transformPropertyAccessExpression: FunctionVisitor<ts.PropertyAcces
context.diagnostics.push(annotationRemoved(node, AnnotationKind.LuaTable));
}

if (ts.isOptionalChain(node)) {
context.diagnostics.push(optionalChainingNotSupported(node));
}

const constEnumValue = tryGetConstEnumValue(context, node);
if (constEnumValue) {
return constEnumValue;
Expand Down Expand Up @@ -98,10 +98,25 @@ export const transformPropertyAccessExpression: FunctionVisitor<ts.PropertyAcces
}
}

if (ts.isOptionalChain(node)) {
// Only handle full optional chains separately, not partial ones
return transformOptionalChain(context, node);
}

const callPath = context.transformExpression(node.expression);
return lua.createTableIndexExpression(callPath, lua.createStringLiteral(property), node);
};

function transformOptionalChain(
context: TransformationContext,
node: ts.OptionalChain & ts.PropertyAccessExpression
): lua.CallExpression {
const left = context.transformExpression(node.expression);
const right = lua.createStringLiteral(node.name.text, node.name);

return transformLuaLibFunction(context, LuaLibFeature.OptionalChainAccess, node, left, right);
}

export const transformQualifiedName: FunctionVisitor<ts.QualifiedName> = (node, context) => {
const right = lua.createStringLiteral(node.right.text, node.right);
const left = context.transformExpression(node.left);
Expand Down
17 changes: 7 additions & 10 deletions src/transformation/visitors/binary-expression/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ export function transformBinaryOperation(

if (operator === ts.SyntaxKind.QuestionQuestionToken) {
assert(ts.isBinaryExpression(node));
return transformNullishCoalescingExpression(context, node);
return transformNullishCoalescingExpression(context, node, left, right);
}

let luaOperator = simpleOperatorsToLua[operator];
Expand Down Expand Up @@ -162,7 +162,9 @@ export function transformBinaryExpressionStatement(

function transformNullishCoalescingExpression(
context: TransformationContext,
node: ts.BinaryExpression
node: ts.BinaryExpression,
transformedLeft: lua.Expression,
transformedRight: lua.Expression
): lua.Expression {
const lhsType = context.checker.getTypeAtLocation(node.left);

Expand All @@ -181,20 +183,15 @@ function transformNullishCoalescingExpression(
// if ____ == nil then return rhs else return ____ end
const ifStatement = lua.createIfStatement(
nilComparison,
lua.createBlock([lua.createReturnStatement([context.transformExpression(node.right)])]),
lua.createBlock([lua.createReturnStatement([transformedRight])]),
lua.createBlock([lua.createReturnStatement([lua.cloneIdentifier(lhsIdentifier)])])
);
// (function(lhs') if lhs' == nil then return rhs else return lhs' end)(lhs)
return lua.createCallExpression(lua.createFunctionExpression(lua.createBlock([ifStatement]), [lhsIdentifier]), [
context.transformExpression(node.left),
transformedLeft,
]);
} else {
// lhs or rhs
return lua.createBinaryExpression(
context.transformExpression(node.left),
context.transformExpression(node.right),
lua.SyntaxKind.OrOperator,
node
);
return lua.createBinaryExpression(transformedLeft, transformedRight, lua.SyntaxKind.OrOperator, node);
}
}
95 changes: 76 additions & 19 deletions src/transformation/visitors/call.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,18 +148,29 @@ export function transformContextualCallExpression(
node: ts.CallExpression | ts.TaggedTemplateExpression,
args: ts.Expression[] | ts.NodeArray<ts.Expression>,
signature?: ts.Signature
): lua.Expression {
): lua.CallExpression | lua.MethodCallExpression {
const left = ts.isCallExpression(node) ? node.expression : node.tag;
if (ts.isPropertyAccessExpression(left) && ts.isIdentifier(left.name) && isValidLuaIdentifier(left.name.text)) {
// table:name()
const table = context.transformExpression(left.expression);

return lua.createMethodCallExpression(
table,
lua.createIdentifier(left.name.text, left.name),
transformArguments(context, args, signature),
node
);
if (ts.isOptionalChain(node)) {
return transformLuaLibFunction(
context,
LuaLibFeature.OptionalMethodCall,
node,
table,
lua.createStringLiteral(left.name.text, left.name),
...transformArguments(context, args, signature)
);
} else {
return lua.createMethodCallExpression(
table,
lua.createIdentifier(left.name.text, left.name),
transformArguments(context, args, signature),
node
);
}
} else if (ts.isElementAccessExpression(left) || ts.isPropertyAccessExpression(left)) {
if (isExpressionWithEvaluationEffect(left.expression)) {
return transformToImmediatelyInvokedFunctionExpression(
Expand All @@ -183,7 +194,10 @@ export function transformContextualCallExpression(
}
}

function transformPropertyCall(context: TransformationContext, node: PropertyCallExpression): lua.Expression {
function transformPropertyCall(
context: TransformationContext,
node: PropertyCallExpression
): lua.CallExpression | lua.MethodCallExpression {
const signature = context.checker.getResolvedSignature(node);

if (node.expression.expression.kind === ts.SyntaxKind.SuperKeyword) {
Expand All @@ -197,17 +211,22 @@ function transformPropertyCall(context: TransformationContext, node: PropertyCal
// table:name()
return transformContextualCallExpression(context, node, node.arguments, signature);
} else {
const table = context.transformExpression(node.expression.expression);

// table.name()
const name = node.expression.name.text;
const callPath = lua.createTableIndexExpression(table, lua.createStringLiteral(name), node.expression);
const callPath = context.transformExpression(node.expression);
const parameters = transformArguments(context, node.arguments, signature);
return lua.createCallExpression(callPath, parameters, node);

if (ts.isOptionalChain(node)) {
return transformLuaLibFunction(context, LuaLibFeature.OptionalFunctionCall, node, callPath, ...parameters);
} else {
return lua.createCallExpression(callPath, parameters, node);
}
}
}

function transformElementCall(context: TransformationContext, node: ts.CallExpression): lua.Expression {
function transformElementCall(
context: TransformationContext,
node: ts.CallExpression
): lua.CallExpression | lua.MethodCallExpression {
const signature = context.checker.getResolvedSignature(node);
const signatureDeclaration = signature?.getDeclaration();
if (!signatureDeclaration || getDeclarationContextType(context, signatureDeclaration) !== ContextType.Void) {
Expand All @@ -229,11 +248,12 @@ export const transformCallExpression: FunctionVisitor<ts.CallExpression> = (node
const returnValueIsUsed = node.parent && !ts.isExpressionStatement(node.parent);
const wrapTupleReturn =
isTupleReturn && !isTupleReturnForward && !isInDestructingAssignment(node) && !isInSpread && returnValueIsUsed;
const wrapResult = wrapTupleReturn || shouldMultiReturnCallBeWrapped(context, node);
const wrapResultInTable = wrapTupleReturn || shouldMultiReturnCallBeWrapped(context, node);
const wrapResultInOptional = ts.isOptionalChain(node);

const builtinResult = transformBuiltinCallExpression(context, node);
if (builtinResult) {
return wrapResult ? wrapInTable(builtinResult) : builtinResult;
return wrapResultInTable ? wrapInTable(builtinResult) : builtinResult;
}

if (isOperatorMapping(context, node)) {
Expand Down Expand Up @@ -274,12 +294,13 @@ export const transformCallExpression: FunctionVisitor<ts.CallExpression> = (node
}

const result = transformPropertyCall(context, node as PropertyCallExpression);
return wrapResult ? wrapInTable(result) : result;
// transformPropertyCall already wraps optional so no need to do so here
return wrapResultInTable ? wrapInTable(result) : result;
}

if (ts.isElementAccessExpression(node.expression)) {
const result = transformElementCall(context, node);
return wrapResult ? wrapInTable(result) : result;
return wrapIfRequired(context, wrapResultInTable, wrapResultInOptional, result, node);
}

const signature = context.checker.getResolvedSignature(node);
Expand Down Expand Up @@ -309,5 +330,41 @@ export const transformCallExpression: FunctionVisitor<ts.CallExpression> = (node
}

const callExpression = lua.createCallExpression(callPath, parameters, node);
return wrapResult ? wrapInTable(callExpression) : callExpression;
return wrapIfRequired(context, wrapResultInTable, wrapResultInOptional, callExpression, node);
};

function wrapIfRequired(
context: TransformationContext,
shouldWrapInTable: boolean,
shouldWrapOptional: boolean,
call: lua.CallExpression | lua.MethodCallExpression,
node: ts.Node
): lua.Expression {
const wrappedOptional = shouldWrapOptional ? wrapOptionalCall(context, call, node) : call;
return shouldWrapInTable ? wrapInTable(wrappedOptional) : wrappedOptional;
}

function wrapOptionalCall(
context: TransformationContext,
call: lua.CallExpression | lua.MethodCallExpression,
node: ts.Node
): lua.CallExpression {
if (lua.isMethodCallExpression(call)) {
return transformLuaLibFunction(
context,
LuaLibFeature.OptionalMethodCall,
node,
call.prefixExpression,
lua.createStringLiteral(call.name.text),
...call.params
);
} else {
return transformLuaLibFunction(
context,
LuaLibFeature.OptionalFunctionCall,
node,
call.expression,
...call.params
);
}
}
1 change: 0 additions & 1 deletion test/transpile/module-resolution.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -288,7 +288,6 @@ describe("module resolution with tsx", () => {
test("project with tsx files", () => {
util.testProject(path.join(projectPath, "tsconfig.json"))
.setMainFileName(path.join(projectPath, "main.tsx"))
.debug()
.expectToEqual({
result: "hello from other.tsx",
indexResult: "hello from dir/index.tsx",
Expand Down
Loading