X Tutup
Skip to content

Commit 281ab54

Browse files
tomblindPerryvw
authored andcommitted
New functions - mixed context overloads (#291)
* passing nil instead of _G as context for global functions when in ES strict mode * fixed logic for determining strict mode * replaced hack-around when passing nil as a function context with a null keyword * testing viability of wrapping context/no-context calls on assignment * working on more function assignment situations * fixed getting constructor signature and refactored things a bit * checking resolved signature when comparing function types passed as arguments * working on assignment checks for methods vs functions * handling context in calls and decls * refactoring and handling tuple destructuring * generalized tuple assignment checking * overloads with function and method signatures default to functions now * preventing non-methods from being passed to bind/call/apply * removed uneccessary helpers * using proper exceptions for function conversion errors * removed context arg from custom constructors and added check for assigning to untyped vars * updated tests * removing leftover NoContext decorators * recursing into interfaces during assignment validation * fixes for issues with overloads using different context types * less-lazy variable naming and improved error message
1 parent 441a2e8 commit 281ab54

File tree

3 files changed

+73
-41
lines changed

3 files changed

+73
-41
lines changed

src/Errors.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,4 +84,16 @@ export class TSTLErrors {
8484
return new TranspileError(`Unsupported conversion from function to method.`, node);
8585
}
8686
}
87+
88+
public static UnsupportedOverloadAssignment = (node: ts.Node, name?: string) => {
89+
if (name) {
90+
return new TranspileError(`Unsupported assignment of mixed function/method overload to "${name}". `
91+
+ `Overloads should either be all functions or all methods, but not both.`,
92+
node);
93+
} else {
94+
return new TranspileError(`Unsupported assignment of mixed function/method overload. `
95+
+ `Overloads should either be all functions or all methods, but not both.`,
96+
node);
97+
}
98+
}
8799
}

src/TSHelper.ts

Lines changed: 35 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
11
import * as ts from "typescript";
22
import { Decorator, DecoratorKind } from "./Decorator";
33

