X Tutup
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
e0ca0a4
passing nil instead of _G as context for global functions when in ES …
tomblind Oct 25, 2018
dcb71a2
fixed logic for determining strict mode
tomblind Oct 25, 2018
a24d775
replaced hack-around when passing nil as a function context with a nu…
tomblind Oct 25, 2018
f9a7eb3
testing viability of wrapping context/no-context calls on assignment
tomblind Oct 26, 2018
90b7eb9
working on more function assignment situations
tomblind Oct 26, 2018
10be963
fixed getting constructor signature and refactored things a bit
tomblind Oct 29, 2018
fa5dd9b
checking resolved signature when comparing function types passed as a…
tomblind Oct 29, 2018
a24caa2
working on assignment checks for methods vs functions
tomblind Nov 19, 2018
7fb2cad
handling context in calls and decls
tomblind Nov 19, 2018
8c75ff0
refactoring and handling tuple destructuring
tomblind Nov 19, 2018
fec41f0
generalized tuple assignment checking
tomblind Nov 19, 2018
422e676
overloads with function and method signatures default to functions now
tomblind Nov 21, 2018
7731342
preventing non-methods from being passed to bind/call/apply
tomblind Nov 21, 2018
eca6cc2
removed uneccessary helpers
tomblind Nov 21, 2018
8314a1e
using proper exceptions for function conversion errors
tomblind Nov 21, 2018
abde08f
removed context arg from custom constructors and added check for assi…
tomblind Nov 22, 2018
07887a5
updated tests
tomblind Nov 22, 2018
4ac27f5
Merge remote-tracking branch 'upstream/new-functions' into new-functions
tomblind Nov 22, 2018
5b10da7
Merge branch 'new-functions' into new-functions-assign-experiment
tomblind Nov 22, 2018
7170080
removing leftover NoContext decorators
tomblind Nov 23, 2018
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
1 change: 0 additions & 1 deletion src/Decorator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,4 @@ export enum DecoratorKind {
Phantom = "Phantom",
TupleReturn = "TupleReturn",
NoClassOr = "NoClassOr",
NoContext = "NoContext",
}
6 changes: 6 additions & 0 deletions src/Errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,4 +68,10 @@ export class TSTLErrors {

public static UnsupportedObjectLiteralElement = (elementKind: ts.SyntaxKind, node: ts.Node) =>
new TranspileError(`Unsupported object literal element: ${elementKind}.`, node)

public static UnsupportedFunctionConversion = (node: ts.Node) =>
new TranspileError(`Unsupported conversion from method to function.`, node)

public static UnsupportedMethodConversion = (node: ts.Node) =>
new TranspileError(`Unsupported conversion from function to method.`, node)
}
34 changes: 34 additions & 0 deletions src/TSHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -248,4 +248,38 @@ export class TSHelper {
}
return [false, null, null];
}

public static isDeclarationWithContext(sigDecl: ts.SignatureDeclaration, checker: ts.TypeChecker): boolean {
const thisArg = sigDecl.parameters.find(p => ts.isIdentifier(p.name)
&& p.name.originalKeywordKind === ts.SyntaxKind.ThisKeyword);
if (thisArg) {
// Explicit 'this'
return !thisArg.type || thisArg.type.kind !== ts.SyntaxKind.VoidKeyword;
}
if ((ts.isMethodDeclaration(sigDecl) || ts.isMethodSignature(sigDecl))
&& !(ts.getCombinedModifierFlags(sigDecl) & ts.ModifierFlags.Static)) {
// Non-static method
return true;
}
if ((ts.isPropertySignature(sigDecl.parent) || ts.isPropertyDeclaration(sigDecl.parent))
&& !(ts.getCombinedModifierFlags(sigDecl.parent) & ts.ModifierFlags.Static)) {
// Non-static lambda property
return true;
}
if (ts.isBinaryExpression(sigDecl.parent)
&& this.isFunctionWithContext(checker.getTypeAtLocation(sigDecl.parent.left), checker)) {
// Function expression: check type being assigned to
return true;
}
return false;
}

