Skip to content

Commit 781bccf

Browse files
hazzard993Perryvw
authored andcommitted
Constrained generic fixes (#668)
* Fix generic array constrained detection * Fix generic string constrained detection * Remove unnecessary conditional change * Add more generic array tests and check potential generic array type first
1 parent c0bf63d commit 781bccf

File tree

4 files changed

+70
-5
lines changed

4 files changed

+70
-5
lines changed

src/LuaTransformer.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2757,7 +2757,7 @@ export class LuaTransformer {
27572757
}
27582758

27592759
const type = this.checker.getTypeAtLocation(statement.expression);
2760-
if (tsHelper.isStringType(type)) {
2760+
if (tsHelper.isStringType(type, this.checker, this.program)) {
27612761
const error = tstl.createIdentifier("error");
27622762
return tstl.createExpressionStatement(
27632763
tstl.createCallExpression(error, [this.transformExpression(statement.expression)]),
@@ -3289,7 +3289,10 @@ export class LuaTransformer {
32893289
// Check is we need to use string concat operator
32903290
const typeLeft = this.checker.getTypeAtLocation(node.left);
32913291
const typeRight = this.checker.getTypeAtLocation(node.right);
3292-
if (tsHelper.isStringType(typeLeft) || tsHelper.isStringType(typeRight)) {
3292+
if (
3293+
tsHelper.isStringType(typeLeft, this.checker, this.program) ||
3294+
tsHelper.isStringType(typeRight, this.checker, this.program)
3295+
) {
32933296
return tstl.SyntaxKind.ConcatOperator;
32943297
}
32953298
}
@@ -4084,7 +4087,7 @@ export class LuaTransformer {
40844087

40854088
// Check for primitive types to override
40864089
const type = this.checker.getTypeAtLocation(expression.expression);
4087-
if (tsHelper.isStringType(type)) {
4090+
if (tsHelper.isStringType(type, this.checker, this.program)) {
40884091
return this.transformStringProperty(expression);
40894092
} else if (tsHelper.isArrayType(type, this.checker, this.program)) {
40904093
const arrayPropertyAccess = this.transformArrayProperty(expression);
@@ -4291,7 +4294,7 @@ export class LuaTransformer {
42914294

42924295
const argumentType = this.checker.getTypeAtLocation(expression.argumentExpression);
42934296
const type = this.checker.getTypeAtLocation(expression.expression);
4294-
if (tsHelper.isNumberType(argumentType) && tsHelper.isStringType(type)) {
4297+
if (tsHelper.isNumberType(argumentType) && tsHelper.isStringType(type, this.checker, this.program)) {
42954298
const index = this.transformExpression(expression.argumentExpression);
42964299
return tstl.createCallExpression(
42974300
tstl.createTableIndexExpression(tstl.createIdentifier("string"), tstl.createStringLiteral("sub")),

src/TSHelper.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,14 @@ export function isStaticNode(node: ts.Node): boolean {
132132
return node.modifiers !== undefined && node.modifiers.some(m => m.kind === ts.SyntaxKind.StaticKeyword);
133133
}
134134

135-
export function isStringType(type: ts.Type): boolean {
135+
export function isStringType(type: ts.Type, checker: ts.TypeChecker, program: ts.Program): boolean {
136+
if (type.symbol) {
137+
const baseConstraint = checker.getBaseConstraintOfType(type);
138+
if (baseConstraint) {
139+
return isStringType(baseConstraint, checker, program);
140+
}
141+
}
142+
136143
return (
137144
(type.flags & ts.TypeFlags.String) !== 0 ||
138145
(type.flags & ts.TypeFlags.StringLike) !== 0 ||
@@ -149,6 +156,13 @@ export function isNumberType(type: ts.Type): boolean {
149156
}
150157

151158
export function isExplicitArrayType(type: ts.Type, checker: ts.TypeChecker, program: ts.Program): boolean {
159+
if (type.symbol) {
160+
const baseConstraint = checker.getBaseConstraintOfType(type);
161+
if (baseConstraint) {
162+
return isExplicitArrayType(baseConstraint, checker, program);
163+
}
164+
}
165+
152166
if (type.isUnionOrIntersection()) {
153167
return type.types.some(t => isExplicitArrayType(t, checker, program));
154168
}

test/unit/array.spec.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,3 +226,38 @@ test.each([`["foo", "bar"].length`, `["foo", "bar"][0]`, `[() => "foo", () => "b
226226
expect(util.transpileAndExecute(code)).toBe(expectResult);
227227
}
228228
);
229+
230+
const genericChecks = [
231+
"function generic<T extends number[]>(array: T)",
232+
"function generic<T extends [...number[]]>(array: T)",
233+
"function generic<T extends any>(array: T[])",
234+
"type ArrayType = number[]; function generic<T extends ArrayType>(array: T)",
235+
"function generic<T extends number[]>(array: T & {})",
236+
"function generic<T extends number[] & {}>(array: T)",
237+
];
238+
239+
test.each(genericChecks)("array constrained generic foreach (%p)", signature => {
240+
const code = `
241+
${signature}: number {
242+
let sum = 0;
243+
array.forEach(item => {
244+
if (typeof item === "number") {
245+
sum += item;
246+
}
247+
});
248+
return sum;
249+
}
250+
return generic([1, 2, 3]);
251+
`;
252+
expect(util.transpileAndExecute(code)).toBe(6);
253+
});
254+
255+
test.each(genericChecks)("array constrained generic length (%p)", signature => {
256+
const code = `
257+
${signature}: number {
258+
return array.length;
259+
}
260+
return generic([1, 2, 3]);
261+
`;
262+
expect(util.transpileAndExecute(code)).toBe(3);
263+
});

test/unit/string.spec.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -348,3 +348,16 @@ test.each([`"foobar".length`, `"foobar".repeat(2)`, "`foo${'bar'}`.length", "`fo
348348
expect(util.transpileAndExecute(code)).toBe(expectResult);
349349
}
350350
);
351+
352+
test.each([
353+
"function generic<T extends string>(string: T)",
354+
"type StringType = string; function generic<T extends StringType>(string: T)",
355+
])("string constrained generic foreach (%p)", signature => {
356+
const code = `
357+
${signature}: number {
358+
return string.length;
359+
}
360+
return generic("string");
361+
`;
362+
expect(util.transpileAndExecute(code)).toBe(6);
363+
});

0 commit comments

Comments
 (0)