4+
export enum ContextType {
5+
None,
6+
Void,
7+
NonVoid,
8+
Mixed,
9+
}
10+
411
export class TSHelper {
512

613
// Reverse lookup of enum key by value
@@ -249,37 +256,45 @@ export class TSHelper {
249256
return [false, null, null];
250257
}
251258

252-
public static isDeclarationWithContext(sigDecl: ts.SignatureDeclaration, checker: ts.TypeChecker): boolean {
253-
const thisArg = sigDecl.parameters.find(p => ts.isIdentifier(p.name)
254-
&& p.name.originalKeywordKind === ts.SyntaxKind.ThisKeyword);
259+
public static getDeclarationContextType(signatureDeclaration: ts.SignatureDeclaration,
260+
checker: ts.TypeChecker): ContextType {
261+
const thisArg = signatureDeclaration.parameters.find(
262+
param => ts.isIdentifier(param.name) && param.name.originalKeywordKind === ts.SyntaxKind.ThisKeyword);
255263
if (thisArg) {
256264
// Explicit 'this'
257-
return !thisArg.type || thisArg.type.kind !== ts.SyntaxKind.VoidKeyword;
265+
return thisArg.type && thisArg.type.kind === ts.SyntaxKind.VoidKeyword
266+
? ContextType.Void : ContextType.NonVoid;
258267
}
259-
if ((ts.isMethodDeclaration(sigDecl) || ts.isMethodSignature(sigDecl))
260-
&& !(ts.getCombinedModifierFlags(sigDecl) & ts.ModifierFlags.Static)) {
268+
if ((ts.isMethodDeclaration(signatureDeclaration) || ts.isMethodSignature(signatureDeclaration))
269+
&& !(ts.getCombinedModifierFlags(signatureDeclaration) & ts.ModifierFlags.Static)) {
261270
// Non-static method
262-
return true;
271+
return ContextType.NonVoid;
263272
}
264-
if ((ts.isPropertySignature(sigDecl.parent) || ts.isPropertyDeclaration(sigDecl.parent))
265-
&& !(ts.getCombinedModifierFlags(sigDecl.parent) & ts.ModifierFlags.Static)) {
273+
if ((ts.isPropertySignature(signatureDeclaration.parent)
274+
|| ts.isPropertyDeclaration(signatureDeclaration.parent))
275+
&& !(ts.getCombinedModifierFlags(signatureDeclaration.parent) & ts.ModifierFlags.Static)) {
266276
// Non-static lambda property
267-
return true;
277+
return ContextType.NonVoid;
268278
}
269-
if (ts.isBinaryExpression(sigDecl.parent)
270-
&& this.isFunctionWithContext(checker.getTypeAtLocation(sigDecl.parent.left), checker)) {
279+
if (ts.isBinaryExpression(signatureDeclaration.parent)) {
271280
// Function expression: check type being assigned to
272-
return true;
281+
return this.getFunctionContextType(checker.getTypeAtLocation(signatureDeclaration.parent.left), checker);
273282
}
274-
return false;
283+
return ContextType.Void;
275284
}
276285

277-
public static isFunctionWithContext(type: ts.Type, checker: ts.TypeChecker): boolean {
278-
const sigs = checker.getSignaturesOfType(type, ts.SignatureKind.Call);
279-
if (sigs.length === 0) {
280-
return false;
286+
public static getFunctionContextType(type: ts.Type, checker: ts.TypeChecker): ContextType {
287+
const signatures = checker.getSignaturesOfType(type, ts.SignatureKind.Call);
288+
if (signatures.length === 0) {
289+
return ContextType.None;
290+
}
291+
const signatureDeclataions = signatures.map(sig => sig.getDeclaration());
292+
const context = this.getDeclarationContextType(signatureDeclataions[0], checker);
293+
for (let i = 1; i < signatureDeclataions.length; ++i) {
294+
if (this.getDeclarationContextType(signatureDeclataions[i], checker) !== context) {
295+
return ContextType.Mixed;
296+
}
281297
}
282-
const sigDecls = sigs.map(s => s.getDeclaration());
283-
return sigDecls.every(s => this.isDeclarationWithContext(s, checker));
298+
return context;
284299
}
285300
}

src/Transpiler.ts

Lines changed: 26 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import * as ts from "typescript";
55
import { CompilerOptions } from "./CompilerOptions";
66
import { DecoratorKind } from "./Decorator";
77
import { TSTLErrors } from "./Errors";
8-
import { TSHelper as tsHelper } from "./TSHelper";
8+
import { ContextType, TSHelper as tsHelper } from "./TSHelper";
99

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

1193-
const sig = this.checker.getResolvedSignature(node);
1193+
const signature = this.checker.getResolvedSignature(node);
11941194

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

1203-
const type = this.checker.getTypeAtLocation(node.expression);
12041203
callPath = this.transpileExpression(node.expression);
1205-
if (tsHelper.isFunctionWithContext(type, this.checker)
1204+
const signatureDeclaration = signature.getDeclaration();
1205+
if (signatureDeclaration
1206+
&& tsHelper.getDeclarationContextType(signatureDeclaration, this.checker) === ContextType.NonVoid
12061207
&& !ts.isPropertyAccessExpression(node.expression)
12071208
&& !ts.isElementAccessExpression(node.expression)) {
12081209
const context = this.isStrict ? ts.createNull() : ts.createIdentifier("_G");
1209-
params = this.transpileArguments(node.arguments, sig, context);
1210+
params = this.transpileArguments(node.arguments, signature, context);
12101211
} else {
1211-
params = this.transpileArguments(node.arguments, sig);
1212+
params = this.transpileArguments(node.arguments, signature);
12121213
}
12131214
return isTupleReturn && !isTupleReturnForward && !isInDestructingAssignment && returnValueIsUsed
12141215
? `({ ${callPath}(${params}) })` : `${callPath}(${params})`;
@@ -1251,12 +1252,12 @@ export abstract class LuaTranspiler {
12511252
return this.transpileFunctionCallExpression(node);
12521253
}
12531254

1254-
const sig = this.checker.getResolvedSignature(node);
1255+
const signature = this.checker.getResolvedSignature(node);
12551256

12561257
// Get the type of the function
12571258
if (node.expression.expression.kind === ts.SyntaxKind.SuperKeyword) {
12581259
// Super calls take the format of super.call(self,...)
1259-
params = this.transpileArguments(node.arguments, sig,
1260+
params = this.transpileArguments(node.arguments, signature,
12601261
ts.createNode(ts.SyntaxKind.ThisKeyword) as ts.Expression);
12611262
return `${this.transpileExpression(node.expression)}(${params})`;
12621263
} else {
@@ -1266,13 +1267,15 @@ export abstract class LuaTranspiler {
12661267
return `tostring(${this.transpileExpression(node.expression.expression)})`;
12671268
} else if (name === "hasOwnProperty") {
12681269
const expr = this.transpileExpression(node.expression.expression);
1269-
params = this.transpileArguments(node.arguments, sig);
1270+
params = this.transpileArguments(node.arguments, signature);
12701271
return `(rawget(${expr}, ${params} )~=nil)`;
12711272
} else {
1272-
const type = this.checker.getTypeAtLocation(node.expression);
1273-
const op = tsHelper.isFunctionWithContext(type, this.checker) ? ":" : ".";
1273+
const signatureDeclaration = signature.getDeclaration();
1274+
const op = !signatureDeclaration
1275+
|| tsHelper.getDeclarationContextType(signatureDeclaration, this.checker) !== ContextType.Void
1276+
? ":" : ".";
12741277
callPath = `${this.transpileExpression(node.expression.expression)}${op}${name}`;
1275-
params = this.transpileArguments(node.arguments, sig);
1278+
params = this.transpileArguments(node.arguments, signature);
12761279
return `${callPath}(${params})`;
12771280
}
12781281
}
@@ -1394,7 +1397,7 @@ export abstract class LuaTranspiler {
13941397
public transpileFunctionCallExpression(node: ts.CallExpression): string {
13951398
const expression = node.expression as ts.PropertyAccessExpression;
13961399
const callerType = this.checker.getTypeAtLocation(expression.expression);
1397-
if (!tsHelper.isFunctionWithContext(callerType, this.checker)) {
1400+
if (tsHelper.getFunctionContextType(callerType, this.checker) === ContextType.Void) {
13981401
throw TSTLErrors.UnsupportedMethodConversion(node);
13991402
}
14001403
const params = this.transpileArguments(node.arguments);
@@ -1435,10 +1438,12 @@ export abstract class LuaTranspiler {
14351438
fromTypeCache.add(toType);
14361439

14371440
// Check function assignments
1438-
const fromHasContext = tsHelper.isFunctionWithContext(fromType, this.checker);
1439-
const toHasContext = tsHelper.isFunctionWithContext(toType, this.checker);
1440-
if (fromHasContext !== toHasContext) {
1441-
if (fromHasContext) {
1441+
const fromContext = tsHelper.getFunctionContextType(fromType, this.checker);
1442+
const toContext = tsHelper.getFunctionContextType(toType, this.checker);
1443+
if (fromContext === ContextType.Mixed || toContext === ContextType.Mixed) {
1444+
throw TSTLErrors.UnsupportedOverloadAssignment(node, toName);
1445+
} else if (fromContext !== toContext) {
1446+
if (toContext === ContextType.Void) {
14421447
throw TSTLErrors.UnsupportedFunctionConversion(node, toName);
14431448
} else {
14441449
throw TSTLErrors.UnsupportedMethodConversion(node, toName);
@@ -1719,7 +1724,7 @@ export abstract class LuaTranspiler {
17191724
const methodName = this.transpileIdentifier(node.name);
17201725

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

17251730
let prefix = this.accessPrefix(node);
@@ -1805,7 +1810,7 @@ export abstract class LuaTranspiler {
18051810
}
18061811

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

18111816
// Build function header
@@ -2078,7 +2083,7 @@ export abstract class LuaTranspiler {
20782083

20792084
public transpileFunctionExpression(node: ts.FunctionLikeDeclaration, context: string | null): string {
20802085
const type = this.checker.getTypeAtLocation(node);
2081-
const hasContext = tsHelper.isFunctionWithContext(type, this.checker);
2086+
const hasContext = tsHelper.getFunctionContextType(type, this.checker) !== ContextType.Void;
20822087
// Build parameter string
20832088
const [paramNames, spreadIdentifier] = this.transpileParameters(node.parameters, hasContext ? context : null);
20842089
let result = `function(${paramNames.join(",")})\n`;

0 commit comments

Comments
 (0)
X Tutup