public static isFunctionWithContext(type: ts.Type, checker: ts.TypeChecker): boolean {
const sigs = checker.getSignaturesOfType(type, ts.SignatureKind.Call);
if (sigs.length === 0) {
return false;
}
const sigDecls = sigs.map(s => s.getDeclaration());
return sigDecls.every(s => this.isDeclarationWithContext(s, checker));
}
}
114 changes: 85 additions & 29 deletions src/Transpiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -942,8 +942,12 @@ export abstract class LuaTranspiler {
return this.transpileSetAccessor(node.left as ts.PropertyAccessExpression, rhs);
}

// Validate assignment
const rightType = this.checker.getTypeAtLocation(node.right);
const leftType = this.checker.getTypeAtLocation(node.left);
this.validateAssignment(node.right, rightType, leftType);

if (ts.isArrayLiteralExpression(node.left)) {
// Destructing assignment
const vars = node.left.elements.map(e => this.transpileExpression(e)).join(",");
const vals = tsHelper.isTupleReturnCall(node.right, this.checker)
? rhs : this.transpileDestructingAssignmentValue(node.right);
Expand Down Expand Up @@ -1144,7 +1148,8 @@ export abstract class LuaTranspiler {

public transpileNewExpression(node: ts.NewExpression): string {
const name = this.transpileExpression(node.expression);
let params = node.arguments ? this.transpileArguments(node.arguments, ts.createTrue()) : "true";
const sig = this.checker.getResolvedSignature(node);
const params = node.arguments ? this.transpileArguments(node.arguments, sig, ts.createTrue()) : "true";
const type = this.checker.getTypeAtLocation(node);
const classDecorators = tsHelper.getCustomDecorators(type, this.checker);

Expand All @@ -1159,15 +1164,7 @@ export abstract class LuaTranspiler {
if (!customDecorator.args[0]) {
throw TSTLErrors.InvalidDecoratorArgumentNumber("!CustomConstructor", 0, 1, node);
}
if (!ts.isPropertyAccessExpression(node.expression)
&& !ts.isElementAccessExpression(node.expression)
&& !tsHelper.getCustomDecorators(type, this.checker).has(DecoratorKind.NoContext)) {
const context = this.isStrict ? ts.createNull() : ts.createIdentifier("_G");
params = this.transpileArguments(node.arguments, context);
} else {
params = this.transpileArguments(node.arguments);
}
return `${customDecorator.args[0]}(${params})`;
return `${customDecorator.args[0]}(${this.transpileArguments(node.arguments)})`;
}

return `${name}.new(${params})`;
Expand All @@ -1190,22 +1187,25 @@ export abstract class LuaTranspiler {
? `({ ${result} })` : result;
}

const sig = this.checker.getResolvedSignature(node);

// Handle super calls properly
if (node.expression.kind === ts.SyntaxKind.SuperKeyword) {
params = this.transpileArguments(node.arguments, ts.createNode(ts.SyntaxKind.ThisKeyword) as ts.Expression);
params = this.transpileArguments(node.arguments, sig,
ts.createNode(ts.SyntaxKind.ThisKeyword) as ts.Expression);
const className = this.classStack[this.classStack.length - 1];
return `${className}.__base.constructor(${params})`;
}

callPath = this.transpileExpression(node.expression);
const type = this.checker.getTypeAtLocation(node.expression);
if (!ts.isPropertyAccessExpression(node.expression)
&& !ts.isElementAccessExpression(node.expression)
&& !tsHelper.getCustomDecorators(type, this.checker).has(DecoratorKind.NoContext)) {
callPath = this.transpileExpression(node.expression);
if (tsHelper.isFunctionWithContext(type, this.checker)
&& !ts.isPropertyAccessExpression(node.expression)
&& !ts.isElementAccessExpression(node.expression)) {
const context = this.isStrict ? ts.createNull() : ts.createIdentifier("_G");
params = this.transpileArguments(node.arguments, context);
params = this.transpileArguments(node.arguments, sig, context);
} else {
params = this.transpileArguments(node.arguments);
params = this.transpileArguments(node.arguments, sig);
}
return isTupleReturn && !isTupleReturnForward && !isInDestructingAssignment && returnValueIsUsed
? `({ ${callPath}(${params}) })` : `${callPath}(${params})`;
Expand Down Expand Up @@ -1248,10 +1248,13 @@ export abstract class LuaTranspiler {
return this.transpileFunctionCallExpression(node);
}

const sig = this.checker.getResolvedSignature(node);

// Get the type of the function
if (node.expression.expression.kind === ts.SyntaxKind.SuperKeyword) {
// Super calls take the format of super.call(self,...)
params = this.transpileArguments(node.arguments, ts.createNode(ts.SyntaxKind.ThisKeyword) as ts.Expression);
params = this.transpileArguments(node.arguments, sig,
ts.createNode(ts.SyntaxKind.ThisKeyword) as ts.Expression);
return `${this.transpileExpression(node.expression)}(${params})`;
} else {
// Replace last . with : here
Expand All @@ -1260,13 +1263,13 @@ export abstract class LuaTranspiler {
return `tostring(${this.transpileExpression(node.expression.expression)})`;
} else if (name === "hasOwnProperty") {
const expr = this.transpileExpression(node.expression.expression);
params = this.transpileArguments(node.arguments);
params = this.transpileArguments(node.arguments, sig);
return `(rawget(${expr}, ${params} )~=nil)`;
} else {
const type = this.checker.getTypeAtLocation(node.expression);
const op = tsHelper.getCustomDecorators(type, this.checker).has(DecoratorKind.NoContext) ? "." : ":";
const op = tsHelper.isFunctionWithContext(type, this.checker) ? ":" : ".";
callPath = `${this.transpileExpression(node.expression.expression)}${op}${name}`;
params = this.transpileArguments(node.arguments);
params = this.transpileArguments(node.arguments, sig);
return `${callPath}(${params})`;
}
}
Expand Down Expand Up @@ -1387,6 +1390,10 @@ export abstract class LuaTranspiler {

public transpileFunctionCallExpression(node: ts.CallExpression): string {
const expression = node.expression as ts.PropertyAccessExpression;
const callerType = this.checker.getTypeAtLocation(expression.expression);
if (!tsHelper.isFunctionWithContext(callerType, this.checker)) {
throw TSTLErrors.UnsupportedMethodConversion(node);
}
const params = this.transpileArguments(node.arguments);
const caller = this.transpileExpression(expression.expression);
const expressionName = this.transpileIdentifier(expression.name);
Expand All @@ -1402,17 +1409,51 @@ export abstract class LuaTranspiler {
}
}

public transpileArguments(params: ts.NodeArray<ts.Expression>, context?: ts.Expression): string {
public validateAssignment(node: ts.Node, fromType: ts.Type, toType: ts.Type): void {
if ((toType.flags & ts.TypeFlags.Any) !== 0) {
// Assigning to un-typed variable
return;
} else if ((fromType as ts.TypeReference).typeArguments && (toType as ts.TypeReference).typeArguments) {
// Recurse into tuples/arrays
(toType as ts.TypeReference).typeArguments.forEach((t, i) => {
this.validateAssignment(node, (fromType as ts.TypeReference).typeArguments[i], t);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤔 Would it be possible toType.typeArguments is shorter/longer than fromType.typeArguments?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's definitely possible for fromType to have more than toType (destructuring, but not catching everything in the tuple), but I haven't come up with a situation where toType could have more.

});
} else {
// Check function assignments
const fromHasContext = tsHelper.isFunctionWithContext(fromType, this.checker);
const toHasContext = tsHelper.isFunctionWithContext(toType, this.checker);
if (fromHasContext !== toHasContext) {
if (fromHasContext) {
throw TSTLErrors.UnsupportedFunctionConversion(node);
} else {
throw TSTLErrors.UnsupportedMethodConversion(node);
}
}
}
}

public transpileArguments(params: ts.NodeArray<ts.Expression>, sig?: ts.Signature,
context?: ts.Expression): string {
const parameters: string[] = [];

// Add context as first param if present
if (context) {
parameters.push(this.transpileExpression(context));
}

params.forEach(param => {
parameters.push(this.transpileExpression(param));
});
if (sig && sig.parameters.length >= params.length) {
for (let i = 0; i < params.length; ++i) {
const param = params[i];
const paramType = this.checker.getTypeAtLocation(param);
const sigType = this.checker.getTypeAtLocation(sig.parameters[i].valueDeclaration);
this.validateAssignment(param, paramType, sigType);
parameters.push(this.transpileExpression(param));
}
} else {
params.forEach(param => {
parameters.push(this.transpileExpression(param));
});
}

return parameters.join(",");
}
Expand Down Expand Up @@ -1592,6 +1633,13 @@ export abstract class LuaTranspiler {
}

public transpileVariableDeclaration(node: ts.VariableDeclaration): string {
if (node.initializer) {
// Validate assignment
const initializerType = this.checker.getTypeAtLocation(node.initializer);
const varType = this.checker.getTypeFromTypeNode(node.type);
this.validateAssignment(node.initializer, initializerType, varType);
}

if (ts.isIdentifier(node.name)) {
// Find variable identifier
const identifierName = this.transpileIdentifier(node.name);
Expand Down Expand Up @@ -1635,7 +1683,7 @@ export abstract class LuaTranspiler {
const methodName = this.transpileIdentifier(node.name);

const type = this.checker.getTypeAtLocation(node);
const context = tsHelper.getCustomDecorators(type, this.checker).has(DecoratorKind.NoContext) ? null : "self";
const context = tsHelper.isFunctionWithContext(type, this.checker) ? "self" : null;
const [paramNames, spreadIdentifier] = this.transpileParameters(node.parameters, context);

let prefix = this.accessPrefix(node);
Expand Down Expand Up @@ -1672,6 +1720,9 @@ export abstract class LuaTranspiler {

// Only push parameter name to paramName array if it isn't a spread parameter
for (const param of parameters) {
if (ts.isIdentifier(param.name) && param.name.originalKeywordKind === ts.SyntaxKind.ThisKeyword) {
continue;
}
const paramName = this.transpileIdentifier(param.name as ts.Identifier);

// This parameter is a spread parameter (...param)
Expand Down Expand Up @@ -1718,7 +1769,7 @@ export abstract class LuaTranspiler {
}

const type = this.checker.getTypeAtLocation(node);
const context = tsHelper.getCustomDecorators(type, this.checker).has(DecoratorKind.NoContext) ? null : "self";
const context = tsHelper.isFunctionWithContext(type, this.checker) ? "self" : null;
const [paramNames, spreadIdentifier] = this.transpileParameters(node.parameters, context);

// Build function header
Expand Down Expand Up @@ -1918,6 +1969,9 @@ export abstract class LuaTranspiler {

public transpileConstructor(node: ts.ConstructorDeclaration,
className: string): string {
// Don't transpile methods without body (overload declarations)
if (!node.body) { return ""; }

const extraInstanceFields = [];

const parameters = ["self"];
Expand Down Expand Up @@ -1987,8 +2041,10 @@ export abstract class LuaTranspiler {
}

public transpileFunctionExpression(node: ts.FunctionLikeDeclaration, context: string | null): string {
const type = this.checker.getTypeAtLocation(node);
const hasContext = tsHelper.isFunctionWithContext(type, this.checker);
// Build parameter string
const [paramNames, spreadIdentifier] = this.transpileParameters(node.parameters, context);
const [paramNames, spreadIdentifier] = this.transpileParameters(node.parameters, hasContext ? context : null);
let result = `function(${paramNames.join(",")})\n`;
this.pushIndent();
const body = ts.isBlock(node.body) ? node.body : ts.createBlock([ts.createReturn(node.body)]);
Expand Down
3 changes: 0 additions & 3 deletions src/lualib/ArrayConcat.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
/** !NoContext */
declare function pcall(func: () => any): any;
/** !NoContext */
declare function type(val: any): string;

/** !NoContext */
function __TS__ArrayConcat(arr1: any[], ...args: any[]): any[] {
const out: any[] = [];
for (const val of arr1) {
Expand Down
1 change: 0 additions & 1 deletion src/lualib/ArrayEvery.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
/** !NoContext */
function __TS__ArrayEvery<T>(arr: T[], callbackfn: (value: T, index?: number, array?: any[]) => boolean): boolean {
for (let i = 0; i < arr.length; i++) {
if (!callbackfn(arr[i], i, arr)) {
Expand Down
1 change: 0 additions & 1 deletion src/lualib/ArrayFilter.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
/** !NoContext */
function __TS__ArrayFilter<T>(arr: T[], callbackfn: (value: T, index?: number, array?: any[]) => boolean): T[] {
const result: T[] = [];
for (let i = 0; i < arr.length; i++) {
Expand Down
1 change: 0 additions & 1 deletion src/lualib/ArrayForEach.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
/** !NoContext */
function __TS__ArrayForEach<T>(arr: T[], callbackFn: (value: T, index?: number, array?: any[]) => any): void {
for (let i = 0; i < arr.length; i++) {
callbackFn(arr[i], i, arr);
Expand Down
1 change: 0 additions & 1 deletion src/lualib/ArrayIndexOf.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
/** !NoContext */
function __TS__ArrayIndexOf<T>(arr: T[], searchElement: T, fromIndex?: number): number {
const len = arr.length;
if (len === 0) {
Expand Down
1 change: 0 additions & 1 deletion src/lualib/ArrayMap.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
/** !NoContext */
function __TS__ArrayMap<T, U>(arr: T[], callbackfn: (value: T, index?: number, array?: T[]) => U): U[] {
const newArray: U[] = [];
for (let i = 0; i < arr.length; i++) {
Expand Down
1 change: 0 additions & 1 deletion src/lualib/ArrayPush.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
/** !NoContext */
function __TS__ArrayPush<T>(arr: T[], ...items: T[]): number {
for (const item of items) {
arr[arr.length] = item;
Expand Down
1 change: 0 additions & 1 deletion src/lualib/ArrayReverse.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
/** !NoContext */
function __TS__ArrayReverse(arr: any[]): any[] {
let i = 0;
let j = arr.length - 1;
Expand Down
2 changes: 0 additions & 2 deletions src/lualib/ArrayShift.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
declare namespace table {
/** !NoContext */
function remove<T>(arr: T[], idx: number): T;
}
/** !NoContext */
function __TS__ArrayShift<T>(arr: T[]): T {
return table.remove(arr, 1);
}
1 change: 0 additions & 1 deletion src/lualib/ArraySlice.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
// https://www.ecma-international.org/publications/files/ECMA-ST/Ecma-262.pdf 22.1.3.23
/** !NoContext */
function __TS__ArraySlice<T>(list: T[], first: number, last: number): T[] {
const len = list.length;

Expand Down
1 change: 0 additions & 1 deletion src/lualib/ArraySome.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
/** !NoContext */
function __TS__ArraySome<T>(arr: T[], callbackfn: (value: T, index?: number, array?: any[]) => boolean): boolean {
for (let i = 0; i < arr.length; i++) {
if (callbackfn(arr[i], i, arr)) {
Expand Down
2 changes: 0 additions & 2 deletions src/lualib/ArraySort.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
declare namespace table {
/** !NoContext */
function sort<T>(arr: T[], compareFn?: (a: T, b: T) => number): void;
}
/** !NoContext */
function __TS__ArraySort<T>(arr: T[], compareFn?: (a: T, b: T) => number): T[] {
table.sort(arr, compareFn);
return arr;
Expand Down
1 change: 0 additions & 1 deletion src/lualib/ArraySplice.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
/** !NoContext */
function __TS__ArraySplice<T>(list: T[], start: number, deleteCount: number, ...items: T[]): T[] {

const len = list.length;
Expand Down
2 changes: 0 additions & 2 deletions src/lualib/ArrayUnshift.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
declare namespace table {
/** !NoContext */
function insert<T>(arr: T[], idx: number, val: T): void;
}
/** !NoContext */
function __TS__ArrayUnshift<T>(arr: T[], ...items: T[]): number {
for (let i = items.length - 1; i >= 0; --i) {
table.insert(arr, 1, items[i]);
Expand Down
4 changes: 0 additions & 4 deletions src/lualib/FunctionApply.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,11 @@
/** !NoContext */
declare function unpack<T>(list: T[], i?: number, j?: number): T[];

declare namespace table {
/** !NoContext */
export function unpack<T>(list: T[], i?: number, j?: number): T[];
}

/** !NoContext */
type ApplyFn = (...argArray: any[]) => any;

/** !NoContext */
function __TS__FunctionApply(fn: ApplyFn, thisArg: any, argsArray?: any[]): any {
if (argsArray) {
return fn(thisArg, (unpack || table.unpack)(argsArray));
Expand Down
Loading
X Tutup