X Tutup
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 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
060f393
Merge remote-tracking branch 'upstream/new-functions' into new-functions
tomblind Dec 2, 2018
055060a
suite of tests for new functions and fixes for edge-cases found
tomblind Dec 11, 2018
7fda8ac
Merge remote-tracking branch 'upstream/new-functions' into new-functions
tomblind Dec 12, 2018
53f35e6
validating return values and hanlding inference of contexts when pass…
tomblind Dec 13, 2018
72d0edc
renamed getFunctionReturnType to getContainingFunctionReturnType
tomblind Dec 14, 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
6 changes: 4 additions & 2 deletions src/Errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,11 +84,13 @@ export class TSTLErrors {
public static UnsupportedMethodConversion = (node: ts.Node, name?: string) => {
if (name) {
return new TranspileError(`Unsupported conversion from function to method "${name}". `
+ `To fix, wrap the function in an arrow function.`,
+ `To fix, wrap the function in an arrow function or declare the function with`
+ ` an explicit 'this' parameter.`,
node);
} else {
return new TranspileError(`Unsupported conversion from function to method. `
+ `To fix, wrap the function in an arrow function.`,
+ `To fix, wrap the function in an arrow function or declare the function with`
+ ` an explicit 'this' parameter.`,
node);
}
}
Expand Down
35 changes: 30 additions & 5 deletions src/TSHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,15 @@ export class TSHelper {
}
}

public static getContainingFunctionReturnType(node: ts.Node, checker: ts.TypeChecker): ts.Type {
const declaration = this.findFirstNodeAbove(node, (n): n is ts.Node => ts.isFunctionLike(n));
if (declaration) {
const signature = checker.getSignatureFromDeclaration(declaration as ts.SignatureDeclaration);
return checker.getReturnTypeOfSignature(signature);
}
return null;
}

