X Tutup
Skip to content

Commit 1e0247f

Browse files
tomblindPerryvw
authored andcommitted
New functions v2 (#465)
* all functions take a self parameter - also added noSelf directive to add `this: void` namespace/class-wide - rewrote function assignment tests to be more thorough and maintainable - fixed bug in generators where parameters were being hidden * fixed array lib issues * a few small tweaks - fixed bad definition in one of the assign test functions - performing diagnostics on invalif function assignment tests - added check in transpileString to ensure ignoreDiagnostics command line flag is respected * split out searching for `@noSelf` to its own function * Renamed and reworded function assignment errors to make sense in new context * Replace O(N^2) function assignment tests with more focused ones to reduce overall amount of testing required. I beleive this should still catch all the known edge-cases. * fixed `this` in module test to actually access `this` and removed a leftover FocusTest reference * limited @noself recursion to namespaces and fixed @noself on class expressions * @noself support on interfaces and added/fixed more tests * added @noSelfInFile file-scope directive * only preventing noSelf recursion on methods now and cleaned up some tests
1 parent d9103e8 commit 1e0247f

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

62 files changed

+851
-682
lines changed

src/Decorator.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@ export class Decorator {
2525
return DecoratorKind.NoClassOr;
2626
case "luaiterator":
2727
return DecoratorKind.LuaIterator;
28+
case "noself":
29+
return DecoratorKind.NoSelf;
30+
case "noselfinfile":
31+
return DecoratorKind.NoSelfInFile;
2832
}
2933

3034
return undefined;
@@ -50,4 +54,6 @@ export enum DecoratorKind {
5054
TupleReturn = "TupleReturn",
5155
NoClassOr = "NoClassOr",
5256
LuaIterator = "LuaIterator",
57+
NoSelf = "NoSelf",
58+
NoSelfInFile = "NoSelfInFile",
5359
}

src/LuaTransformer.ts

Lines changed: 14 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1454,16 +1454,13 @@ export class LuaTransformer {
14541454
private transformGeneratorFunction(
14551455
parameters: ts.NodeArray<ts.ParameterDeclaration>,
14561456
body: ts.Block,
1457-
transformedParameters: tstl.Identifier[],
1458-
dotsLiteral: tstl.DotsLiteral,
14591457
spreadIdentifier?: tstl.Identifier
14601458
): [tstl.Statement[], Scope]
14611459
{
14621460
this.importLuaLibFeature(LuaLibFeature.Symbol);
14631461
const [functionBody, functionScope] = this.transformFunctionBody(
14641462
parameters,
1465-
body,
1466-
spreadIdentifier
1463+
body
14671464
);
14681465

14691466
const coroutineIdentifier = tstl.createIdentifier("____co");
@@ -1478,12 +1475,7 @@ export class LuaTransformer {
14781475
tstl.createTableIndexExpression(tstl.createIdentifier("coroutine"),
14791476
tstl.createStringLiteral("create")
14801477
),
1481-
[tstl.createFunctionExpression(
1482-
tstl.createBlock(functionBody),
1483-
transformedParameters,
1484-
dotsLiteral,
1485-
spreadIdentifier),
1486-
]
1478+
[tstl.createFunctionExpression(tstl.createBlock(functionBody))]
14871479
)
14881480
);
14891481

@@ -1586,6 +1578,12 @@ export class LuaTransformer {
15861578
//return ____it
15871579
tstl.createReturnStatement([itIdentifier]),
15881580
];
1581+
1582+
if (spreadIdentifier) {
1583+
const spreadTable = this.wrapInTable(tstl.createDotsLiteral());
1584+
block.unshift(tstl.createVariableDeclarationStatement(spreadIdentifier, spreadTable));
1585+
}
1586+
15891587
return [block, functionScope];
15901588
}
15911589

@@ -1606,8 +1604,6 @@ export class LuaTransformer {
16061604
? this.transformGeneratorFunction(
16071605
functionDeclaration.parameters,
16081606
functionDeclaration.body,
1609-
params,
1610-
dotsLiteral,
16111607
restParamName
16121608
)
16131609
: this.transformFunctionBody(
@@ -3131,14 +3127,12 @@ export class LuaTransformer {
31313127
const callPath = this.transformExpression(node.expression);
31323128
const signatureDeclaration = signature && signature.getDeclaration();
31333129
if (signatureDeclaration
3134-
&& !ts.isPropertyAccessExpression(node.expression)
3135-
&& tsHelper.getDeclarationContextType(signatureDeclaration, this.checker) === ContextType.NonVoid
3136-
&& !ts.isElementAccessExpression(node.expression))
3130+
&& tsHelper.getDeclarationContextType(signatureDeclaration, this.checker) === ContextType.Void)
31373131
{
3132+
parameters = this.transformArguments(node.arguments, signature);
3133+
} else {
31383134
const context = this.isStrict ? ts.createNull() : ts.createIdentifier("_G");
31393135
parameters = this.transformArguments(node.arguments, signature, context);
3140-
} else {
3141-
parameters = this.transformArguments(node.arguments, signature);
31423136
}
31433137

31443138
const expressionType = this.checker.getTypeAtLocation(node.expression);
@@ -3885,7 +3879,7 @@ export class LuaTransformer {
38853879
const expression = node.expression as ts.PropertyAccessExpression;
38863880
const callerType = this.checker.getTypeAtLocation(expression.expression);
38873881
if (tsHelper.getFunctionContextType(callerType, this.checker) === ContextType.Void) {
3888-
throw TSTLErrors.UnsupportedMethodConversion(node);
3882+
throw TSTLErrors.UnsupportedSelfFunctionConversion(node);
38893883
}
38903884
const params = this.transformArguments(node.arguments);
38913885
const caller = this.transformExpression(expression.expression);
@@ -4326,9 +4320,9 @@ export class LuaTransformer {
43264320
throw TSTLErrors.UnsupportedOverloadAssignment(node, toName);
43274321
} else if (fromContext !== toContext && fromContext !== ContextType.None && toContext !== ContextType.None) {
43284322
if (toContext === ContextType.Void) {
4329-
throw TSTLErrors.UnsupportedFunctionConversion(node, toName);
4323+
throw TSTLErrors.UnsupportedNoSelfFunctionConversion(node, toName);
43304324
} else {
4331-
throw TSTLErrors.UnsupportedMethodConversion(node, toName);
4325+
throw TSTLErrors.UnsupportedSelfFunctionConversion(node, toName);
43324326
}
43334327
}
43344328

src/TSHelper.ts

Lines changed: 59 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,21 @@ export class TSHelper {
249249
return decMap;
250250
}
251251

252+
public static getCustomFileDirectives(file: ts.SourceFile): Map<DecoratorKind, Decorator> {
253+
const decMap = new Map<DecoratorKind, Decorator>();
254+
if (file.statements.length > 0) {
255+
const tags = ts.getJSDocTags(file.statements[0]);
256+
for (const tag of tags) {
257+
const tagName = tag.tagName.escapedText as string;
258+
if (Decorator.isValid(tagName)) {
259+
const dec = new Decorator(tagName, tag.comment ? tag.comment.split(" ") : []);
260+
decMap.set(dec.kind, dec);
261+
}
262+
}
263+
}
264+
return decMap;
265+
}
266+
252267
// Search up until finding a node satisfying the callback
253268
public static findFirstNodeAbove<T extends ts.Node>(node: ts.Node, callback: (n: ts.Node) => n is T): T {
254269
let current = node;
@@ -559,6 +574,24 @@ export class TSHelper {
559574
return signatureDeclarations;
560575
}
561576

577+
public static hasNoSelfAncestor(declaration: ts.Declaration, checker: ts.TypeChecker): boolean {
578+
const scopeDeclaration = TSHelper.findFirstNodeAbove(
579+
declaration,
580+
(n): n is ts.SourceFile | ts.ModuleDeclaration => ts.isSourceFile(n) || ts.isModuleDeclaration(n)
581+
);
582+
if (!scopeDeclaration) {
583+
return false;
584+
}
585+
if (ts.isSourceFile(scopeDeclaration)) {
586+
return TSHelper.getCustomFileDirectives(scopeDeclaration).has(DecoratorKind.NoSelfInFile);
587+
}
588+
const scopeType = checker.getTypeAtLocation(scopeDeclaration);
589+
if (scopeType && TSHelper.getCustomDecorators(scopeType, checker).has(DecoratorKind.NoSelf)) {
590+
return true;
591+
}
592+
return TSHelper.hasNoSelfAncestor(scopeDeclaration, checker);
593+
}
594+
562595
public static getDeclarationContextType(
563596
signatureDeclaration: ts.SignatureDeclaration,
564597
checker: ts.TypeChecker
@@ -571,22 +604,35 @@ export class TSHelper {
571604
? ContextType.Void
572605
: ContextType.NonVoid;
573606
}
574-
if (ts.isMethodDeclaration(signatureDeclaration) || ts.isMethodSignature(signatureDeclaration)) {
575-
// Method
576-
return ContextType.NonVoid;
577-
}
578-
if (ts.isPropertySignature(signatureDeclaration.parent)
579-
|| ts.isPropertyDeclaration(signatureDeclaration.parent)
580-
|| ts.isPropertyAssignment(signatureDeclaration.parent)) {
581-
// Lambda property
607+
608+
if (ts.isMethodSignature(signatureDeclaration)
609+
|| ts.isMethodDeclaration(signatureDeclaration)
610+
|| ts.isConstructSignatureDeclaration(signatureDeclaration)
611+
|| ts.isConstructorDeclaration(signatureDeclaration)
612+
|| (signatureDeclaration.parent && ts.isPropertyDeclaration(signatureDeclaration.parent))
613+
|| (signatureDeclaration.parent && ts.isPropertySignature(signatureDeclaration.parent)))
614+
{
615+
// Class/interface methods only respect @noSelf on their parent
616+
const scopeDeclaration = TSHelper.findFirstNodeAbove(
617+
signatureDeclaration,
618+
(n): n is ts.ClassLikeDeclaration | ts.InterfaceDeclaration =>
619+
ts.isClassDeclaration(n)
620+
|| ts.isClassExpression(n)
621+
|| ts.isInterfaceDeclaration(n)
622+
);
623+
const scopeType = checker.getTypeAtLocation(scopeDeclaration);
624+
if (scopeType && TSHelper.getCustomDecorators(scopeType, checker).has(DecoratorKind.NoSelf)) {
625+
return ContextType.Void;
626+
}
582627
return ContextType.NonVoid;
583628
}
584-
if (ts.isBinaryExpression(signatureDeclaration.parent)) {
585-
// Function expression: check type being assigned to
586-
return TSHelper.getFunctionContextType(
587-
checker.getTypeAtLocation(signatureDeclaration.parent.left), checker);
629+
630+
// Walk up to find @noSelf or @noSelfOnFile
631+
if (TSHelper.hasNoSelfAncestor(signatureDeclaration, checker)) {
632+
return ContextType.Void;
588633
}
589-
return ContextType.Void;
634+
635+
return ContextType.NonVoid;
590636
}
591637

592638
public static reduceContextTypes(contexts: ContextType[]): ContextType {

src/TSTLErrors.ts

Lines changed: 14 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -78,46 +78,44 @@ export class TSTLErrors {
7878
public static UnsupportedForTarget = (functionality: string, version: string, node: ts.Node) =>
7979
new TranspileError(`${functionality} is/are not supported for target Lua ${version}.`, node);
8080

81-
public static UnsupportedFunctionConversion = (node: ts.Node, name?: string) => {
81+
public static UnsupportedNoSelfFunctionConversion = (node: ts.Node, name?: string) => {
8282
if (name) {
8383
return new TranspileError(
84-
`Unsupported conversion from method to function "${name}". ` +
85-
`To fix, wrap the method in an arrow function.`,
84+
`Unable to convert function with a 'this' parameter to function "${name}" with no 'this'. ` +
85+
`To fix, wrap in an arrow function, or declare with 'this: void'.`,
8686
node);
8787
} else {
8888
return new TranspileError(
89-
`Unsupported conversion from method to function. ` +
90-
`To fix, wrap the method in an arrow function.`,
89+
`Unable to convert function with a 'this' parameter to function with no 'this'. ` +
90+
`To fix, wrap in an arrow function, or declare with 'this: void'.`,
9191
node);
9292
}
9393
};
9494

95-
public static UnsupportedMethodConversion = (node: ts.Node, name?: string) => {
95+
public static UnsupportedSelfFunctionConversion = (node: ts.Node, name?: string) => {
9696
if (name) {
9797
return new TranspileError(
98-
`Unsupported conversion from function to method "${name}". ` +
99-
`To fix, wrap the function in an arrow function or declare the function with` +
100-
` an explicit 'this' parameter.`,
98+
`Unable to convert function with no 'this' parameter to function "${name}" with 'this'. ` +
99+
`To fix, wrap in an arrow function or declare with 'this: any'.`,
101100
node);
102101
} else {
103102
return new TranspileError(
104-
`Unsupported conversion from function to method. ` +
105-
`To fix, wrap the function in an arrow function or declare the function with` +
106-
` an explicit 'this' parameter.`,
103+
`Unable to convert function with no 'this' parameter to function with 'this'. ` +
104+
`To fix, wrap in an arrow function or declare with 'this: any'.`,
107105
node);
108106
}
109107
};
110108

111109
public static UnsupportedOverloadAssignment = (node: ts.Node, name?: string) => {
112110
if (name) {
113111
return new TranspileError(
114-
`Unsupported assignment of mixed function/method overload to "${name}". ` +
115-
`Overloads should either be all functions or all methods, but not both.`,
112+
`Unsupported assignment of function with different overloaded types for 'this' to "${name}". ` +
113+
`Overloads should all have the same type for 'this'.`,
116114
node);
117115
} else {
118116
return new TranspileError(
119-
`Unsupported assignment of mixed function/method overload. ` +
120-
`Overloads should either be all functions or all methods, but not both.`,
117+
`Unsupported assignment of function with different overloaded types for 'this'. ` +
118+
`Overloads should all have the same type for 'this'.`,
121119
node);
122120
}
123121
};

src/lualib/ArrayConcat.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
declare function pcall(func: () => any): any;
2-
declare function type(val: any): string;
1+
declare function pcall(this: void, func: () => any): any;
2+
declare function type(this: void, val: any): string;
33

4-
function __TS__ArrayConcat(arr1: any[], ...args: any[]): any[] {
4+
function __TS__ArrayConcat(this: void, arr1: any[], ...args: any[]): any[] {
55
const out: any[] = [];
66
for (const val of arr1) {
77
out[out.length] = val;

src/lualib/ArrayEvery.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
function __TS__ArrayEvery<T>(arr: T[], callbackfn: (value: T, index?: number, array?: any[]) => boolean): boolean {
1+
function __TS__ArrayEvery<T>(this: void, arr: T[], callbackfn: (value: T, index?: number, array?: any[]) => boolean)
2+
: boolean
3+
{
24
for (let i = 0; i < arr.length; i++) {
35
if (!callbackfn(arr[i], i, arr)) {
46
return false;

src/lualib/ArrayFilter.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
function __TS__ArrayFilter<T>(arr: T[], callbackfn: (value: T, index?: number, array?: any[]) => boolean): T[] {
1+
function __TS__ArrayFilter<T>(this: void, arr: T[], callbackfn: (value: T, index?: number, array?: any[]) => boolean)
2+
: T[]
3+
{
24
const result: T[] = [];
35
for (let i = 0; i < arr.length; i++) {
46
if (callbackfn(arr[i], i, arr)) {

src/lualib/ArrayFindIndex.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
function __TS__ArrayFindIndex<T>(
2+
this: void,
23
arr: T[],
3-
callbackFn: (this: void, element: T, index?: number, array?: T[]) => boolean
4+
callbackFn: (element: T, index?: number, array?: T[]) => boolean
45
): number {
56
for (let i = 0, len = arr.length; i < len; i++) {
67
if (callbackFn(arr[i], i, arr)) {

src/lualib/ArrayForEach.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
function __TS__ArrayForEach<T>(arr: T[], callbackFn: (value: T, index?: number, array?: any[]) => any): void {
1+
function __TS__ArrayForEach<T>(this: void, arr: T[], callbackFn: (value: T, index?: number, array?: any[]) => any)
2+
: void
3+
{
24
for (let i = 0; i < arr.length; i++) {
35
callbackFn(arr[i], i, arr);
46
}

src/lualib/ArrayIndexOf.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
function __TS__ArrayIndexOf<T>(arr: T[], searchElement: T, fromIndex?: number): number {
1+
function __TS__ArrayIndexOf<T>(this: void, arr: T[], searchElement: T, fromIndex?: number): number {
22
const len = arr.length;
33
if (len === 0) {
44
return -1;

0 commit comments

Comments
 (0)
X Tutup