Skip to content

Commit 0158732

Browse files
tomblindPerryvw
andauthored
Operator Mapping Library Extensions (#969)
* initial setup of add operator mapping * basic add operator mappings working, including tests and invalid use checks * overload tests for operator mapping * added unary operator map for len and updated tests * added missing operator extensions * added checks for unsupported operators in pre-53 lua versions * reverting package-lock.json to correct version * tabs to spaces * fixed snapshot * addressed feedback * Update src/transformation/utils/diagnostics.ts Co-authored-by: Perry van Wesel <Perryvw@users.noreply.github.com> * snapshot update Co-authored-by: Tom <tomblind@users.noreply.github.com> Co-authored-by: Perry van Wesel <Perryvw@users.noreply.github.com>
1 parent 948f71b commit 0158732

File tree

8 files changed

+1200
-3
lines changed

8 files changed

+1200
-3
lines changed

language-extensions/index.d.ts

Lines changed: 402 additions & 0 deletions
Large diffs are not rendered by default.

src/transformation/utils/diagnostics.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,10 @@ export const unsupportedMultiFunctionAssignment = createErrorDiagnosticFactory(
172172
"Omitted expressions and BindingElements are expected here."
173173
);
174174

175+
export const invalidOperatorMappingUse = createErrorDiagnosticFactory(
176+
"This function must always be directly called and cannot be referred to."
177+
);
178+
175179
export const annotationDeprecated = createWarningDiagnosticFactory(
176180
(kind: AnnotationKind) =>
177181
`'@${kind}' is deprecated and will be removed in a future update. Please update your code before upgrading to the next release, otherwise your project will no longer compile. ` +

src/transformation/utils/language-extensions.ts

Lines changed: 81 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,84 @@ import * as path from "path";
44
export enum ExtensionKind {
55
MultiFunction = "MultiFunction",
66
MultiType = "MultiType",
7+
AdditionOperatorType = "AdditionOperatorType",
8+
AdditionOperatorMethodType = "AdditionOperatorMethodType",
9+
SubtractionOperatorType = "SubtractionOperatorType",
10+
SubtractionOperatorMethodType = "SubtractionOperatorMethodType",
11+
MultiplicationOperatorType = "MultiplicationOperatorType",
12+
MultiplicationOperatorMethodType = "MultiplicationOperatorMethodType",
13+
DivisionOperatorType = "DivisionOperatorType",
14+
DivisionOperatorMethodType = "DivisionOperatorMethodType",
15+
ModuloOperatorType = "ModuloOperatorType",
16+
ModuloOperatorMethodType = "ModuloOperatorMethodType",
17+
PowerOperatorType = "PowerOperatorType",
18+
PowerOperatorMethodType = "PowerOperatorMethodType",
19+
FloorDivisionOperatorType = "FloorDivisionOperatorType",
20+
FloorDivisionOperatorMethodType = "FloorDivisionOperatorMethodType",
21+
BitwiseAndOperatorType = "BitwiseAndOperatorType",
22+
BitwiseAndOperatorMethodType = "BitwiseAndOperatorMethodType",
23+
BitwiseOrOperatorType = "BitwiseOrOperatorType",
24+
BitwiseOrOperatorMethodType = "BitwiseOrOperatorMethodType",
25+
BitwiseExclusiveOrOperatorType = "BitwiseExclusiveOrOperatorType",
26+
BitwiseExclusiveOrOperatorMethodType = "BitwiseExclusiveOrOperatorMethodType",
27+
BitwiseLeftShiftOperatorType = "BitwiseLeftShiftOperatorType",
28+
BitwiseLeftShiftOperatorMethodType = "BitwiseLeftShiftOperatorMethodType",
29+
BitwiseRightShiftOperatorType = "BitwiseRightShiftOperatorType",
30+
BitwiseRightShiftOperatorMethodType = "BitwiseRightShiftOperatorMethodType",
31+
ConcatOperatorType = "ConcatOperatorType",
32+
ConcatOperatorMethodType = "ConcatOperatorMethodType",
33+
LessThanOperatorType = "LessThanOperatorType",
34+
LessThanOperatorMethodType = "LessThanOperatorMethodType",
35+
GreaterThanOperatorType = "GreaterThanOperatorType",
36+
GreaterThanOperatorMethodType = "GreaterThanOperatorMethodType",
37+
NegationOperatorType = "NegationOperatorType",
38+
NegationOperatorMethodType = "NegationOperatorMethodType",
39+
BitwiseNotOperatorType = "BitwiseNotOperatorType",
40+
BitwiseNotOperatorMethodType = "BitwiseNotOperatorMethodType",
41+
LengthOperatorType = "LengthOperatorType",
42+
LengthOperatorMethodType = "LengthOperatorMethodType",
743
}
844

45+
const typeNameToExtensionKind: { [name: string]: ExtensionKind } = {
46+
MultiReturn: ExtensionKind.MultiType,
47+
LuaAddition: ExtensionKind.AdditionOperatorType,
48+
LuaAdditionMethod: ExtensionKind.AdditionOperatorMethodType,
49+
LuaSubtraction: ExtensionKind.SubtractionOperatorType,
50+
LuaSubtractionMethod: ExtensionKind.SubtractionOperatorMethodType,
51+
LuaMultiplication: ExtensionKind.MultiplicationOperatorType,
52+
LuaMultiplicationMethod: ExtensionKind.MultiplicationOperatorMethodType,
53+
LuaDivision: ExtensionKind.DivisionOperatorType,
54+
LuaDivisionMethod: ExtensionKind.DivisionOperatorMethodType,
55+
LuaModulo: ExtensionKind.ModuloOperatorType,
56+
LuaModuloMethod: ExtensionKind.ModuloOperatorMethodType,
57+
LuaPower: ExtensionKind.PowerOperatorType,
58+
LuaPowerMethod: ExtensionKind.PowerOperatorMethodType,
59+
LuaFloorDivision: ExtensionKind.FloorDivisionOperatorType,
60+
LuaFloorDivisionMethod: ExtensionKind.FloorDivisionOperatorMethodType,
61+
LuaBitwiseAnd: ExtensionKind.BitwiseAndOperatorType,
62+
LuaBitwiseAndMethod: ExtensionKind.BitwiseAndOperatorMethodType,
63+
LuaBitwiseOr: ExtensionKind.BitwiseOrOperatorType,
64+
LuaBitwiseOrMethod: ExtensionKind.BitwiseOrOperatorMethodType,
65+
LuaBitwiseExclusiveOr: ExtensionKind.BitwiseExclusiveOrOperatorType,
66+
LuaBitwiseExclusiveOrMethod: ExtensionKind.BitwiseExclusiveOrOperatorMethodType,
67+
LuaBitwiseLeftShift: ExtensionKind.BitwiseLeftShiftOperatorType,
68+
LuaBitwiseLeftShiftMethod: ExtensionKind.BitwiseLeftShiftOperatorMethodType,
69+
LuaBitwiseRightShift: ExtensionKind.BitwiseRightShiftOperatorType,
70+
LuaBitwiseRightShiftMethod: ExtensionKind.BitwiseRightShiftOperatorMethodType,
71+
LuaConcat: ExtensionKind.ConcatOperatorType,
72+
LuaConcatMethod: ExtensionKind.ConcatOperatorMethodType,
73+
LuaLessThan: ExtensionKind.LessThanOperatorType,
74+
LuaLessThanMethod: ExtensionKind.LessThanOperatorMethodType,
75+
LuaGreaterThan: ExtensionKind.GreaterThanOperatorType,
76+
LuaGreaterThanMethod: ExtensionKind.GreaterThanOperatorMethodType,
77+
LuaNegation: ExtensionKind.NegationOperatorType,
78+
LuaNegationMethod: ExtensionKind.NegationOperatorMethodType,
79+
LuaBitwiseNot: ExtensionKind.BitwiseNotOperatorType,
80+
LuaBitwiseNotMethod: ExtensionKind.BitwiseNotOperatorMethodType,
81+
LuaLength: ExtensionKind.LengthOperatorType,
82+
LuaLengthMethod: ExtensionKind.LengthOperatorMethodType,
83+
};
84+
985
function isSourceFileFromLanguageExtensions(sourceFile: ts.SourceFile): boolean {
1086
const extensionDirectory = path.resolve(__dirname, "../../../language-extensions");
1187
const sourceFileDirectory = path.dirname(path.normalize(sourceFile.fileName));
@@ -19,8 +95,11 @@ export function getExtensionKind(declaration: ts.Declaration): ExtensionKind | u
1995
return ExtensionKind.MultiFunction;
2096
}
2197

22-
if (ts.isTypeAliasDeclaration(declaration) && declaration.name.text === "MultiReturn") {
23-
return ExtensionKind.MultiType;
98+
if (ts.isTypeAliasDeclaration(declaration)) {
99+
const extensionKind = typeNameToExtensionKind[declaration.name.text];
100+
if (extensionKind) {
101+
return extensionKind;
102+
}
24103
}
25104

26105
throw new Error("Unknown extension kind");

src/transformation/visitors/call.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { isArrayType, isExpressionWithEvaluationEffect, isInDestructingAssignmen
1212
import { transformElementAccessArgument } from "./access";
1313
import { transformLuaTableCallExpression } from "./lua-table";
1414
import { returnsMultiType } from "./language-extensions/multi";
15+
import { isOperatorMapping, transformOperatorMappingExpression } from "./language-extensions/operators";
1516

1617
export type PropertyCallExpression = ts.CallExpression & { expression: ts.PropertyAccessExpression };
1718

@@ -208,6 +209,10 @@ export const transformCallExpression: FunctionVisitor<ts.CallExpression> = (node
208209
return wrapResult ? wrapInTable(builtinResult) : builtinResult;
209210
}
210211

212+
if (isOperatorMapping(context, node)) {
213+
return transformOperatorMappingExpression(context, node);
214+
}
215+
211216
if (ts.isPropertyAccessExpression(node.expression)) {
212217
const result = transformPropertyCall(context, node as PropertyCallExpression);
213218
return wrapResult ? wrapInTable(result) : result;

src/transformation/visitors/identifier.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,24 @@ import * as lua from "../../LuaAST";
33
import { transformBuiltinIdentifierExpression } from "../builtins";
44
import { FunctionVisitor, TransformationContext } from "../context";
55
import { isForRangeType } from "../utils/annotations";
6-
import { invalidForRangeCall, invalidMultiFunctionUse } from "../utils/diagnostics";
6+
import { invalidForRangeCall, invalidMultiFunctionUse, invalidOperatorMappingUse } from "../utils/diagnostics";
77
import { createExportedIdentifier, getSymbolExportScope } from "../utils/export";
88
import { createSafeName, hasUnsafeIdentifierName } from "../utils/safe-names";
99
import { getIdentifierSymbolId } from "../utils/symbols";
1010
import { findFirstNodeAbove } from "../utils/typescript";
1111
import { isMultiFunctionNode } from "./language-extensions/multi";
12+
import { isOperatorMapping } from "./language-extensions/operators";
1213

1314
export function transformIdentifier(context: TransformationContext, identifier: ts.Identifier): lua.Identifier {
1415
if (isMultiFunctionNode(context, identifier)) {
1516
context.diagnostics.push(invalidMultiFunctionUse(identifier));
1617
return lua.createAnonymousIdentifier(identifier);
1718
}
1819

20+
if (isOperatorMapping(context, identifier)) {
21+
context.diagnostics.push(invalidOperatorMappingUse(identifier));
22+
}
23+
1924
if (isForRangeType(context, identifier)) {
2025
const callExpression = findFirstNodeAbove(identifier, ts.isCallExpression);
2126
if (!callExpression || !callExpression.parent || !ts.isForOfStatement(callExpression.parent)) {
Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
import * as ts from "typescript";
2+
import * as lua from "../../../LuaAST";
3+
import { TransformationContext } from "../../context";
4+
import * as extensions from "../../utils/language-extensions";
5+
import { assert } from "../../../utils";
6+
import { findFirstNodeAbove } from "../../utils/typescript";
7+
import { LuaTarget } from "../../../CompilerOptions";
8+
import { unsupportedForTarget } from "../../utils/diagnostics";
9+
10+
const binaryOperatorMappings = new Map<extensions.ExtensionKind, lua.BinaryOperator>([
11+
[extensions.ExtensionKind.AdditionOperatorType, lua.SyntaxKind.AdditionOperator],
12+
[extensions.ExtensionKind.AdditionOperatorMethodType, lua.SyntaxKind.AdditionOperator],
13+
[extensions.ExtensionKind.SubtractionOperatorType, lua.SyntaxKind.SubtractionOperator],
14+
[extensions.ExtensionKind.SubtractionOperatorMethodType, lua.SyntaxKind.SubtractionOperator],
15+
[extensions.ExtensionKind.MultiplicationOperatorType, lua.SyntaxKind.MultiplicationOperator],
16+
[extensions.ExtensionKind.MultiplicationOperatorMethodType, lua.SyntaxKind.MultiplicationOperator],
17+
[extensions.ExtensionKind.DivisionOperatorType, lua.SyntaxKind.DivisionOperator],
18+
[extensions.ExtensionKind.DivisionOperatorMethodType, lua.SyntaxKind.DivisionOperator],
19+
[extensions.ExtensionKind.ModuloOperatorType, lua.SyntaxKind.ModuloOperator],
20+
[extensions.ExtensionKind.ModuloOperatorMethodType, lua.SyntaxKind.ModuloOperator],
21+
[extensions.ExtensionKind.PowerOperatorType, lua.SyntaxKind.PowerOperator],
22+
[extensions.ExtensionKind.PowerOperatorMethodType, lua.SyntaxKind.PowerOperator],
23+
[extensions.ExtensionKind.FloorDivisionOperatorType, lua.SyntaxKind.FloorDivisionOperator],
24+
[extensions.ExtensionKind.FloorDivisionOperatorMethodType, lua.SyntaxKind.FloorDivisionOperator],
25+
[extensions.ExtensionKind.BitwiseAndOperatorType, lua.SyntaxKind.BitwiseAndOperator],
26+
[extensions.ExtensionKind.BitwiseAndOperatorMethodType, lua.SyntaxKind.BitwiseAndOperator],
27+
[extensions.ExtensionKind.BitwiseOrOperatorType, lua.SyntaxKind.BitwiseOrOperator],
28+
[extensions.ExtensionKind.BitwiseOrOperatorMethodType, lua.SyntaxKind.BitwiseOrOperator],
29+
[extensions.ExtensionKind.BitwiseExclusiveOrOperatorType, lua.SyntaxKind.BitwiseExclusiveOrOperator],
30+
[extensions.ExtensionKind.BitwiseExclusiveOrOperatorMethodType, lua.SyntaxKind.BitwiseExclusiveOrOperator],
31+
[extensions.ExtensionKind.BitwiseLeftShiftOperatorType, lua.SyntaxKind.BitwiseLeftShiftOperator],
32+
[extensions.ExtensionKind.BitwiseLeftShiftOperatorMethodType, lua.SyntaxKind.BitwiseLeftShiftOperator],
33+
[extensions.ExtensionKind.BitwiseRightShiftOperatorType, lua.SyntaxKind.BitwiseRightShiftOperator],
34+
[extensions.ExtensionKind.BitwiseRightShiftOperatorMethodType, lua.SyntaxKind.BitwiseRightShiftOperator],
35+
[extensions.ExtensionKind.ConcatOperatorType, lua.SyntaxKind.ConcatOperator],
36+
[extensions.ExtensionKind.ConcatOperatorMethodType, lua.SyntaxKind.ConcatOperator],
37+
[extensions.ExtensionKind.LessThanOperatorType, lua.SyntaxKind.LessThanOperator],
38+
[extensions.ExtensionKind.LessThanOperatorMethodType, lua.SyntaxKind.LessThanOperator],
39+
[extensions.ExtensionKind.GreaterThanOperatorType, lua.SyntaxKind.GreaterThanOperator],
40+
[extensions.ExtensionKind.GreaterThanOperatorMethodType, lua.SyntaxKind.GreaterThanOperator],
41+
]);
42+
43+
const unaryOperatorMappings = new Map<extensions.ExtensionKind, lua.UnaryOperator>([
44+
[extensions.ExtensionKind.NegationOperatorType, lua.SyntaxKind.NegationOperator],
45+
[extensions.ExtensionKind.NegationOperatorMethodType, lua.SyntaxKind.NegationOperator],
46+
[extensions.ExtensionKind.BitwiseNotOperatorType, lua.SyntaxKind.BitwiseNotOperator],
47+
[extensions.ExtensionKind.BitwiseNotOperatorMethodType, lua.SyntaxKind.BitwiseNotOperator],
48+
[extensions.ExtensionKind.LengthOperatorType, lua.SyntaxKind.LengthOperator],
49+
[extensions.ExtensionKind.LengthOperatorMethodType, lua.SyntaxKind.LengthOperator],
50+
]);
51+
52+
const operatorMapExtensions = new Set<extensions.ExtensionKind>([
53+
...binaryOperatorMappings.keys(),
54+
...unaryOperatorMappings.keys(),
55+
]);
56+
57+
const bitwiseOperatorMapExtensions = new Set<extensions.ExtensionKind>([
58+
extensions.ExtensionKind.BitwiseAndOperatorType,
59+
extensions.ExtensionKind.BitwiseAndOperatorMethodType,
60+
extensions.ExtensionKind.BitwiseOrOperatorType,
61+
extensions.ExtensionKind.BitwiseOrOperatorMethodType,
62+
extensions.ExtensionKind.BitwiseExclusiveOrOperatorType,
63+
extensions.ExtensionKind.BitwiseExclusiveOrOperatorMethodType,
64+
extensions.ExtensionKind.BitwiseLeftShiftOperatorType,
65+
extensions.ExtensionKind.BitwiseLeftShiftOperatorMethodType,
66+
extensions.ExtensionKind.BitwiseRightShiftOperatorType,
67+
extensions.ExtensionKind.BitwiseRightShiftOperatorMethodType,
68+
extensions.ExtensionKind.BitwiseNotOperatorType,
69+
extensions.ExtensionKind.BitwiseNotOperatorMethodType,
70+
]);
71+
72+
function getTypeDeclaration(declaration: ts.Declaration) {
73+
return ts.isTypeAliasDeclaration(declaration)
74+
? declaration
75+
: findFirstNodeAbove(declaration, ts.isTypeAliasDeclaration);
76+
}
77+
78+
function getOperatorMapExtensionKindForCall(context: TransformationContext, node: ts.CallExpression) {
79+
const signature = context.checker.getResolvedSignature(node);
80+
if (!signature || !signature.declaration) {
81+
return;
82+
}
83+
const typeDeclaration = getTypeDeclaration(signature.declaration);
84+
if (!typeDeclaration) {
85+
return;
86+
}
87+
const mapping = extensions.getExtensionKind(typeDeclaration);
88+
if (mapping !== undefined && operatorMapExtensions.has(mapping)) {
89+
return mapping;
90+
}
91+
}
92+
93+
function isOperatorMapDeclaration(declaration: ts.Declaration) {
94+
const typeDeclaration = getTypeDeclaration(declaration);
95+
if (typeDeclaration) {
96+
const extensionKind = extensions.getExtensionKind(typeDeclaration);
97+
return extensionKind !== undefined ? operatorMapExtensions.has(extensionKind) : false;
98+
}
99+
}
100+
101+
function isOperatorMapType(context: TransformationContext, type: ts.Type): boolean {
102+
if (type.isUnionOrIntersection()) {
103+
return type.types.some(t => isOperatorMapType(context, t));
104+
} else {
105+
return type.symbol?.declarations?.some(isOperatorMapDeclaration);
106+
}
107+
}
108+
109+
function isOperatorMapIdentifier(context: TransformationContext, node: ts.Identifier) {
110+
const type = context.checker.getTypeAtLocation(node);
111+
return isOperatorMapType(context, type);
112+
}
113+
114+
export function isOperatorMapping(context: TransformationContext, node: ts.CallExpression | ts.Identifier) {
115+
if (ts.isCallExpression(node)) {
116+
return getOperatorMapExtensionKindForCall(context, node) !== undefined;
117+
} else {
118+
return isOperatorMapIdentifier(context, node);
119+
}
120+
}
121+
122+
export function transformOperatorMappingExpression(
123+
context: TransformationContext,
124+
node: ts.CallExpression
125+
): lua.Expression {
126+
const extensionKind = getOperatorMapExtensionKindForCall(context, node);
127+
assert(extensionKind);
128+
129+
const isBefore53 =
130+
context.luaTarget === LuaTarget.Lua51 ||
131+
context.luaTarget === LuaTarget.Lua52 ||
132+
context.luaTarget === LuaTarget.LuaJIT ||
133+
context.luaTarget === LuaTarget.Universal;
134+
if (isBefore53) {
135+
const luaTarget = context.luaTarget === LuaTarget.Universal ? LuaTarget.Lua51 : context.luaTarget;
136+
if (bitwiseOperatorMapExtensions.has(extensionKind)) {
137+
context.diagnostics.push(unsupportedForTarget(node, "Native bitwise operations", luaTarget));
138+
} else if (
139+
extensionKind === extensions.ExtensionKind.FloorDivisionOperatorType ||
140+
extensionKind === extensions.ExtensionKind.FloorDivisionOperatorMethodType
141+
) {
142+
context.diagnostics.push(unsupportedForTarget(node, "Floor division operator", luaTarget));
143+
}
144+
}
145+
146+
const args = node.arguments.slice();
147+
if (binaryOperatorMappings.has(extensionKind)) {
148+
if (
149+
args.length === 1 &&
150+
(ts.isPropertyAccessExpression(node.expression) || ts.isElementAccessExpression(node.expression))
151+
) {
152+
args.unshift(node.expression.expression);
153+
}
154+
155+
const luaOperator = binaryOperatorMappings.get(extensionKind);
156+
assert(luaOperator);
157+
return lua.createBinaryExpression(
158+
context.transformExpression(args[0]),
159+
context.transformExpression(args[1]),
160+
luaOperator
161+
);
162+
} else {
163+
let arg: ts.Expression;
164+
if (
165+
args.length === 0 &&
166+
(ts.isPropertyAccessExpression(node.expression) || ts.isElementAccessExpression(node.expression))
167+
) {
168+
arg = node.expression.expression;
169+
} else {
170+
arg = args[0];
171+
}
172+
173+
const luaOperator = unaryOperatorMappings.get(extensionKind);
174+
assert(luaOperator);
175+
return lua.createUnaryExpression(context.transformExpression(arg), luaOperator);
176+
}
177+
}

0 commit comments

Comments
 (0)