X Tutup
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 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
f90e2ec
Merge remote-tracking branch 'upstream/new-functions' into new-functions
tomblind Nov 24, 2018
3ac149e
recursing into interfaces during assignment validation
tomblind Nov 28, 2018
2913e93
fixes for issues with overloads using different context types
tomblind Nov 30, 2018
e088b7f
Merge remote-tracking branch 'upstream/new-functions' into new-functions
tomblind Nov 30, 2018
b79756e
less-lazy variable naming and improved error message
tomblind Dec 2, 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
12 changes: 12 additions & 0 deletions src/Errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,4 +84,16 @@ export class TSTLErrors {
return new TranspileError(`Unsupported conversion from function to method.`, node);
}
}

public static UnsupportedOverloadAssignment = (node: ts.Node, name?: string) => {
if (name) {
return new TranspileError(`Unsupported assignment of mixed function/method overload to "${name}". `
+ `Overloads should either be all functions or all methods, but not both.`,
node);
} else {
return new TranspileError(`Unsupported assignment of mixed function/method overload. `
+ `Overloads should either be all functions or all methods, but not both.`,
node);
}
}
}
55 changes: 35 additions & 20 deletions src/TSHelper.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
import * as ts from "typescript";
import { Decorator, DecoratorKind } from "./Decorator";

export enum ContextType {
None,
Void,
NonVoid,
Mixed,
}

