Skip to content

Commit 5d63b1c

Browse files
PerryvwZ3rio
andauthored
Reworked function context checking based on #1510 (#1519)
* Cleanup code / make more readable * Further readability fix * Fix noSelfInFile check for call visitor * Fix/further rewrite of `isContextualCallExpression` * Further readability improvements * Change back returns in `isContextualCallExpression` * Add testcase for noSelfInFile over noImplicitSelf * Simplify testcase * Improve NoSelfInFile check * Further hasnoselfinfile fix * wrap declaration statement again * Add noSelfInFile check for func declared in other file * Remove accidentally commited debug logs * Rework function call context checking * Add back some caching that was removed in last commit --------- Co-authored-by: Zerio <neoboij@gmail.com>
1 parent bf87953 commit 5d63b1c

File tree

4 files changed

+123
-30
lines changed

4 files changed

+123
-30
lines changed

src/transformation/utils/function-context.ts

Lines changed: 53 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,37 @@ function getExplicitThisParameter(signatureDeclaration: ts.SignatureDeclaration)
3535
}
3636
}
3737

38+
const callContextTypes = new WeakMap<ts.CallLikeExpression, ContextType>();
39+
40+
export function getCallContextType(context: TransformationContext, callExpression: ts.CallLikeExpression): ContextType {
41+
const known = callContextTypes.get(callExpression);
42+
if (known !== undefined) return known;
43+
44+
const signature = context.checker.getResolvedSignature(callExpression);
45+
const signatureDeclaration = signature?.getDeclaration();
46+
47+
let contextType = ContextType.None;
48+
49+
if (signatureDeclaration) {
50+
contextType = computeDeclarationContextType(context, signatureDeclaration);
51+
} else {
52+
// No signature declaration could be resolved, so instead try to see if the declaration is in a
53+
// noSelfInFile file
54+
const declarations = findRootDeclarations(context, callExpression);
55+
contextType = declarations.some(d => getFileAnnotations(d.getSourceFile()).has(AnnotationKind.NoSelfInFile))
56+
? ContextType.Void
57+
: context.options.noImplicitSelf
58+
? ContextType.Void
59+
: ContextType.NonVoid;
60+
}
61+
62+
callContextTypes.set(callExpression, contextType);
63+
return contextType;
64+
}
65+
3866
const signatureDeclarationContextTypes = new WeakMap<ts.SignatureDeclaration, ContextType>();
3967

