X Tutup
Skip to content

Commit 8597fd4

Browse files
tomblindPerryvw
authored andcommitted
validating function type conversions in type assertions and 'as' expressions (#312)
1 parent ee7a61d commit 8597fd4

File tree

2 files changed

+58
-8
lines changed

2 files changed

+58
-8
lines changed

src/Transpiler.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -797,11 +797,8 @@ export abstract class LuaTranspiler {
797797
case ts.SyntaxKind.SuperKeyword:
798798
return "self.__base";
799799
case ts.SyntaxKind.TypeAssertionExpression:
800-
// Simply ignore the type assertion
801-
return this.transpileExpression((node as ts.TypeAssertion).expression);
802800
case ts.SyntaxKind.AsExpression:
803-
// Also ignore as casts
804-
return this.transpileExpression((node as ts.AsExpression).expression);
801+
return this.transpileAssertionExpression(node as ts.AssertionExpression);
805802
case ts.SyntaxKind.TypeOfExpression:
806803
return this.transpileTypeOfExpression(node as ts.TypeOfExpression);
807804
case ts.SyntaxKind.EmptyStatement:
@@ -1694,6 +1691,13 @@ export abstract class LuaTranspiler {
16941691
}
16951692
}
16961693

1694+
public transpileAssertionExpression(node: ts.AssertionExpression): string {
1695+
this.validateFunctionAssignment(node,
1696+
this.checker.getTypeAtLocation(node.expression),
1697+
this.checker.getTypeAtLocation(node.type));
1698+
return this.transpileExpression(node.expression);
1699+
}
1700+
16971701
public transpileTypeOfExpression(node: ts.TypeOfExpression): string {
16981702
const expression = this.transpileExpression(node.expression);
16991703
return `(type(${expression}) == "table" and "object" or type(${expression}))`;

test/unit/assignments.spec.ts

Lines changed: 50 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,8 @@ export class AssignmentTests {
264264
@TestCase("foo.voidLambdaProp", "Foo.staticLambdaProp", "foo+staticLambdaProp")
265265
@TestCase("foo.voidLambdaProp", "foo.voidMethod", "foo+voidMethod")
266266
@TestCase("func", "(func as (string | ((s: string) => string)))", "foo+func")
267+
@TestCase("func", "<(s: string) => string>lambda", "foo+lambda")
268+
@TestCase("func", "lambda as ((s: string) => string)", "foo+lambda")
267269
@Test("Valid function assignment")
268270
public validFunctionAssignment(func: string, assignTo: string, expectResult: string): void {
269271
const code = `${AssignmentTests.funcAssignTestCode} ${func} = ${assignTo}; return ${func}("foo");`;
@@ -282,10 +284,12 @@ export class AssignmentTests {
282284
@TestCase("function(this: void, s: string) { return s; }", "foo")
283285
@TestCase("func", "foo+func", "string | ((s: string) => string)")
284286
@TestCase("func", "foo+func", "T")
287+
@TestCase("<(s: string) => string>func", "foo+func")
288+
@TestCase("func as ((s: string) => string)", "foo+func")
285289
@Test("Valid function argument")
286290
public validFunctionArgument(func: string, expectResult: string, funcType?: string): void {
287291
if (!funcType) {
288-
funcType = "(s: string) => s";
292+
funcType = "(s: string) => string";
289293
}
290294
const code = `${AssignmentTests.funcAssignTestCode}
291295
function takesFunc<T extends ((s: string) => string)>(fn: ${funcType}) {
@@ -307,10 +311,12 @@ export class AssignmentTests {
307311
@TestCase("function(this: void, s: string) { return s; }", "foo")
308312
@TestCase("func", "foo+func", "string | ((s: string) => string)")
309313
@TestCase("func", "foo+func", "T")
314+
@TestCase("<(s: string) => string>func", "foo+func")
315+
@TestCase("func as ((s: string) => string)", "foo+func")
310316
@Test("Valid function return")
311317
public validFunctionReturn(func: string, expectResult: string, funcType?: string): void {
312318
if (!funcType) {
313-
funcType = "(s: string) => s";
319+
funcType = "(s: string) => string";
314320
}
315321
const code = `${AssignmentTests.funcAssignTestCode}
316322
function returnsFunc<T extends ((s: string) => string)>(): ${funcType} {
@@ -383,6 +389,8 @@ export class AssignmentTests {
383389
@TestCase("thisLambda", "Foo.thisStaticLambdaProp", "foo+thisStaticLambdaProp")
384390
@TestCase("thisLambda", "thisFunc", "foo+thisFunc")
385391
@TestCase("foo.method", "(foo.method as (string | ((this: Foo, s: string) => string))", "foo+method")
392+
@TestCase("foo.method", "<(this: Foo, s: string) => string>foo.lambdaProp", "foo+lambdaProp")
393+
@TestCase("foo.method", "foo.lambdaProp as ((this: Foo, s: string) => string)", "foo+lambdaProp")
386394
@Test("Valid method assignment")
387395
public validMethodAssignment(func: string, assignTo: string, expectResult: string): void {
388396
const code = `${AssignmentTests.funcAssignTestCode} ${func} = ${assignTo}; return ${func}("foo");`;
@@ -401,6 +409,8 @@ export class AssignmentTests {
401409
@TestCase("function(this: Foo, s: string) { return s; }", "foo")
402410
@TestCase("foo.method", "foo+method", "string | ((this: Foo, s: string) => string)")
403411
@TestCase("foo.method", "foo+method", "T")
412+
@TestCase("<(this: Foo, s: string) => string>foo.method", "foo+method")
413+
@TestCase("foo.method as ((this: Foo, s: string) => string)", "foo+method")
404414
@Test("Valid method argument")
405415
public validMethodArgument(func: string, expectResult: string, funcType?: string): void {
406416
if (!funcType) {
@@ -427,6 +437,8 @@ export class AssignmentTests {
427437
@TestCase("function(this: Foo, s: string) { return s; }", "foo")
428438
@TestCase("foo.method", "foo+method", "string | ((this: Foo, s: string) => string)")
429439
@TestCase("foo.method", "foo+method", "T")
440+
@TestCase("<(this: Foo, s: string) => string>foo.method", "foo+method")
441+
@TestCase("foo.method as ((this: Foo, s: string) => string)", "foo+method")
430442
@Test("Valid method return")
431443
public validMethodReturn(func: string, expectResult: string, funcType?: string): void {
432444
if (!funcType) {
@@ -473,6 +485,8 @@ export class AssignmentTests {
473485
@TestCase("Foo.staticLambdaProp", "Foo.thisStaticLambdaProp")
474486
@TestCase("Foo.staticLambdaProp", "function(this: Foo, s: string) { return s; }")
475487
@TestCase("func", "(foo.method as (string | ((this: Foo, s: string) => string)))")
488+
@TestCase("func", "<(s: string) => string>foo.method")
489+
@TestCase("func", "foo.method as ((s: string) => string)")
476490
@Test("Invalid function assignment")
477491
public invalidFunctionAssignment(func: string, assignTo: string): void {
478492
const code = `${AssignmentTests.funcAssignTestCode} ${func} = ${assignTo};`;
@@ -493,7 +507,7 @@ export class AssignmentTests {
493507
@Test("Invalid function argument")
494508
public invalidFunctionArgument(func: string, funcType?: string): void {
495509
if (!funcType) {
496-
funcType = "(s: string) => s";
510+
funcType = "(s: string) => string";
497511
}
498512
const code = `${AssignmentTests.funcAssignTestCode}
499513
declare function takesFunc<T extends ((s: string) => string)>(fn: ${funcType});
@@ -503,6 +517,18 @@ export class AssignmentTests {
503517
"Unsupported conversion from method to function \"fn\". To fix, wrap the method in an arrow function.");
504518
}
505519

520+
@TestCase("<(s: string) => string>foo.method")
521+
@TestCase("foo.method as ((s: string) => string)")
522+
@Test("Invalid function argument cast")
523+
public invalidFunctionArgumentCast(func: string): void {
524+
const code = `${AssignmentTests.funcAssignTestCode}
525+
declare function takesFunc<T extends ((s: string) => string)>(fn: (s: string) => string);
526+
takesFunc(${func});`;
527+
Expect(() => util.transpileString(code)).toThrowError(
528+
TranspileError,
529+
"Unsupported conversion from method to function. To fix, wrap the method in an arrow function.");
530+
}
531+
506532
@TestCase("foo.method")
507533
@TestCase("foo.lambdaProp")
508534
@TestCase("Foo.thisStaticMethod")
@@ -512,10 +538,12 @@ export class AssignmentTests {
512538
@TestCase("function(this: Foo, s: string) { return s; }")
513539
@TestCase("foo.method", "string | ((s: string) => string)")
514540
@TestCase("foo.method", "T")
541+
@TestCase("<(s: string) => string>foo.method")
542+
@TestCase("foo.method as ((s: string) => string)")
515543
@Test("Invalid function return")
516544
public invalidFunctionReturn(func: string, funcType?: string): void {
517545
if (!funcType) {
518-
funcType = "(s: string) => s";
546+
funcType = "(s: string) => string";
519547
}
520548
const code = `${AssignmentTests.funcAssignTestCode}
521549
function returnsFunc<T extends ((s: string) => string)>(): ${funcType} {
@@ -569,6 +597,8 @@ export class AssignmentTests {
569597
@TestCase("thisLambda", "foo.voidLambdaProp")
570598
@TestCase("thisLambda", "function(this: void, s: string) { return s; }")
571599
@TestCase("foo.method", "(func as string | ((s: string) => string))")
600+
@TestCase("foo.method", "<(this: Foo, s: string) => string>func")
601+
@TestCase("foo.method", "func as ((this: Foo, s: string) => string)")
572602
@Test("Invalid method assignment")
573603
public invalidMethodAssignment(func: string, assignTo: string): void {
574604
const code = `${AssignmentTests.funcAssignTestCode} ${func} = ${assignTo};`;
@@ -601,6 +631,20 @@ export class AssignmentTests {
601631
+ "or declare the function with an explicit 'this' parameter.");
602632
}
603633

634+
@TestCase("<(this: Foo, s: string) => string>func")
635+
@TestCase("func as ((this: Foo, s: string) => string)")
636+
@Test("Invalid method argument cast")
637+
public invalidMethodArgumentCast(func: string): void {
638+
const code = `${AssignmentTests.funcAssignTestCode}
639+
declare function takesMethod<T extends ((this: Foo, s: string) => string)>(
640+
meth: (this: Foo, s: string) => string);
641+
takesMethod(${func});`;
642+
Expect(() => util.transpileString(code)).toThrowError(
643+
TranspileError,
644+
"Unsupported conversion from function to method. To fix, wrap the function in an arrow function "
645+
+ "or declare the function with an explicit 'this' parameter.");
646+
}
647+
604648
@TestCase("func")
605649
@TestCase("lambda")
606650
@TestCase("Foo.staticMethod")
@@ -610,6 +654,8 @@ export class AssignmentTests {
610654
@TestCase("function(this: void, s: string) { return s; }")
611655
@TestCase("func", "string | ((this: Foo, s: string) => string)")
612656
@TestCase("func", "T")
657+
@TestCase("<(this: Foo, s: string) => string>func")
658+
@TestCase("func as ((this: Foo, s: string) => string)")
613659
@Test("Invalid method return")
614660
public invalidMethodReturn(func: string, funcType?: string): void {
615661
if (!funcType) {

0 commit comments

Comments
 (0)
X Tutup