export class TSHelper {

// Reverse lookup of enum key by value
Expand Down Expand Up @@ -249,37 +256,45 @@ 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);
public static getDeclarationContextType(signatureDeclaration: ts.SignatureDeclaration,
checker: ts.TypeChecker): ContextType {
const thisArg = signatureDeclaration.parameters.find(
param => ts.isIdentifier(param.name) && param.name.originalKeywordKind === ts.SyntaxKind.ThisKeyword);
if (thisArg) {
// Explicit 'this'
return !thisArg.type || thisArg.type.kind !== ts.SyntaxKind.VoidKeyword;
return thisArg.type && thisArg.type.kind === ts.SyntaxKind.VoidKeyword
? ContextType.Void : ContextType.NonVoid;
}
if ((ts.isMethodDeclaration(sigDecl) || ts.isMethodSignature(sigDecl))
&& !(ts.getCombinedModifierFlags(sigDecl) & ts.ModifierFlags.Static)) {
if ((ts.isMethodDeclaration(signatureDeclaration) || ts.isMethodSignature(signatureDeclaration))
&& !(ts.getCombinedModifierFlags(signatureDeclaration) & ts.ModifierFlags.Static)) {
// Non-static method
return true;
return ContextType.NonVoid;
}
if ((ts.isPropertySignature(sigDecl.parent) || ts.isPropertyDeclaration(sigDecl.parent))
&& !(ts.getCombinedModifierFlags(sigDecl.parent) & ts.ModifierFlags.Static)) {
if ((ts.isPropertySignature(signatureDeclaration.parent)
|| ts.isPropertyDeclaration(signatureDeclaration.parent))
&& !(ts.getCombinedModifierFlags(signatureDeclaration.parent) & ts.ModifierFlags.Static)) {
// Non-static lambda property
return true;
return ContextType.NonVoid;
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.

I thought lambda properties are currently transpiled without context, would that not mean void type here, or do we want to change this?

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.

lambda properties get a dummy context (versus stand-alone lambdas, which do not). This ensures they can be called the same as methods when obfuscated behind an interface.

}
if (ts.isBinaryExpression(sigDecl.parent)
&& this.isFunctionWithContext(checker.getTypeAtLocation(sigDecl.parent.left), checker)) {
if (ts.isBinaryExpression(signatureDeclaration.parent)) {
// Function expression: check type being assigned to
return true;
return this.getFunctionContextType(checker.getTypeAtLocation(signatureDeclaration.parent.left), checker);
}
return false;
return ContextType.Void;
}

public static isFunctionWithContext(type: ts.Type, checker: ts.TypeChecker): boolean {
const sigs = checker.getSignaturesOfType(type, ts.SignatureKind.Call);
if (sigs.length === 0) {
return false;
public static getFunctionContextType(type: ts.Type, checker: ts.TypeChecker): ContextType {
const signatures = checker.getSignaturesOfType(type, ts.SignatureKind.Call);
if (signatures.length === 0) {
return ContextType.None;
}
const signatureDeclataions = signatures.map(sig => sig.getDeclaration());
const context = this.getDeclarationContextType(signatureDeclataions[0], checker);
for (let i = 1; i < signatureDeclataions.length; ++i) {
if (this.getDeclarationContextType(signatureDeclataions[i], checker) !== context) {
return ContextType.Mixed;
}
}
const sigDecls = sigs.map(s => s.getDeclaration());
return sigDecls.every(s => this.isDeclarationWithContext(s, checker));
return context;
}
}
47 changes: 26 additions & 21 deletions src/Transpiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import * as ts from "typescript";
import { CompilerOptions } from "./CompilerOptions";
import { DecoratorKind } from "./Decorator";
import { TSTLErrors } from "./Errors";
import { TSHelper as tsHelper } from "./TSHelper";
import { ContextType, TSHelper as tsHelper } from "./TSHelper";

/* tslint:disable */
const packageJSON = require("../package.json");
Expand Down Expand Up @@ -1190,25 +1190,26 @@ export abstract class LuaTranspiler {
? `({ ${result} })` : result;
}

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

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

const type = this.checker.getTypeAtLocation(node.expression);
callPath = this.transpileExpression(node.expression);
if (tsHelper.isFunctionWithContext(type, this.checker)
const signatureDeclaration = signature.getDeclaration();
if (signatureDeclaration
&& tsHelper.getDeclarationContextType(signatureDeclaration, this.checker) === ContextType.NonVoid
&& !ts.isPropertyAccessExpression(node.expression)
&& !ts.isElementAccessExpression(node.expression)) {
const context = this.isStrict ? ts.createNull() : ts.createIdentifier("_G");
params = this.transpileArguments(node.arguments, sig, context);
params = this.transpileArguments(node.arguments, signature, context);
} else {
params = this.transpileArguments(node.arguments, sig);
params = this.transpileArguments(node.arguments, signature);
}
return isTupleReturn && !isTupleReturnForward && !isInDestructingAssignment && returnValueIsUsed
? `({ ${callPath}(${params}) })` : `${callPath}(${params})`;
Expand Down Expand Up @@ -1251,12 +1252,12 @@ export abstract class LuaTranspiler {
return this.transpileFunctionCallExpression(node);
}

const sig = this.checker.getResolvedSignature(node);
const signature = 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, sig,
params = this.transpileArguments(node.arguments, signature,
ts.createNode(ts.SyntaxKind.ThisKeyword) as ts.Expression);
return `${this.transpileExpression(node.expression)}(${params})`;
} else {
Expand All @@ -1266,13 +1267,15 @@ 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, sig);
params = this.transpileArguments(node.arguments, signature);
return `(rawget(${expr}, ${params} )~=nil)`;
} else {
const type = this.checker.getTypeAtLocation(node.expression);
const op = tsHelper.isFunctionWithContext(type, this.checker) ? ":" : ".";
const signatureDeclaration = signature.getDeclaration();
const op = !signatureDeclaration
|| tsHelper.getDeclarationContextType(signatureDeclaration, this.checker) !== ContextType.Void
? ":" : ".";
callPath = `${this.transpileExpression(node.expression.expression)}${op}${name}`;
params = this.transpileArguments(node.arguments, sig);
params = this.transpileArguments(node.arguments, signature);
return `${callPath}(${params})`;
}
}
Expand Down Expand Up @@ -1394,7 +1397,7 @@ 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)) {
if (tsHelper.getFunctionContextType(callerType, this.checker) === ContextType.Void) {
throw TSTLErrors.UnsupportedMethodConversion(node);
}
const params = this.transpileArguments(node.arguments);
Expand Down Expand Up @@ -1435,10 +1438,12 @@ export abstract class LuaTranspiler {
fromTypeCache.add(toType);

// Check function assignments
const fromHasContext = tsHelper.isFunctionWithContext(fromType, this.checker);
const toHasContext = tsHelper.isFunctionWithContext(toType, this.checker);
if (fromHasContext !== toHasContext) {
if (fromHasContext) {
const fromContext = tsHelper.getFunctionContextType(fromType, this.checker);
const toContext = tsHelper.getFunctionContextType(toType, this.checker);
if (fromContext === ContextType.Mixed || toContext === ContextType.Mixed) {
throw TSTLErrors.UnsupportedOverloadAssignment(node, toName);
} else if (fromContext !== toContext) {
if (toContext === ContextType.Void) {
throw TSTLErrors.UnsupportedFunctionConversion(node, toName);
} else {
throw TSTLErrors.UnsupportedMethodConversion(node, toName);
Expand Down Expand Up @@ -1719,7 +1724,7 @@ export abstract class LuaTranspiler {
const methodName = this.transpileIdentifier(node.name);

const type = this.checker.getTypeAtLocation(node);
const context = tsHelper.isFunctionWithContext(type, this.checker) ? "self" : null;
const context = tsHelper.getFunctionContextType(type, this.checker) !== ContextType.Void ? "self" : null;
const [paramNames, spreadIdentifier] = this.transpileParameters(node.parameters, context);

let prefix = this.accessPrefix(node);
Expand Down Expand Up @@ -1805,7 +1810,7 @@ export abstract class LuaTranspiler {
}

const type = this.checker.getTypeAtLocation(node);
const context = tsHelper.isFunctionWithContext(type, this.checker) ? "self" : null;
const context = tsHelper.getFunctionContextType(type, this.checker) !== ContextType.Void ? "self" : null;
const [paramNames, spreadIdentifier] = this.transpileParameters(node.parameters, context);

// Build function header
Expand Down Expand Up @@ -2078,7 +2083,7 @@ 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);
const hasContext = tsHelper.getFunctionContextType(type, this.checker) !== ContextType.Void;
// Build parameter string
const [paramNames, spreadIdentifier] = this.transpileParameters(node.parameters, hasContext ? context : null);
let result = `function(${paramNames.join(",")})\n`;
Expand Down
X Tutup