Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 37 additions & 6 deletions src/codegen/types/collections/array/search-predicate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -547,18 +547,43 @@ function generateStringArrayEvery(
// includes
// ============================================

function resolveIncludesFromIndex(
gen: IGeneratorContext,
fromIndex: string,
length: string,
): string {
const isNeg = gen.emitIcmp("slt", "i32", fromIndex, "0");
const adjusted = gen.nextTemp();
gen.emit(`${adjusted} = add i32 ${fromIndex}, ${length}`);
const resolved = gen.nextTemp();
gen.emit(`${resolved} = select i1 ${isNeg}, i32 ${adjusted}, i32 ${fromIndex}`);
const stillNeg = gen.emitIcmp("slt", "i32", resolved, "0");
const clamped = gen.nextTemp();
gen.emit(`${clamped} = select i1 ${stillNeg}, i32 0, i32 ${resolved}`);
return clamped;
}

export function generateArrayIncludes(
gen: IGeneratorContext,
expr: MethodCallNode,
params: string[],
): string {
if (expr.args.length !== 1) {
return gen.emitError("includes() requires exactly 1 argument", expr.loc);
if (expr.args.length < 1 || expr.args.length > 2) {
return gen.emitError("includes() requires 1 or 2 arguments", expr.loc);
}

const arrayPtr = gen.generateExpression(expr.object, params);
const searchValue = gen.generateExpression(expr.args[0], params);

let fromIndex: string | null = null;
if (expr.args.length === 2) {
const fromRaw = gen.generateExpression(expr.args[1], params);
const fromDbl = gen.ensureDouble(fromRaw);
const tmp = gen.nextTemp();
gen.emit(`${tmp} = fptosi double ${fromDbl} to i32`);
fromIndex = tmp;
}

let isStringArray = false;
const exprObjBase = expr.object as ExprBase;
if (exprObjBase.type === "variable") {
Expand All @@ -571,16 +596,17 @@ export function generateArrayIncludes(
}

if (isStringArray) {
return generateStringArrayIncludes(gen, arrayPtr, searchValue);
return generateStringArrayIncludes(gen, arrayPtr, searchValue, fromIndex);
} else {
return generateIntArrayIncludes(gen, arrayPtr, searchValue);
return generateIntArrayIncludes(gen, arrayPtr, searchValue, fromIndex);
}
}

function generateIntArrayIncludes(
gen: IGeneratorContext,
arrayPtr: string,
searchValue: string,
fromIndex: string | null,
): string {
const lenPtr = gen.nextTemp();
gen.emit(`${lenPtr} = getelementptr inbounds %Array, %Array* ${arrayPtr}, i32 0, i32 1`);
Expand All @@ -591,6 +617,8 @@ function generateIntArrayIncludes(
const dataPtr = gen.nextTemp();
gen.emit(`${dataPtr} = load double*, double** ${dataPtrField}`);

const startIndex = fromIndex ? resolveIncludesFromIndex(gen, fromIndex, length) : "0";

const loopLabel = gen.nextLabel("includes_loop");
const checkLabel = gen.nextLabel("includes_check");
const bodyLabel = gen.nextLabel("includes_body");
Expand All @@ -599,7 +627,7 @@ function generateIntArrayIncludes(

const counterPtr = gen.nextTemp();
gen.emit(`${counterPtr} = alloca i32`);
gen.emitStore("i32", "0", counterPtr);
gen.emitStore("i32", startIndex, counterPtr);

gen.emitBr(checkLabel);

Expand Down Expand Up @@ -641,6 +669,7 @@ function generateStringArrayIncludes(
gen: IGeneratorContext,
arrayPtr: string,
searchValue: string,
fromIndex: string | null,
): string {
const lenPtr = gen.nextTemp();
gen.emit(
Expand All @@ -655,6 +684,8 @@ function generateStringArrayIncludes(
const dataPtr = gen.nextTemp();
gen.emit(`${dataPtr} = load i8**, i8*** ${dataPtrField}`);

const startIndex = fromIndex ? resolveIncludesFromIndex(gen, fromIndex, length) : "0";

const loopLabel = gen.nextLabel("includes_loop");
const checkLabel = gen.nextLabel("includes_check");
const bodyLabel = gen.nextLabel("includes_body");
Expand All @@ -663,7 +694,7 @@ function generateStringArrayIncludes(

const counterPtr = gen.nextTemp();
gen.emit(`${counterPtr} = alloca i32`);
gen.emitStore("i32", "0", counterPtr);
gen.emitStore("i32", startIndex, counterPtr);

gen.emitBr(checkLabel);

Expand Down
60 changes: 60 additions & 0 deletions tests/fixtures/arrays/array-includes-fromindex.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
const nums: number[] = [10, 20, 30, 40, 50];

if (!nums.includes(30, 0)) {
console.log("FAIL: includes 30 from 0");
process.exit(1);
}

if (!nums.includes(30, 2)) {
console.log("FAIL: includes 30 from 2");
process.exit(1);
}

if (nums.includes(30, 3)) {
console.log("FAIL: includes 30 from 3 should be false");
process.exit(1);
}

if (nums.includes(10, 1)) {
console.log("FAIL: includes 10 from 1 should be false");
process.exit(1);
}

if (!nums.includes(50, -1)) {
console.log("FAIL: includes 50 from -1 should search last element");
process.exit(1);
}

if (nums.includes(10, -1)) {
console.log("FAIL: includes 10 from -1 should not find first element");
process.exit(1);
}

if (!nums.includes(10, -10)) {
console.log("FAIL: includes 10 from -10 should clamp to 0");
process.exit(1);
}

if (nums.includes(30, 100)) {
console.log("FAIL: includes from past end should be false");
process.exit(1);
}

const strs: string[] = ["hello", "world", "foo"];

if (!strs.includes("world", 0)) {
console.log("FAIL: string includes world from 0");
process.exit(1);
}

if (strs.includes("hello", 1)) {
console.log("FAIL: string includes hello from 1 should be false");
process.exit(1);
}

if (!strs.includes("foo", -1)) {
console.log("FAIL: string includes foo from -1");
process.exit(1);
}

console.log("TEST_PASSED");
Loading