X Tutup
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 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
213ba03
Merge remote-tracking branch 'upstream/new-functions' into new-functions
tomblind Dec 14, 2018
b4b0820
handling more edge cases, adding more tests and a little bit of refac…
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
38 changes: 28 additions & 10 deletions src/TSHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,9 +117,9 @@ 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));
const declaration = this.findFirstNodeAbove(node, ts.isFunctionLike);
if (declaration) {
const signature = checker.getSignatureFromDeclaration(declaration as ts.SignatureDeclaration);
const signature = checker.getSignatureFromDeclaration(declaration);
return checker.getReturnTypeOfSignature(signature);
}
return null;
Expand Down Expand Up @@ -320,7 +320,8 @@ export class TSHelper {
return ContextType.NonVoid;
}
if ((ts.isPropertySignature(signatureDeclaration.parent)
|| ts.isPropertyDeclaration(signatureDeclaration.parent))
|| ts.isPropertyDeclaration(signatureDeclaration.parent)
|| ts.isPropertyAssignment(signatureDeclaration.parent))
&& !(ts.getCombinedModifierFlags(signatureDeclaration.parent) & ts.ModifierFlags.Static)) {
// Non-static lambda property
return ContextType.NonVoid;
Expand All @@ -332,18 +333,35 @@ export class TSHelper {
return ContextType.Void;
}

public static reduceContextTypes(contexts: ContextType[]): ContextType {
const reducer = (a: ContextType, b: ContextType) => {
if (a === ContextType.None) {
return b;
} else if (b === ContextType.None) {
return a;
} else if (a !== b) {
return ContextType.Mixed;
} else {
return a;
}
};
return contexts.reduce(reducer, ContextType.None);
}

public static getFunctionContextType(type: ts.Type, checker: ts.TypeChecker): ContextType {
if (type.isTypeParameter()) {
type = type.getConstraint() || type;
}

if (type.isUnion()) {
return this.reduceContextTypes(type.types.map(t => this.getFunctionContextType(t, checker)));
}

const signatures = checker.getSignaturesOfType(type, ts.SignatureKind.Call);
if (signatures.length === 0) {
return ContextType.None;
}
const signatureDeclarations = this.getSignatureDeclarations(signatures, checker);
const context = this.getDeclarationContextType(signatureDeclarations[0], checker);
for (let i = 1; i < signatureDeclarations.length; ++i) {
if (this.getDeclarationContextType(signatureDeclarations[i], checker) !== context) {
return ContextType.Mixed;
}
}
return context;
return this.reduceContextTypes(signatureDeclarations.map(s => this.getDeclarationContextType(s, checker)));
}
}
14 changes: 8 additions & 6 deletions src/Transpiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -800,7 +800,7 @@ export abstract class LuaTranspiler {
case ts.SyntaxKind.FunctionExpression:
return this.transpileFunctionExpression(node as ts.ArrowFunction, "self");
case ts.SyntaxKind.ArrowFunction:
return this.transpileFunctionExpression(node as ts.ArrowFunction, "_");
return this.transpileFunctionExpression(node as ts.ArrowFunction, "____");
case ts.SyntaxKind.NewExpression:
return this.transpileNewExpression(node as ts.NewExpression);
case ts.SyntaxKind.ComputedPropertyName:
Expand Down Expand Up @@ -1447,7 +1447,7 @@ export abstract class LuaTranspiler {
const toContext = tsHelper.getFunctionContextType(toType, this.checker);
if (fromContext === ContextType.Mixed || toContext === ContextType.Mixed) {
throw TSTLErrors.UnsupportedOverloadAssignment(node, toName);
} else if (fromContext !== toContext) {
} else if (fromContext !== toContext && fromContext !== ContextType.None && toContext !== ContextType.None) {
if (toContext === ContextType.Void) {
throw TSTLErrors.UnsupportedFunctionConversion(node, toName);
} else {
Expand All @@ -1469,10 +1469,12 @@ export abstract class LuaTranspiler {
toType.symbol.members.forEach(
(toMember, memberName) => {
const fromMember = fromType.symbol.members.get(memberName);
const toMemberType = this.checker.getTypeOfSymbolAtLocation(toMember, node);
const fromMemberType = this.checker.getTypeOfSymbolAtLocation(fromMember, node);
this.validateAssignment(node, fromMemberType, toMemberType,
toName ? `${toName}.${memberName}` : memberName.toString());
if (fromMember) {
const toMemberType = this.checker.getTypeOfSymbolAtLocation(toMember, node);
const fromMemberType = this.checker.getTypeOfSymbolAtLocation(fromMember, node);
this.validateAssignment(node, fromMemberType, toMemberType,
toName ? `${toName}.${memberName}` : memberName.toString());
}
}
);
}
Expand Down
2 changes: 1 addition & 1 deletion test/translation/lua/namespaceMerge.lua
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ MergedClass = MergedClass or {}
MergedClass.__index = MergedClass
function MergedClass.new(construct, ...)
local self = setmetatable({}, MergedClass)
self.propertyFunc = function(_)
self.propertyFunc = function(____)
end
if construct and MergedClass.constructor then MergedClass.constructor(self, ...) end
return self
Expand Down
90 changes: 73 additions & 17 deletions test/unit/assignments.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,7 @@ export class AssignmentTests {
@TestCase("foo.voidLambdaProp", "Foo.staticMethod", "foo+staticMethod")
@TestCase("foo.voidLambdaProp", "Foo.staticLambdaProp", "foo+staticLambdaProp")
@TestCase("foo.voidLambdaProp", "foo.voidMethod", "foo+voidMethod")
@TestCase("func", "(func as (string | ((s: string) => string)))", "foo+func")
@Test("Valid function assignment")
public validFunctionAssignment(func: string, assignTo: string, expectResult: string): void {
const code = `${AssignmentTests.funcAssignTestCode} ${func} = ${assignTo}; return ${func}("foo");`;
Expand All @@ -279,10 +280,17 @@ export class AssignmentTests {
@TestCase("s => s", "foo")
@TestCase("function(s) { return s; }", "foo")
@TestCase("function(this: void, s: string) { return s; }", "foo")
@TestCase("func", "foo+func", "string | ((s: string) => string)")
@TestCase("func", "foo+func", "T")
@Test("Valid function argument")
public validFunctionArgument(func: string, expectResult: string): void {
public validFunctionArgument(func: string, expectResult: string, funcType?: string): void {
if (!funcType) {
funcType = "(s: string) => s";
}
const code = `${AssignmentTests.funcAssignTestCode}
function takesFunc(fn: (s: string) => s) { return fn("foo"); }
function takesFunc<T extends ((s: string) => string)>(fn: ${funcType}) {
return (fn as any)("foo");
}
return takesFunc(${func});`;
const result = util.transpileAndExecute(code);
Expect(result).toBe(expectResult);
Expand All @@ -297,10 +305,17 @@ export class AssignmentTests {
@TestCase("s => s", "foo")
@TestCase("function(s) { return s; }", "foo")
@TestCase("function(this: void, s: string) { return s; }", "foo")
@TestCase("func", "foo+func", "string | ((s: string) => string)")
@TestCase("func", "foo+func", "T")
@Test("Valid function return")
public validFunctionReturn(func: string, expectResult: string): void {
public validFunctionReturn(func: string, expectResult: string, funcType?: string): void {
if (!funcType) {
funcType = "(s: string) => s";
}
const code = `${AssignmentTests.funcAssignTestCode}
function returnsFunc(): (s: string) => string { return ${func}; }
function returnsFunc<T extends ((s: string) => string)>(): ${funcType} {
return ${func};
}
const fn = returnsFunc();
return fn("foo");`;
const result = util.transpileAndExecute(code);
Expand Down Expand Up @@ -367,6 +382,7 @@ export class AssignmentTests {
@TestCase("thisLambda", "Foo.thisStaticMethod", "foo+thisStaticMethod")
@TestCase("thisLambda", "Foo.thisStaticLambdaProp", "foo+thisStaticLambdaProp")
@TestCase("thisLambda", "thisFunc", "foo+thisFunc")
@TestCase("foo.method", "(foo.method as (string | ((this: Foo, s: string) => string))", "foo+method")
@Test("Valid method assignment")
public validMethodAssignment(func: string, assignTo: string, expectResult: string): void {
const code = `${AssignmentTests.funcAssignTestCode} ${func} = ${assignTo}; return ${func}("foo");`;
Expand All @@ -383,10 +399,17 @@ export class AssignmentTests {
@TestCase("s => s", "foo")
@TestCase("function(s) { return s; }", "foo")
@TestCase("function(this: Foo, s: string) { return s; }", "foo")
@TestCase("foo.method", "foo+method", "string | ((this: Foo, s: string) => string)")
@TestCase("foo.method", "foo+method", "T")
@Test("Valid method argument")
public validMethodArgument(func: string, expectResult: string): void {
public validMethodArgument(func: string, expectResult: string, funcType?: string): void {
if (!funcType) {
funcType = "(this: Foo, s: string) => string";
}
const code = `${AssignmentTests.funcAssignTestCode}
function takesMethod(meth: (this: Foo, s: string) => string) { foo.method = meth; }
function takesMethod<T extends ((this: Foo, s: string) => string)>(meth: ${funcType}) {
foo.method = meth as any;
}
takesMethod(${func});
return foo.method("foo");`;
const result = util.transpileAndExecute(code);
Expand All @@ -402,10 +425,17 @@ export class AssignmentTests {
@TestCase("s => s", "foo")
@TestCase("function(s) { return s; }", "foo")
@TestCase("function(this: Foo, s: string) { return s; }", "foo")
@TestCase("foo.method", "foo+method", "string | ((this: Foo, s: string) => string)")
@TestCase("foo.method", "foo+method", "T")
@Test("Valid method return")
public validMethodReturn(func: string, expectResult: string): void {
public validMethodReturn(func: string, expectResult: string, funcType?: string): void {
if (!funcType) {
funcType = "(this: Foo, s: string) => string";
}
const code = `${AssignmentTests.funcAssignTestCode}
function returnMethod(): (this: Foo, s: string) => string { return ${func}; }
function returnMethod<T extends ((this: Foo, s: string) => string)>(): ${funcType} {
return ${func};
}
foo.method = returnMethod();
return foo.method("foo");`;
const result = util.transpileAndExecute(code);
Expand Down Expand Up @@ -442,6 +472,7 @@ export class AssignmentTests {
@TestCase("Foo.staticLambdaProp", "Foo.thisStaticMethod")
@TestCase("Foo.staticLambdaProp", "Foo.thisStaticLambdaProp")
@TestCase("Foo.staticLambdaProp", "function(this: Foo, s: string) { return s; }")
@TestCase("func", "(foo.method as (string | ((this: Foo, s: string) => string)))")
@Test("Invalid function assignment")
public invalidFunctionAssignment(func: string, assignTo: string): void {
const code = `${AssignmentTests.funcAssignTestCode} ${func} = ${assignTo};`;
Expand All @@ -457,10 +488,15 @@ export class AssignmentTests {
@TestCase("thisFunc")
@TestCase("thisLambda")
@TestCase("function(this: Foo, s: string) { return s; }")
@TestCase("foo.method", "string | ((s: string) => string)")
@TestCase("foo.method", "T")
@Test("Invalid function argument")
public invalidFunctionArgument(func: string): void {
public invalidFunctionArgument(func: string, funcType?: string): void {
if (!funcType) {
funcType = "(s: string) => s";
}
const code = `${AssignmentTests.funcAssignTestCode}
declare function takesFunc(fn: (s: string) => s);
declare function takesFunc<T extends ((s: string) => string)>(fn: ${funcType});
takesFunc(${func});`;
Expect(() => util.transpileString(code)).toThrowError(
TranspileError,
Expand All @@ -474,10 +510,17 @@ export class AssignmentTests {
@TestCase("thisFunc")
@TestCase("thisLambda")
@TestCase("function(this: Foo, s: string) { return s; }")
@TestCase("foo.method", "string | ((s: string) => string)")
@TestCase("foo.method", "T")
@Test("Invalid function return")
public invalidFunctionReturn(func: string): void {
public invalidFunctionReturn(func: string, funcType?: string): void {
if (!funcType) {
funcType = "(s: string) => s";
}
const code = `${AssignmentTests.funcAssignTestCode}
function returnsFunc(): (s: string) => string { return ${func}; }`;
function returnsFunc<T extends ((s: string) => string)>(): ${funcType} {
return ${func};
}`;
Expect(() => util.transpileString(code)).toThrowError(
TranspileError,
"Unsupported conversion from method to function. To fix, wrap the method in an arrow function.");
Expand Down Expand Up @@ -525,6 +568,7 @@ export class AssignmentTests {
@TestCase("thisLambda", "foo.voidMethod")
@TestCase("thisLambda", "foo.voidLambdaProp")
@TestCase("thisLambda", "function(this: void, s: string) { return s; }")
@TestCase("foo.method", "(func as string | ((s: string) => string))")
@Test("Invalid method assignment")
public invalidMethodAssignment(func: string, assignTo: string): void {
const code = `${AssignmentTests.funcAssignTestCode} ${func} = ${assignTo};`;
Expand All @@ -541,10 +585,15 @@ export class AssignmentTests {
@TestCase("foo.voidMethod")
@TestCase("foo.voidLambdaProp")
@TestCase("function(this: void, s: string) { return s; }")
@TestCase("func", "string | ((this: Foo, s: string) => string)")
@TestCase("func", "T")
@Test("Invalid method argument")
public invalidMethodArgument(func: string): void {
public invalidMethodArgument(func: string, funcType?: string): void {
if (!funcType) {
funcType = "(this: Foo, s: string) => string";
}
const code = `${AssignmentTests.funcAssignTestCode}
declare function takesMethod(meth: (this: Foo, s: string) => s);
declare function takesMethod<T extends ((this: Foo, s: string) => string)>(meth: ${funcType});
takesMethod(${func});`;
Expect(() => util.transpileString(code)).toThrowError(
TranspileError,
Expand All @@ -559,10 +608,17 @@ export class AssignmentTests {
@TestCase("foo.voidMethod")
@TestCase("foo.voidLambdaProp")
@TestCase("function(this: void, s: string) { return s; }")
@TestCase("func", "string | ((this: Foo, s: string) => string)")
@TestCase("func", "T")
@Test("Invalid method return")
public invalidMethodReturn(func: string): void {
public invalidMethodReturn(func: string, funcType?: string): void {
if (!funcType) {
funcType = "(this: Foo, s: string) => string";
}
const code = `${AssignmentTests.funcAssignTestCode}
function returnsMethod(): (this: Foo, s: string) => s { return ${func}; }`;
function returnsMethod<T extends ((this: Foo, s: string) => string)>(): ${funcType} {
return ${func};
}`;
Expect(() => util.transpileString(code)).toThrowError(
TranspileError,
"Unsupported conversion from function to method. To fix, wrap the function in an arrow function "
Expand Down Expand Up @@ -635,7 +691,7 @@ export class AssignmentTests {
public validInterfaceMethodAssignment(): void {
const code = `interface A { fn(this: void, s: string): string; }
interface B { fn(this: void, s: string): string; }
const a: A = { fn: s => s };
const a: A = { fn(this: void, s) { return s; } };
const b: B = a;
return b.fn("foo");`;
const result = util.transpileAndExecute(code);
Expand Down
19 changes: 19 additions & 0 deletions test/unit/functions.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -340,4 +340,23 @@ export class FunctionTests {
const result = util.transpileAndExecute(code);
Expect(result).toBe(expectResult);
}

@Test("Nested Function")
public nestedFunction(): void {
const code = `class C {
private prop = "bar";
public outer() {
const o = {
prop: "foo",
innerFunc: function() { return this.prop; },
innerArrow: () => this.prop
};
return o.innerFunc() + o.innerArrow();
}
}
let c = new C();
return c.outer();`;
const result = util.transpileAndExecute(code);
Expect(result).toBe("foobar");
}
}
X Tutup