40-
export function getDeclarationContextType(
68+
function getSignatureContextType(
4169
context: TransformationContext,
4270
signatureDeclaration: ts.SignatureDeclaration
4371
): ContextType {
@@ -48,6 +76,29 @@ export function getDeclarationContextType(
4876
return contextType;
4977
}
5078

79+
function findRootDeclarations(context: TransformationContext, callExpression: ts.CallLikeExpression): ts.Declaration[] {
80+
const calledExpression = ts.isTaggedTemplateExpression(callExpression)
81+
? callExpression.tag
82+
: ts.isJsxSelfClosingElement(callExpression)
83+
? callExpression.tagName
84+
: ts.isJsxOpeningElement(callExpression)
85+
? callExpression.tagName
86+
: callExpression.expression;
87+
const calledSymbol = context.checker.getSymbolAtLocation(calledExpression);
88+
if (calledSymbol === undefined) return [];
89+
90+
return (
91+
calledSymbol.getDeclarations()?.flatMap(d => {
92+
if (ts.isImportSpecifier(d)) {
93+
const aliasSymbol = context.checker.getAliasedSymbol(calledSymbol);
94+
return aliasSymbol.getDeclarations() ?? [];
95+
} else {
96+
return [d];
97+
}
98+
}) ?? []
99+
);
100+
}
101+
51102
function computeDeclarationContextType(context: TransformationContext, signatureDeclaration: ts.SignatureDeclaration) {
52103
const thisParameter = getExplicitThisParameter(signatureDeclaration);
53104
if (thisParameter) {
@@ -172,6 +223,6 @@ function computeFunctionContextType(context: TransformationContext, type: ts.Typ
172223
}
173224

174225
return reduceContextTypes(
175-
signatures.flatMap(s => getSignatureDeclarations(context, s)).map(s => getDeclarationContextType(context, s))
226+
signatures.flatMap(s => getSignatureDeclarations(context, s)).map(s => getSignatureContextType(context, s))
176227
);
177228
}

src/transformation/visitors/call.ts

Lines changed: 11 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import * as lua from "../../LuaAST";
33
import { transformBuiltinCallExpression } from "../builtins";
44
import { FunctionVisitor, TransformationContext } from "../context";
55
import { validateAssignment } from "../utils/assignment-validation";
6-
import { ContextType, getDeclarationContextType } from "../utils/function-context";
6+
import { ContextType, getCallContextType } from "../utils/function-context";
77
import { wrapInTable } from "../utils/lua-ast";
88
import { isValidLuaIdentifier } from "../utils/safe-names";
99
import { isExpressionWithEvaluationEffect } from "../utils/typescript";
@@ -117,16 +117,15 @@ function transformElementAccessCall(
117117
export function transformContextualCallExpression(
118118
context: TransformationContext,
119119
node: ts.CallExpression | ts.TaggedTemplateExpression,
120-
args: ts.Expression[] | ts.NodeArray<ts.Expression>,
121-
signature?: ts.Signature
120+
args: ts.Expression[] | ts.NodeArray<ts.Expression>
122121
): lua.Expression {
123122
if (ts.isOptionalChain(node)) {
124123
return transformOptionalChain(context, node);
125124
}
126125
const left = ts.isCallExpression(node) ? getCalledExpression(node) : node.tag;
127126

128127
let { precedingStatements: argPrecedingStatements, result: transformedArguments } =
129-
transformInPrecedingStatementScope(context, () => transformArguments(context, args, signature));
128+
transformInPrecedingStatementScope(context, () => transformArguments(context, args));
130129

131130
if (
132131
ts.isPropertyAccessExpression(left) &&
@@ -189,10 +188,9 @@ function transformPropertyCall(
189188
return lua.createCallExpression(context.transformExpression(node.expression), parameters, node);
190189
}
191190

192-
const signatureDeclaration = signature?.getDeclaration();
193-
if (!signatureDeclaration || getDeclarationContextType(context, signatureDeclaration) !== ContextType.Void) {
191+
if (getCallContextType(context, node) !== ContextType.Void) {
194192
// table:name()
195-
return transformContextualCallExpression(context, node, node.arguments, signature);
193+
return transformContextualCallExpression(context, node, node.arguments);
196194
} else {
197195
// table.name()
198196
const [callPath, parameters] = transformCallAndArguments(context, node.expression, node.arguments, signature);
@@ -202,14 +200,12 @@ function transformPropertyCall(
202200
}
203201

204202
function transformElementCall(context: TransformationContext, node: ts.CallExpression): lua.Expression {
205-
const signature = context.checker.getResolvedSignature(node);
206-
const signatureDeclaration = signature?.getDeclaration();
207-
if (!signatureDeclaration || getDeclarationContextType(context, signatureDeclaration) !== ContextType.Void) {
203+
if (getCallContextType(context, node) !== ContextType.Void) {
208204
// A contextual parameter must be given to this call expression
209-
return transformContextualCallExpression(context, node, node.arguments, signature);
205+
return transformContextualCallExpression(context, node, node.arguments);
210206
} else {
211207
// No context
212-
const [expression, parameters] = transformCallAndArguments(context, node.expression, node.arguments, signature);
208+
const [expression, parameters] = transformCallAndArguments(context, node.expression, node.arguments);
213209
return lua.createCallExpression(expression, parameters, node);
214210
}
215211
}
@@ -267,7 +263,9 @@ export const transformCallExpression: FunctionVisitor<ts.CallExpression> = (node
267263

268264
let callPath: lua.Expression;
269265
let parameters: lua.Expression[];
270-
const isContextualCall = isContextualCallExpression(context, signature);
266+
267+
const isContextualCall = getCallContextType(context, node) !== ContextType.Void;
268+
271269
if (!isContextualCall) {
272270
[callPath, parameters] = transformCallAndArguments(context, calledExpression, node.arguments, signature);
273271
} else {
@@ -290,14 +288,6 @@ export const transformCallExpression: FunctionVisitor<ts.CallExpression> = (node
290288
return wrapResultInTable ? wrapInTable(callExpression) : callExpression;
291289
};
292290

293-
function isContextualCallExpression(context: TransformationContext, signature: ts.Signature | undefined): boolean {
294-
const declaration = signature?.getDeclaration();
295-
if (!declaration) {
296-
return !context.options.noImplicitSelf;
297-
}
298-
return getDeclarationContextType(context, declaration) !== ContextType.Void;
299-
}
300-
301291
export function getCalledExpression(node: ts.CallExpression): ts.Expression {
302292
return ts.skipOuterExpressions(node.expression);
303293
}

src/transformation/visitors/template.ts

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import * as ts from "typescript";
22
import * as lua from "../../LuaAST";
33
import { FunctionVisitor } from "../context";
4-
import { ContextType, getDeclarationContextType } from "../utils/function-context";
4+
import { ContextType, getCallContextType } from "../utils/function-context";
55
import { wrapInToStringForConcat } from "../utils/lua-ast";
66
import { isStringType } from "../utils/typescript";
77
import { transformArguments, transformContextualCallExpression } from "./call";
@@ -88,17 +88,14 @@ export const transformTaggedTemplateExpression: FunctionVisitor<ts.TaggedTemplat
8888
expressions.unshift(stringObject);
8989

9090
// Evaluate if there is a self parameter to be used.
91-
const signature = context.checker.getResolvedSignature(expression);
92-
const signatureDeclaration = signature?.getDeclaration();
93-
const useSelfParameter =
94-
signatureDeclaration && getDeclarationContextType(context, signatureDeclaration) !== ContextType.Void;
91+
const useSelfParameter = getCallContextType(context, expression) !== ContextType.Void;
9592

9693
if (useSelfParameter) {
97-
return transformContextualCallExpression(context, expression, expressions, signature);
94+
return transformContextualCallExpression(context, expression, expressions);
9895
}
9996

10097
// Argument evaluation.
101-
const callArguments = transformArguments(context, expressions, signature);
98+
const callArguments = transformArguments(context, expressions);
10299

103100
const leftHandSideExpression = context.transformExpression(expression.tag);
104101
return lua.createCallExpression(leftHandSideExpression, callArguments);

test/unit/functions/noSelfAnnotation.spec.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,3 +62,58 @@ test("explicit this parameter respected over @noSelf", () => {
6262
export const result = foo(1);
6363
`.expectToMatchJsResult();
6464
});
65+
66+
test("respect noSelfInFile over noImplicitSelf", () => {
67+
const result = util.testModule`
68+
/** @noSelfInFile **/
69+
const func: Function = () => 1;
70+
export const result = func(1);
71+
`
72+
.expectToMatchJsResult()
73+
.getLuaResult();
74+
75+
expect(result.transpiledFiles).not.toHaveLength(0);
76+
77+
const mainFile = result.transpiledFiles.find(f => f.outPath === "main.lua");
78+
expect(mainFile).toBeDefined();
79+
80+
// avoid ts error "not defined", even though toBeDefined is being checked above
81+
if (!mainFile) return;
82+
83+
expect(mainFile.lua).toBeDefined();
84+
expect(mainFile.lua).toContain("func(1)");
85+
});
86+
87+
test("respect noSelfInFile over noImplicitSelf (func declared in other file)", () => {
88+
const result = util.testModule`
89+
import { func, result } from "./functions";
90+
91+
export const result1 = result;
92+
export const result2 = func(1);
93+
`
94+
.addExtraFile(
95+
"functions.ts",
96+
`
97+
/** @noSelfInFile **/
98+
export const func: Function = () => 1;
99+
export const result = func(2);
100+
`
101+
)
102+
.expectToMatchJsResult()
103+
.getLuaResult();
104+
105+
expect(result.transpiledFiles).not.toHaveLength(0);
106+
107+
const mainFile = result.transpiledFiles.find(f => f.outPath.includes("main.lua"));
108+
expect(mainFile).toBeDefined();
109+
const functionFile = result.transpiledFiles.find(f => f.outPath.includes("functions.lua"));
110+
expect(functionFile).toBeDefined();
111+
112+
// avoid ts error "not defined", even though toBeDefined is being checked above
113+
if (!mainFile || !functionFile) return;
114+
115+
expect(mainFile.lua).toBeDefined();
116+
expect(mainFile.lua).toContain("func(1)");
117+
expect(mainFile.lua).toBeDefined();
118+
expect(functionFile.lua).toContain("func(2)");
119+
});

0 commit comments

Comments
 (0)