public static collectCustomDecorators(symbol: ts.Symbol, checker: ts.TypeChecker,
decMap: Map<DecoratorKind, Decorator>): void {
const comments = symbol.getDocumentationComment(checker);
Expand Down Expand Up @@ -269,11 +278,27 @@ export class TSHelper {
if ((ts.isFunctionExpression(signatureDeclaration) || ts.isArrowFunction(signatureDeclaration))
&& !this.getExplicitThisParameter(signatureDeclaration)) {
// Function expressions: get signatures of type being assigned to, unless 'this' was explicit
const declType = checker.getTypeAtLocation(signatureDeclaration.parent);
const declSignatures = declType.getCallSignatures();
if (declSignatures.length > 0) {
declSignatures.map(s => s.getDeclaration()).forEach(decl => signatureDeclarations.push(decl));
continue;
let declType: ts.Type;
if (ts.isCallExpression(signatureDeclaration.parent)) {
// Function expression being passed as argument to another function
const i = signatureDeclaration.parent.arguments.indexOf(signatureDeclaration);
if (i >= 0) {
const parentSignature = checker.getResolvedSignature(signatureDeclaration.parent);
const parentSignatureDeclaration = parentSignature.getDeclaration();
declType = checker.getTypeAtLocation(parentSignatureDeclaration.parameters[i]);
}
} else if (ts.isReturnStatement(signatureDeclaration.parent)) {
declType = this.getContainingFunctionReturnType(signatureDeclaration.parent, checker);
} else {
// Function expression being assigned
declType = checker.getTypeAtLocation(signatureDeclaration.parent);
}
if (declType) {
const declSignatures = declType.getCallSignatures();
if (declSignatures.length > 0) {
declSignatures.map(s => s.getDeclaration()).forEach(decl => signatureDeclarations.push(decl));
continue;
}
}
}
signatureDeclarations.push(signatureDeclaration);
Expand Down
5 changes: 5 additions & 0 deletions src/Transpiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -702,6 +702,11 @@ export abstract class LuaTranspiler {

public transpileReturn(node: ts.ReturnStatement): string {
if (node.expression) {
const returnType = tsHelper.getContainingFunctionReturnType(node, this.checker);
if (returnType) {
const expressionType = this.checker.getTypeAtLocation(node.expression);
this.validateAssignment(node, expressionType, returnType);
}
if (tsHelper.isInTupleReturnFunction(node, this.checker)) {
// Parent function is a TupleReturn function
if (ts.isArrayLiteralExpression(node.expression)) {
Expand Down
91 changes: 86 additions & 5 deletions test/unit/assignments.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,9 @@ export class AssignmentTests {
@TestCase("Foo.staticLambdaProp", "foo+staticLambdaProp")
@TestCase("foo.voidMethod", "foo+voidMethod")
@TestCase("foo.voidLambdaProp", "foo+voidLambdaProp")
@TestCase("s => s", "foo")
@TestCase("function(s) { return s; }", "foo")
@TestCase("function(this: void, s: string) { return s; }", "foo")
@Test("Valid function argument")
public validFunctionArgument(func: string, expectResult: string): void {
const code = `${AssignmentTests.funcAssignTestCode}
Expand All @@ -285,6 +288,25 @@ export class AssignmentTests {
Expect(result).toBe(expectResult);
}

@TestCase("func", "foo+func")
@TestCase("lambda", "foo+lambda")
@TestCase("Foo.staticMethod", "foo+staticMethod")
@TestCase("Foo.staticLambdaProp", "foo+staticLambdaProp")
@TestCase("foo.voidMethod", "foo+voidMethod")
@TestCase("foo.voidLambdaProp", "foo+voidLambdaProp")
@TestCase("s => s", "foo")
@TestCase("function(s) { return s; }", "foo")
@TestCase("function(this: void, s: string) { return s; }", "foo")
@Test("Valid function return")
public validFunctionReturn(func: string, expectResult: string): void {
const code = `${AssignmentTests.funcAssignTestCode}
function returnsFunc(): (s: string) => string { return ${func}; }
const fn = returnsFunc();
return fn("foo");`;
const result = util.transpileAndExecute(code);
Expect(result).toBe(expectResult);
}

@TestCase("foo.method", "foo.lambdaProp", "foo+lambdaProp")
@TestCase("foo.method", "s => s", "foo")
@TestCase("foo.method", "function(s) { return s; }", "foo")
Expand Down Expand Up @@ -358,17 +380,38 @@ export class AssignmentTests {
@TestCase("Foo.thisStaticLambdaProp", "foo+thisStaticLambdaProp")
@TestCase("thisFunc", "foo+thisFunc")
@TestCase("thisLambda", "foo+thisLambda")
@TestCase("s => s", "foo")
@TestCase("function(s) { return s; }", "foo")
@TestCase("function(this: Foo, s: string) { return s; }", "foo")
@Test("Valid method argument")
public validMethodArgument(func: string, expectResult: string): void {
const code = `${AssignmentTests.funcAssignTestCode}
const foo = new Foo();
function takesMethod(meth: (this: Foo, s: string) => s) { foo.method = meth; }
function takesMethod(meth: (this: Foo, s: string) => string) { foo.method = meth; }
takesMethod(${func});
return foo.method("foo");`;
const result = util.transpileAndExecute(code);
Expect(result).toBe(expectResult);
}

@TestCase("foo.method", "foo+method")
@TestCase("foo.lambdaProp", "foo+lambdaProp")
@TestCase("Foo.thisStaticMethod", "foo+thisStaticMethod")
@TestCase("Foo.thisStaticLambdaProp", "foo+thisStaticLambdaProp")
@TestCase("thisFunc", "foo+thisFunc")
@TestCase("thisLambda", "foo+thisLambda")
@TestCase("s => s", "foo")
@TestCase("function(s) { return s; }", "foo")
@TestCase("function(this: Foo, s: string) { return s; }", "foo")
@Test("Valid method return")
public validMethodReturn(func: string, expectResult: string): void {
const code = `${AssignmentTests.funcAssignTestCode}
function returnMethod(): (this: Foo, s: string) => string { return ${func}; }
foo.method = returnMethod();
return foo.method("foo");`;
const result = util.transpileAndExecute(code);
Expect(result).toBe(expectResult);
}

@TestCase("func", "foo.method")
@TestCase("func", "foo.lambdaProp")
@TestCase("func", "Foo.thisStaticMethod")
Expand Down Expand Up @@ -413,6 +456,7 @@ export class AssignmentTests {
@TestCase("Foo.thisStaticLambdaProp")
@TestCase("thisFunc")
@TestCase("thisLambda")
@TestCase("function(this: Foo, s: string) { return s; }")
@Test("Invalid function argument")
public invalidFunctionArgument(func: string): void {
const code = `${AssignmentTests.funcAssignTestCode}
Expand All @@ -423,6 +467,22 @@ export class AssignmentTests {
"Unsupported conversion from method to function \"fn\". To fix, wrap the method in an arrow function.");
}

@TestCase("foo.method")
@TestCase("foo.lambdaProp")
@TestCase("Foo.thisStaticMethod")
@TestCase("Foo.thisStaticLambdaProp")
@TestCase("thisFunc")
@TestCase("thisLambda")
@TestCase("function(this: Foo, s: string) { return s; }")
@Test("Invalid function return")
public invalidFunctionReturn(func: string): void {
const code = `${AssignmentTests.funcAssignTestCode}
function returnsFunc(): (s: string) => string { return ${func}; }`;
Expect(() => util.transpileString(code)).toThrowError(
TranspileError,
"Unsupported conversion from method to function. To fix, wrap the method in an arrow function.");
}

@TestCase("foo.method", "func")
@TestCase("foo.method", "lambda")
@TestCase("foo.method", "Foo.staticMethod")
Expand Down Expand Up @@ -470,7 +530,8 @@ export class AssignmentTests {
const code = `${AssignmentTests.funcAssignTestCode} ${func} = ${assignTo};`;
Expect(() => util.transpileString(code)).toThrowError(
TranspileError,
"Unsupported conversion from function to method. To fix, wrap the function in an arrow function.");
"Unsupported conversion from function to method. To fix, wrap the function in an arrow function or declare"
+ " the function with an explicit 'this' parameter.");
}

@TestCase("func")
Expand All @@ -479,14 +540,33 @@ export class AssignmentTests {
@TestCase("Foo.staticLambdaProp")
@TestCase("foo.voidMethod")
@TestCase("foo.voidLambdaProp")
@TestCase("function(this: void, s: string) { return s; }")
@Test("Invalid method argument")
public invalidMethodArgument(func: string): void {
const code = `${AssignmentTests.funcAssignTestCode}
declare function takesMethod(meth: (this: Foo, s: string) => s);
takesMethod(${func});`;
Expect(() => util.transpileString(code)).toThrowError(
TranspileError,
"Unsupported conversion from function to method \"meth\". To fix, wrap the function in an arrow function.");
"Unsupported conversion from function to method \"meth\". To fix, wrap the function in an arrow function "
+ "or declare the function with an explicit 'this' parameter.");
}

@TestCase("func")
@TestCase("lambda")
@TestCase("Foo.staticMethod")
@TestCase("Foo.staticLambdaProp")
@TestCase("foo.voidMethod")
@TestCase("foo.voidLambdaProp")
@TestCase("function(this: void, s: string) { return s; }")
@Test("Invalid method return")
public invalidMethodReturn(func: string): void {
const code = `${AssignmentTests.funcAssignTestCode}
function returnsMethod(): (this: Foo, s: string) => s { return ${func}; }`;
Expect(() => util.transpileString(code)).toThrowError(
TranspileError,
"Unsupported conversion from function to method. To fix, wrap the function in an arrow function "
+ "or declare the function with an explicit 'this' parameter.");
}

@Test("Interface method assignment")
Expand Down Expand Up @@ -547,7 +627,8 @@ export class AssignmentTests {
let [i, f]: [number, Meth] = getTuple();`;
Expect(() => util.transpileString(code)).toThrowError(
TranspileError,
"Unsupported conversion from function to method. To fix, wrap the function in an arrow function.");
"Unsupported conversion from function to method. To fix, wrap the function in an arrow function or declare"
+ " the function with an explicit 'this' parameter.");
}

@Test("Valid interface method assignment")
Expand Down
X Tutup