mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-10-02 08:35:07 +08:00
Merge current main into Effect static compatibility branch
- Reconcile string search lowering with the latest compiler behavior. - Keep corpus numbering unique and regenerate the order baseline.
This commit is contained in:
@@ -116,6 +116,9 @@ Prebuilds release runtime objects and native TLS/dynamic-engine archives for tar
|
||||
<dt><code>--backend <c|llvm></code></dt>
|
||||
<dd>Code generator: <code>llvm</code> (default) or <code>c</code> (the readable debugging backend). On supported targets, LLVM-tier executable code generation uses the bundled helper and precompiled runtime pack before the platform link. Unset, a native build can fall back to C when the program is outside the LLVM tier. The production <code>wasm32-wasi</code> target never falls back: a missing LLVM lowering is <code>SC3001</code>.</dd>
|
||||
|
||||
<dt><code>--strip</code></dt>
|
||||
<dd>Remove symbol and debug payload when linking an executable, including cross-compiled targets. This opt-in flag works with either backend and <code>--from-c</code> without changing code optimization.</dd>
|
||||
|
||||
<dt><code>--windows-subsystem <console|gui></code></dt>
|
||||
<dd>Select the subsystem of a Windows executable. <code>console</code> is the default; <code>gui</code> prevents Windows from opening a console window for the app. Valid only for executable builds targeting Windows, including <code>--from-c</code>.</dd>
|
||||
|
||||
|
||||
@@ -128,6 +128,7 @@ async function tryFastPath(): Promise<number | null> {
|
||||
dynamic: values.dynamic,
|
||||
backend: backend ?? "auto",
|
||||
...(optimization === "dev" ? { optimization: "dev" as const } : {}),
|
||||
...(values.strip ? { strip: true as const } : {}),
|
||||
npmStatic,
|
||||
ffiProfile: ffiPath === null ? null : { path: ffiPath, bytes: ffiBytes! },
|
||||
target: `${process.env["SCRIPTC_TARGET"] ?? "native"}:${buildPlatform}:${process.arch}:${
|
||||
|
||||
@@ -71,7 +71,7 @@ async function main(): Promise<number> {
|
||||
const [command, inputArg] = positionals;
|
||||
if (command === "cache") {
|
||||
if (inputArg !== "warm") fail(`unknown cache command "${inputArg ?? ""}" (supported: warm)\n\n${USAGE}`);
|
||||
if (values.lib || values.dynamic || values.backend !== undefined || values.emit !== undefined || values.print !== undefined || values["from-c"] || values.ffi !== undefined || values.profile !== undefined || values["windows-subsystem"] !== undefined || (values["npm-static"] ?? []).length > 0 || values["provenance-sources"] || externalTypeArgs.length > 0 || values.out !== undefined || values["emit-ir"] || !values["keep-c"]) {
|
||||
if (values.lib || values.dynamic || values.backend !== undefined || values.emit !== undefined || values.print !== undefined || values["from-c"] || values.ffi !== undefined || values.profile !== undefined || values.strip || values["windows-subsystem"] !== undefined || (values["npm-static"] ?? []).length > 0 || values["provenance-sources"] || externalTypeArgs.length > 0 || values.out !== undefined || values["emit-ir"] || !values["keep-c"]) {
|
||||
fail(`scriptc cache warm takes only native optimization/sanitizer options and profile names\n\n${USAGE}`);
|
||||
}
|
||||
const optimization = values.optimization;
|
||||
@@ -119,9 +119,9 @@ async function main(): Promise<number> {
|
||||
if (inputArg) {
|
||||
fail("scriptc build --lib takes no input positional: the profile names the entry module");
|
||||
}
|
||||
if (values.dynamic || values.backend !== undefined || values.emit !== undefined || values.print !== undefined || values.optimization !== undefined || values.ffi !== undefined || values["windows-subsystem"] !== undefined || (values["npm-static"] ?? []).length > 0 || externalTypeArgs.length > 0) {
|
||||
if (values.dynamic || values.backend !== undefined || values.emit !== undefined || values.print !== undefined || values.optimization !== undefined || values.strip || values.ffi !== undefined || values["windows-subsystem"] !== undefined || (values["npm-static"] ?? []).length > 0 || externalTypeArgs.length > 0) {
|
||||
fail(
|
||||
"scriptc build --lib takes no --dynamic/--backend/--emit/--print/--optimization/--windows-subsystem/--npm-static/--ffi/--external-types: the profile pins the emission and optimization, npm imports are judged automatically, outbound FFI belongs to executable builds, and external type mappings belong to coverage",
|
||||
"scriptc build --lib takes no --dynamic/--backend/--emit/--print/--optimization/--strip/--windows-subsystem/--npm-static/--ffi/--external-types: the profile pins the emission and optimization, npm imports are judged automatically, outbound FFI belongs to executable builds, and external type mappings belong to coverage",
|
||||
);
|
||||
}
|
||||
const profilePath = resolve(profileArg);
|
||||
@@ -155,6 +155,9 @@ async function main(): Promise<number> {
|
||||
if (command === "coverage" && values.emit !== undefined) {
|
||||
fail(`--emit is a build/run option\n\n${USAGE}`);
|
||||
}
|
||||
if (command === "coverage" && values.strip) {
|
||||
fail(`--strip is a build/run option\n\n${USAGE}`);
|
||||
}
|
||||
if (values.print !== undefined && values.print !== "native-link-info") {
|
||||
fail(`unknown print kind "${values.print}" (supported: native-link-info)\n\n${USAGE}`);
|
||||
}
|
||||
@@ -220,6 +223,7 @@ async function main(): Promise<number> {
|
||||
keepC: values["keep-c"],
|
||||
sanitize: values.sanitize,
|
||||
...(values.optimization === undefined ? {} : { optimization: values.optimization }),
|
||||
strip: values.strip,
|
||||
...(windowsSubsystem === undefined ? {} : { windowsSubsystem }),
|
||||
...(values.ffi === undefined ? {} : { ffi: values.ffi }),
|
||||
});
|
||||
@@ -295,6 +299,7 @@ async function main(): Promise<number> {
|
||||
sanitize: values.sanitize,
|
||||
dynamic: values.dynamic,
|
||||
...(optimization !== undefined ? { optimization } : {}),
|
||||
...(values.strip ? { strip: true } : {}),
|
||||
...(windowsSubsystem !== undefined ? { windowsSubsystem } : {}),
|
||||
});
|
||||
return outPath;
|
||||
@@ -308,6 +313,7 @@ async function main(): Promise<number> {
|
||||
dynamic: values.dynamic,
|
||||
...(backend !== undefined ? { backend } : {}),
|
||||
...(optimization !== undefined ? { optimization } : {}),
|
||||
...(values.strip ? { strip: true } : {}),
|
||||
...(windowsSubsystem !== undefined ? { windowsSubsystem } : {}),
|
||||
...(npmStatic !== undefined ? { npmStatic } : {}),
|
||||
...(ffiProfilePath !== undefined ? { ffiProfilePath } : {}),
|
||||
|
||||
@@ -9,6 +9,7 @@ export interface OutputOptionValues {
|
||||
keepC: boolean;
|
||||
sanitize: boolean;
|
||||
optimization?: string;
|
||||
strip?: boolean;
|
||||
windowsSubsystem?: string;
|
||||
ffi?: string;
|
||||
}
|
||||
@@ -62,6 +63,9 @@ export function resolveOutputOptions(
|
||||
if (values.windowsSubsystem !== undefined && emit !== "exe") {
|
||||
return { ok: false, message: `--windows-subsystem is only supported with --emit=exe` };
|
||||
}
|
||||
if (values.strip && emit !== "exe") {
|
||||
return { ok: false, message: `--strip is only supported with --emit=exe` };
|
||||
}
|
||||
if (emit === "ir" && backend !== undefined) {
|
||||
return { ok: false, message: `--emit=ir cannot be combined with --backend; IR is emitted before backend selection` };
|
||||
}
|
||||
|
||||
@@ -35,6 +35,8 @@ Options:
|
||||
native optimization posture (default: release/-O2). dev
|
||||
uses -O0 and stable cached LLVM object shards for faster
|
||||
edits of large programs
|
||||
--strip remove symbol/debug payload from the linked executable
|
||||
for smaller builds (opt in; --emit=exe only)
|
||||
--windows-subsystem <console|gui>
|
||||
Windows executable subsystem (default: console). gui
|
||||
prevents Windows from opening a console window
|
||||
@@ -77,6 +79,7 @@ export const CLI_OPTIONS = {
|
||||
print: { type: "string" },
|
||||
backend: { type: "string" },
|
||||
optimization: { type: "string" },
|
||||
strip: { type: "boolean", default: false },
|
||||
"windows-subsystem": { type: "string" },
|
||||
"from-c": { type: "boolean", default: false },
|
||||
"keep-c": { type: "boolean", default: true },
|
||||
|
||||
@@ -48,6 +48,8 @@ describe("output option compatibility", () => {
|
||||
[{ backend: "wat" }, /unknown backend/],
|
||||
[{ emit: "obj", windowsSubsystem: "gui" }, /windows-subsystem/],
|
||||
[{ emit: "llvm", windowsSubsystem: "gui" }, /windows-subsystem/],
|
||||
[{ emit: "obj", strip: true }, /strip/],
|
||||
[{ emit: "llvm", strip: true }, /strip/],
|
||||
] as const)("rejects %j", (override, message) => {
|
||||
const result = resolveOutputOptions("build", { ...BASE, ...override });
|
||||
expect(result).toEqual({ ok: false, message: expect.stringMatching(message) });
|
||||
@@ -98,4 +100,11 @@ describe("output option compatibility", () => {
|
||||
outputKind: "exe",
|
||||
});
|
||||
});
|
||||
|
||||
test("executable outputs accept stripping", () => {
|
||||
expect(resolveOutputOptions("build", { ...BASE, strip: true })).toMatchObject({
|
||||
ok: true,
|
||||
outputKind: "exe",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
import { execFile } from "node:child_process";
|
||||
import { createRequire } from "node:module";
|
||||
import { mkdtemp, rm, stat, writeFile } from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import { dirname, join } from "node:path";
|
||||
import { promisify } from "node:util";
|
||||
import { expect, test } from "vitest";
|
||||
|
||||
const execFileAsync = promisify(execFile);
|
||||
const require = createRequire(import.meta.url);
|
||||
const repoRoot = join(import.meta.dirname, "../../..");
|
||||
const cliEntry = join(repoRoot, "packages/cli/src/main.ts");
|
||||
const bootstrapEntry = join(repoRoot, "packages/cli/dist/bootstrap.js");
|
||||
const tsxLoader = join(dirname(require.resolve("tsx/package.json")), "dist/loader.mjs");
|
||||
|
||||
test.each(["auto", "c", "from-c"] as const)(
|
||||
"--strip shrinks and runs %s executables without mixing cache variants",
|
||||
async (route) => {
|
||||
const dir = await mkdtemp(join(process.platform === "win32" ? tmpdir() : "/tmp", "scriptc-strip-"));
|
||||
const entry = join(dir, route === "from-c" ? "main.c" : "main.ts");
|
||||
const outPath = join(dir, process.platform === "win32" ? "program.exe" : "program");
|
||||
const env = { ...process.env, SCRIPTC_CACHE_DIR: join(dir, "cache") };
|
||||
const routeArgs = route === "auto" ? [] : route === "c" ? ["--backend=c"] : ["--from-c"];
|
||||
const build = async (strip: boolean, bootstrap = false): Promise<number> => {
|
||||
await execFileAsync(process.execPath, [
|
||||
...(bootstrap ? [bootstrapEntry] : ["--import", tsxLoader, cliEntry]),
|
||||
"build", entry, "-o", outPath, ...routeArgs, ...(strip ? ["--strip"] : []),
|
||||
], { env, maxBuffer: 4 * 1024 * 1024 });
|
||||
return (await stat(outPath)).size;
|
||||
};
|
||||
try {
|
||||
await writeFile(entry, route === "from-c"
|
||||
? '#include <stdio.h>\nint main(void) { puts("strip-ok"); return 0; }\n'
|
||||
: 'console.log("strip-ok");\n');
|
||||
const originalSize = await build(false);
|
||||
const strippedSize = await build(true);
|
||||
expect(strippedSize).toBeLessThan(originalSize);
|
||||
expect((await execFileAsync(outPath)).stdout).toBe("strip-ok\n");
|
||||
if (route === "auto") {
|
||||
expect(await build(true, true)).toBe(strippedSize);
|
||||
expect(await build(false, true)).toBe(originalSize);
|
||||
expect((await execFileAsync(outPath)).stdout).toBe("strip-ok\n");
|
||||
}
|
||||
} finally {
|
||||
await rm(dir, { recursive: true, force: true });
|
||||
}
|
||||
},
|
||||
180_000,
|
||||
);
|
||||
@@ -12,6 +12,7 @@ import type { NativeArtifactDependency } from "./native-toolchain.js";
|
||||
import { loadRuntimePack, type RuntimePackSelection } from "./runtime-pack.js";
|
||||
import {
|
||||
executableOptimizationLinkerArgs,
|
||||
executableStripLinkerArgs,
|
||||
windowsSubsystemLinkerArgs,
|
||||
type NativeTargetSpec,
|
||||
type WindowsSubsystem,
|
||||
@@ -38,6 +39,7 @@ export async function createNativeLinkPlan(options: {
|
||||
features: NativeLinkFeatures;
|
||||
ffi: FfiProfile | null;
|
||||
optimization: "release" | "dev";
|
||||
strip?: boolean;
|
||||
windowsSubsystem?: WindowsSubsystem;
|
||||
programObjectDependencies?: readonly NativeArtifactDependency[];
|
||||
env?: NodeJS.ProcessEnv;
|
||||
@@ -62,6 +64,7 @@ export async function createNativeLinkPlan(options: {
|
||||
index > 0 && args[index - 1] === "-target" ? options.target.linkerTargetTriple : arg
|
||||
),
|
||||
...executableOptimizationLinkerArgs(options.target.platform, options.optimization),
|
||||
...executableStripLinkerArgs(options.target.platform, options.strip ?? false),
|
||||
...windowsSubsystemLinkerArgs(options.target.platform, options.windowsSubsystem),
|
||||
],
|
||||
dependencyPaths: [
|
||||
|
||||
@@ -10,7 +10,7 @@ import { performance } from "node:perf_hooks";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { promisify } from "node:util";
|
||||
import { localizeElfObject, mergeAndLocalizeCoffObjects } from "./object-localize.js";
|
||||
import { executableOptimizationLinkerArgs, windowsSubsystemLinkerArgs, type WindowsSubsystem } from "./targets.js";
|
||||
import { executableOptimizationLinkerArgs, executableStripLinkerArgs, windowsSubsystemLinkerArgs, type WindowsSubsystem } from "./targets.js";
|
||||
import {
|
||||
createVendorArchives,
|
||||
MBEDTLS_VERSION,
|
||||
@@ -285,6 +285,8 @@ export interface CcOptions {
|
||||
* executable lane; dev selects -O0 and may compile a caller-provided LLVM
|
||||
* shard set into independently cached objects before the final link. */
|
||||
optimization?: "release" | "dev";
|
||||
/** Remove symbol/debug payload from the linked executable. */
|
||||
strip?: boolean;
|
||||
/** PE executable subsystem; omitted and console use the driver default. */
|
||||
windowsSubsystem?: WindowsSubsystem;
|
||||
/** Optional equivalent LLVM modules for dev compilation. Unsupported
|
||||
@@ -4062,6 +4064,7 @@ async function compileCInternal(
|
||||
targetPlatform(driver),
|
||||
optimization,
|
||||
),
|
||||
...executableStripLinkerArgs(targetPlatform(driver), opts.strip ?? false),
|
||||
...windowsSubsystemArgs,
|
||||
];
|
||||
// scr_async.c submits callback-style filesystem work to a native worker.
|
||||
|
||||
@@ -54,6 +54,19 @@ export function executableOptimizationLinkerArgs(
|
||||
: [];
|
||||
}
|
||||
|
||||
/** Remove symbol/debug payload at link time so the executable cache stores
|
||||
* the same bytes the user receives, including for cross-compiled targets. */
|
||||
export function executableStripLinkerArgs(platform: string, strip: boolean): string[] {
|
||||
if (!strip) return [];
|
||||
switch (platform) {
|
||||
case "darwin": return ["-Wl,-S,-x"];
|
||||
case "linux":
|
||||
case "win32": return ["-Wl,-s"];
|
||||
case "wasi": return ["-Wl,--strip-all"];
|
||||
default: throw new Error(`--strip is not supported for the ${platform} executable target`);
|
||||
}
|
||||
}
|
||||
|
||||
export interface NativeTargetSpec {
|
||||
/** Stable scriptc-facing identity used in cache keys and diagnostics. */
|
||||
name: NativeTargetName;
|
||||
|
||||
@@ -85,6 +85,8 @@ export interface EarlyExecutableCacheOptions {
|
||||
backend: "auto" | "c" | "llvm";
|
||||
/** Omitted is the historical release posture and preserves v1 keys. */
|
||||
optimization?: "dev";
|
||||
/** Omitted retains the unstripped executable's historical cache key. */
|
||||
strip?: true;
|
||||
/** Omitted for the default Windows console subsystem. */
|
||||
windowsSubsystem?: "gui";
|
||||
npmStatic: readonly string[] | "auto" | null;
|
||||
@@ -188,6 +190,7 @@ function cacheKey(options: EarlyExecutableCacheOptions): string {
|
||||
options.dynamic ? "dynamic" : "static",
|
||||
options.backend,
|
||||
...(options.optimization === "dev" ? ["optimization-dev"] : []),
|
||||
...(options.strip ? ["strip"] : []),
|
||||
...(options.windowsSubsystem === "gui" ? ["windows-subsystem-gui"] : []),
|
||||
options.npmStatic === null
|
||||
? "<npm-static-off>"
|
||||
@@ -216,6 +219,7 @@ function routeKey(options: EarlyExecutableRouteOptions): string {
|
||||
.update(options.dynamic ? "dynamic" : "static").update("\0")
|
||||
.update(options.backend).update("\0");
|
||||
if (options.optimization === "dev") hash.update("optimization-dev\0");
|
||||
if (options.strip) hash.update("strip\0");
|
||||
if (options.windowsSubsystem === "gui") hash.update("windows-subsystem-gui\0");
|
||||
hash
|
||||
.update(options.npmStatic === null
|
||||
|
||||
@@ -6,62 +6,13 @@ import type { Lowerer } from "../lowerer.js";
|
||||
import { own } from "../lowerer.js";
|
||||
import { isRequireMainFilename } from "../expressions/optional-chains.js";
|
||||
import { STR_METHODS } from "../surfaces.js";
|
||||
import { defaultAfterUndefined, lowerOptionalArgument, lowerStaticallyUndefinedArgument } from "../optional-arguments.js";
|
||||
import { defaultAfterUndefined, lowerOptionalArgument, lowerPositionArgument, lowerStaticallyUndefinedArgument, lowerStringSearchArgument, positionNumber } from "../optional-arguments.js";
|
||||
|
||||
function lowerSplitLimitArg(lowerer: Lowerer, node: ts.Expression | undefined, loc: SrcLoc): IrExpr {
|
||||
const defaultValue: IrExpr = { kind: "numLit", value: 4294967295, type: F64, loc };
|
||||
return node ? lowerOptionalArgument(lowerer, node, F64, defaultValue) : defaultValue;
|
||||
}
|
||||
|
||||
/** Complete unit-valued defaults while retaining argument side effects. */
|
||||
function lowerStringPositionArgument(lowerer: Lowerer, node: ts.Expression | undefined, defaultValue: IrExpr): IrExpr {
|
||||
if (!node) return defaultValue;
|
||||
const undefinedArg = lowerStaticallyUndefinedArgument(lowerer, node);
|
||||
if (undefinedArg) return defaultAfterUndefined(undefinedArg, defaultValue);
|
||||
const value = lowerer.lowerExpr(node);
|
||||
if (isUnitType(value.type) || value.type.kind === "void") {
|
||||
return defaultAfterUndefined(value, value.type.kind === "nullT" ? numLit(0, value.loc) : defaultValue);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
/** Convert a helper parameter, after every argument expression has run.
|
||||
* Only undefined selects a missing end; null still converts to zero. */
|
||||
function stringPositionNumber(lowerer: Lowerer, value: IrExpr, defaultValue: IrExpr, node: ts.Expression): IrExpr {
|
||||
const loc = value.loc;
|
||||
switch (value.type.kind) {
|
||||
case "f64": return value;
|
||||
case "string": return { kind: "libCall", fn: "num.fromString", args: [value], type: F64, loc };
|
||||
case "bool": return { kind: "ternary", cond: value, then: numLit(1, loc), else_: numLit(0, loc), type: F64, loc };
|
||||
case "nullT": return numLit(0, loc);
|
||||
case "undefinedT": return defaultValue;
|
||||
case "jsval": return { kind: "jsExit", value, type: F64, loc };
|
||||
case "union": {
|
||||
const unionId = value.type.unionId;
|
||||
const arms = lowerer.unions.get(unionId)!.arms;
|
||||
let result: IrExpr = defaultValue;
|
||||
for (let tag = arms.length - 1; tag >= 0; tag--) {
|
||||
const narrowed: IrExpr = { kind: "unionNarrow", unionId, tag, value, type: arms[tag]!, loc };
|
||||
const converted = stringPositionNumber(lowerer, narrowed, defaultValue, node);
|
||||
result = tag === arms.length - 1 ? converted : {
|
||||
kind: "ternary",
|
||||
cond: { kind: "unionIsTag", unionId, tag, value, negated: false, type: BOOL, loc },
|
||||
then: converted, else_: result, type: F64, loc,
|
||||
};
|
||||
}
|
||||
return result;
|
||||
}
|
||||
case "dyn": return {
|
||||
kind: "ternary",
|
||||
cond: { kind: "dynTest", test: "undefined", value, type: BOOL, loc },
|
||||
then: defaultValue,
|
||||
else_: { kind: "libCall", fn: "dyn.toNumberCoerce", args: [value], type: F64, loc },
|
||||
type: F64, loc,
|
||||
};
|
||||
default: return lowerer.noLowering(`string position of '${lowerer.fmt(value.type)}' values`, node);
|
||||
}
|
||||
}
|
||||
|
||||
function lowerRegexSubject(lowerer: Lowerer, node: ts.Expression | undefined, loc: SrcLoc): IrExpr {
|
||||
const absent: IrExpr = { kind: "strLit", value: "undefined", type: STRING, loc };
|
||||
if (!node) return absent;
|
||||
@@ -320,24 +271,23 @@ export function lowerStringMethodCall(lowerer: Lowerer, call: ts.CallExpression,
|
||||
? dynReceiver()
|
||||
: lowerMethodReceiver(lowerer, access.expression, STRING, access.name.text);
|
||||
const loc = locOf(call);
|
||||
if ((entry.method === "indexOf" || entry.method === "includes" ||
|
||||
entry.method === "startsWith" || entry.method === "endsWith") && call.arguments.length === 2) {
|
||||
const rawNeedle = lowerer.lowerExpr(call.arguments[0]!);
|
||||
// indexOf ToString-coerces the search value before converting the
|
||||
// position. The other search methods must also reject RegExp values,
|
||||
// so keep their non-string inputs fenced until IsRegExp is lowered.
|
||||
const needle = rawNeedle.type.kind === "string"
|
||||
? rawNeedle
|
||||
: entry.method === "indexOf" && rawNeedle.type.kind === "dyn"
|
||||
? { kind: "libCall", fn: "dyn.toStringCoerce", args: [rawNeedle], type: STRING, loc: rawNeedle.loc } satisfies IrExpr
|
||||
: entry.method === "indexOf"
|
||||
? lowerer.ensureString(rawNeedle, call.arguments[0]!)
|
||||
: lowerer.noLowering(`.${entry.method} with a non-string search value`, call.arguments[0]!);
|
||||
const searchMethod = entry.method === "indexOf" || entry.method === "includes" ||
|
||||
entry.method === "startsWith" || entry.method === "endsWith";
|
||||
if (searchMethod && call.arguments.length < 2) {
|
||||
const needle = lowerStringSearchArgument(lowerer, call.arguments[0], loc);
|
||||
return { kind: "strIntrinsic", method: entry.method, receiver, args: [needle], type: entry.result, loc };
|
||||
}
|
||||
if (searchMethod && call.arguments.length === 2) {
|
||||
const needle = lowerer.lowerExpr(call.arguments[0]!);
|
||||
const optionalNeedle = needle.type.kind === "union" && lowerer.runtimeOptionalWidening(needle.type, STRING) !== null;
|
||||
if (needle.type.kind !== "string" && needle.type.kind !== "dyn" && !optionalNeedle) {
|
||||
lowerer.noLowering(`.${entry.method} with '${lowerer.fmt(needle.type)}' search values`, call);
|
||||
}
|
||||
const defaultPosition: IrExpr = entry.method === "endsWith"
|
||||
? { kind: "bin", op: "/", left: numLit(1, loc), right: numLit(0, loc), type: F64, loc }
|
||||
: numLit(0, loc);
|
||||
const position = lowerStringPositionArgument(lowerer, call.arguments[1], defaultPosition);
|
||||
if (position.type.kind === "f64" || position.type.kind === "jsval") {
|
||||
const position = lowerPositionArgument(lowerer, call.arguments[1], defaultPosition);
|
||||
if (needle.type.kind === "string" && (position.type.kind === "f64" || position.type.kind === "jsval")) {
|
||||
return { kind: "strIntrinsic", method: entry.method, receiver, args: [needle, position], type: entry.result, loc };
|
||||
}
|
||||
const key = `str.positions:${entry.method}:${typeKey(needle.type)}:${typeKey(position.type)}`;
|
||||
@@ -345,19 +295,31 @@ export function lowerStringMethodCall(lowerer: Lowerer, call: ts.CallExpression,
|
||||
if (!helper) {
|
||||
helper = `%str.positions.${lowerer.widthHelpers.size}`;
|
||||
const params = [receiver, needle, position].map((arg, index) => ({ localId: `arg.${index}`, name: `arg${index}`, type: arg.type }));
|
||||
const coerceNeedle = needle.type.kind !== "string";
|
||||
const search: IrExpr = coerceNeedle ? varRef("search.0", STRING, loc) : varRef("arg.1", STRING, loc);
|
||||
const result: IrExpr = {
|
||||
kind: "strIntrinsic", method: entry.method, receiver: varRef("arg.0", STRING, loc),
|
||||
args: [
|
||||
varRef("arg.1", needle.type, loc),
|
||||
stringPositionNumber(lowerer, varRef("arg.2", position.type, loc), defaultPosition, call.arguments[1]!),
|
||||
search,
|
||||
positionNumber(lowerer, varRef("arg.2", position.type, loc), defaultPosition, call.arguments[1]!, "string position"),
|
||||
],
|
||||
type: entry.result, loc,
|
||||
};
|
||||
const locals = params.map(param => ({ id: param.localId, name: param.name, type: param.type, mutable: false }));
|
||||
const body: IrStmt[] = [];
|
||||
if (coerceNeedle) {
|
||||
const value = varRef("arg.1", needle.type, loc);
|
||||
const init: IrExpr = needle.type.kind === "dyn"
|
||||
? { kind: "libCall", fn: "dyn.toStringCoerce", args: [value], type: STRING, loc }
|
||||
: lowerer.ensureString(value, call.arguments[0]!);
|
||||
locals.push({ id: "search.0", name: "search", type: STRING, mutable: false });
|
||||
body.push({ kind: "varDecl", localId: "search.0", init, loc });
|
||||
}
|
||||
body.push({ kind: "return", value: result, loc });
|
||||
lowerer.widthHelpers.set(key, helper);
|
||||
lowerer.liftedFns.push({
|
||||
name: helper, params, returnType: entry.result,
|
||||
locals: params.map(param => ({ id: param.localId, name: param.name, type: param.type, mutable: false })),
|
||||
body: [{ kind: "return", value: result, loc }], loc,
|
||||
locals, body, loc,
|
||||
});
|
||||
}
|
||||
return { kind: "call", callee: helper, args: [receiver, needle, position], type: entry.result, loc };
|
||||
@@ -367,7 +329,7 @@ export function lowerStringMethodCall(lowerer: Lowerer, call: ts.CallExpression,
|
||||
if (entry.method === "slice" || entry.method === "substring") {
|
||||
defaults.push({ kind: "bin", op: "/", left: numLit(1, loc), right: numLit(0, loc), type: F64, loc });
|
||||
}
|
||||
const args = defaults.map((value, index) => lowerStringPositionArgument(lowerer, call.arguments[index], value));
|
||||
const args = defaults.map((value, index) => lowerPositionArgument(lowerer, call.arguments[index], value));
|
||||
// Keep ordinary numeric calls on the direct intrinsic path, including the
|
||||
// existing boundary validation for island values in numeric slots.
|
||||
if (args.every(arg => arg.type.kind === "f64" || arg.type.kind === "jsval")) {
|
||||
@@ -383,7 +345,7 @@ export function lowerStringMethodCall(lowerer: Lowerer, call: ts.CallExpression,
|
||||
const params = [receiver, ...args].map((arg, index) => ({ localId: `arg.${index}`, name: `arg${index}`, type: arg.type }));
|
||||
const result: IrExpr = {
|
||||
kind: "strIntrinsic", method: entry.method, receiver: varRef("arg.0", STRING, loc),
|
||||
args: args.map((arg, index) => stringPositionNumber(lowerer, varRef(`arg.${index + 1}`, arg.type, loc), defaults[index]!, call.arguments[index] ?? call)),
|
||||
args: args.map((arg, index) => positionNumber(lowerer, varRef(`arg.${index + 1}`, arg.type, loc), defaults[index]!, call.arguments[index] ?? call, "string position")),
|
||||
type: entry.result, loc,
|
||||
};
|
||||
lowerer.widthHelpers.set(key, helper);
|
||||
|
||||
@@ -32,7 +32,7 @@ import {
|
||||
import { lowerAbsenceProbe } from "./lower-exprs.js";
|
||||
import { conditionalSpreadOf, lowerDynObjectLiteral } from "./expressions/object-literals.js";
|
||||
import { isSafeToDiscard } from "./expressions/evaluation-safety.js";
|
||||
import { defaultAfterUndefined, lowerOptionalArgument, lowerStaticallyUndefinedArgument } from "./optional-arguments.js";
|
||||
import { defaultAfterUndefined, lowerOptionalArgument, lowerStaticallyUndefinedArgument, lowerStringSearchArgument } from "./optional-arguments.js";
|
||||
import { HTTP2_CONSTANTS } from "./http2-constants.js";
|
||||
import { CRYPTO_CIPHERS, CRYPTO_CONSTANTS, CRYPTO_CURVES, CRYPTO_HASHES } from "./crypto-tables.js";
|
||||
import { generatorMeta, timerStyleCallback, type ParamShape } from "./lower-calls.js";
|
||||
@@ -8925,8 +8925,8 @@ function staticTextDecoderEncoding(label: string): StaticTextDecoderEncoding | n
|
||||
return { kind: "libCall", fn: "string.fromCharCode", args: [packed], type: STRING, loc };
|
||||
}
|
||||
|
||||
/** `s.lastIndexOf(needle, position?)` on string receivers, using UTF-16
|
||||
* indices. Omitted or undefined positions clamp to the string's end;
|
||||
/** `s.lastIndexOf(searchValue?, position?)` on string receivers, using UTF-16
|
||||
* indices. An omitted search value searches for "undefined". Omitted or undefined positions clamp to the string's end;
|
||||
* MAX_SAFE_INTEGER has the same effect for every representable string.
|
||||
* Null for non-string receivers and other members. */
|
||||
export function lowerStringLastIndexOfCall(lowerer: Lowerer, call: ts.CallExpression,
|
||||
@@ -8936,19 +8936,20 @@ function staticTextDecoderEncoding(label: string): StaticTextDecoderEncoding | n
|
||||
if (lowerer.mapTypeOf(lowerer.typeOf(access.expression))?.kind !== "string") return null;
|
||||
if (!lowerer.isStdlibMember(access)) return null;
|
||||
const loc = locOf(call);
|
||||
if (call.arguments.length < 1 || call.arguments.length > 2 || call.arguments.some(ts.isSpreadElement)) {
|
||||
if (call.arguments.length > 2 || call.arguments.some(ts.isSpreadElement)) {
|
||||
lowerer.noLowering(
|
||||
"lastIndexOf with this argument shape",
|
||||
call,
|
||||
"pass a string needle and an optional numeric position",
|
||||
"pass no arguments, or a search value with an optional numeric position",
|
||||
);
|
||||
}
|
||||
const receiver = lowerer.lowerExprExpecting(access.expression, STRING);
|
||||
const needle = lowerer.lowerExprExpecting(call.arguments[0]!, STRING);
|
||||
const positionNode = call.arguments[1];
|
||||
if (!positionNode) {
|
||||
const needle = lowerStringSearchArgument(lowerer, call.arguments[0], loc);
|
||||
return { kind: "libCall", fn: "string.lastIndexOf", args: [receiver, needle], type: F64, loc };
|
||||
}
|
||||
const needle = lowerer.lowerExprExpecting(call.arguments[0]!, STRING);
|
||||
const position = lowerOptionalArgument(lowerer, positionNode, F64, numLit(Number.MAX_SAFE_INTEGER, loc));
|
||||
return { kind: "libCall", fn: "string.lastIndexOfFrom", args: [receiver, needle, position], type: F64, loc };
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ import { arrayIndexPresent, arrayValueRead, arrayValueStore, arrayValueType, cur
|
||||
import { typeKey } from "../type-mapper.js";
|
||||
import { WidthLift } from "./lowerer.js";
|
||||
import { boolLit, countedFor, numLit, varRef } from "../../ir/build.js";
|
||||
import { lowerOptionalArgument } from "./optional-arguments.js";
|
||||
import { lowerOptionalArgument, lowerPositionArgument, positionNumber } from "./optional-arguments.js";
|
||||
|
||||
/** Build the argument array for a mutating/copying operation while keeping
|
||||
* the receiver's scalar payload ABI. A missing read is represented as the
|
||||
@@ -325,7 +325,7 @@ function fenceProducedArrayElem(lowerer: Lowerer, node: ts.Node, producer: strin
|
||||
// included — Node returns the unchanged length); reduce/reduceRight
|
||||
// lower both declared forms (with and without an initial value).
|
||||
const arity = {
|
||||
push: [0, Number.MAX_SAFE_INTEGER], unshift: [0, Number.MAX_SAFE_INTEGER], pop: [0, 0], indexOf: [1, 2], includes: [1, 2], join: [1, 1],
|
||||
push: [0, Number.MAX_SAFE_INTEGER], unshift: [0, Number.MAX_SAFE_INTEGER], pop: [0, 0], indexOf: [1, 2], lastIndexOf: [1, 2], includes: [0, 2], join: [1, 1],
|
||||
concat: [0, Number.MAX_SAFE_INTEGER],
|
||||
slice: [0, 2], shift: [0, 0], splice: [1, 2], at: [1, 1],
|
||||
map: [1, 1], filter: [1, 1], forEach: [1, 1], find: [1, 1], findIndex: [1, 1], some: [1, 1],
|
||||
@@ -333,7 +333,7 @@ function fenceProducedArrayElem(lowerer: Lowerer, node: ts.Node, producer: strin
|
||||
every: [1, 1], flatMap: [1, 1], reduce: [1, 2], reduceRight: [1, 2],
|
||||
}[
|
||||
name as
|
||||
| "push" | "unshift" | "pop" | "concat" | "indexOf" | "includes" | "join" | "slice" | "shift" | "splice" | "at" | "map" | "filter"
|
||||
| "push" | "unshift" | "pop" | "concat" | "indexOf" | "lastIndexOf" | "includes" | "join" | "slice" | "shift" | "splice" | "at" | "map" | "filter"
|
||||
| "forEach" | "find" | "findIndex" | "findLast" | "findLastIndex" | "some" | "every" | "flatMap" | "reduce" | "reduceRight"
|
||||
];
|
||||
if (call.arguments.length < arity[0]! || call.arguments.length > arity[1]!) {
|
||||
@@ -448,7 +448,7 @@ function fenceProducedArrayElem(lowerer: Lowerer, node: ts.Node, producer: strin
|
||||
loc,
|
||||
};
|
||||
}
|
||||
if (name === "indexOf" || name === "includes") {
|
||||
if (name === "indexOf" || name === "lastIndexOf" || name === "includes") {
|
||||
return lowerArraySearchCall(lowerer, call, access, name, elem);
|
||||
}
|
||||
if (name === "concat") {
|
||||
@@ -599,24 +599,28 @@ function fenceProducedArrayElem(lowerer: Lowerer, node: ts.Node, producer: strin
|
||||
/** `indexOf` and `includes` normally use the runtime's typed search helpers,
|
||||
* but an indexed array read is a `T | undefined` value. Keep that union
|
||||
* through a small lowered loop so the needle's missing state is observable:
|
||||
* indexOf skips holes, while includes treats holes as undefined. The same
|
||||
* loop also gives union-element arrays their value equality rather than
|
||||
* comparing the compiler's union boxes by pointer. */
|
||||
* indexOf and lastIndexOf skip holes, while includes treats holes as
|
||||
* undefined. The same loop also gives union-element arrays their value
|
||||
* equality rather than comparing the compiler's union boxes by pointer. */
|
||||
function lowerArraySearchCall(
|
||||
lowerer: Lowerer,
|
||||
call: ts.CallExpression,
|
||||
access: ts.PropertyAccessExpression,
|
||||
method: "indexOf" | "includes",
|
||||
method: "indexOf" | "lastIndexOf" | "includes",
|
||||
elem: IrType,
|
||||
): IrExpr {
|
||||
const loc = locOf(call);
|
||||
const receiver = lowerer.lowerExpr(access.expression);
|
||||
const argNode = call.arguments[0]!;
|
||||
const argNode = call.arguments[0] ?? call;
|
||||
if (call.arguments.some(ts.isSpreadElement)) lowerer.noLowering(`.${method} with spread arguments`, call);
|
||||
let needle = lowerer.lowerExpr(argNode);
|
||||
const fromIndex = call.arguments[1]
|
||||
? lowerOptionalArgument(lowerer, call.arguments[1], F64, numLit(0, loc))
|
||||
: numLit(0, loc);
|
||||
let needle: IrExpr = call.arguments[0]
|
||||
? lowerer.lowerExpr(call.arguments[0])
|
||||
: { kind: "unitLit", unit: "undefined", type: UNDEFINED_T, loc };
|
||||
const fromIndex: IrExpr = call.arguments[1]
|
||||
? lowerPositionArgument(lowerer, call.arguments[1], numLit(0, loc))
|
||||
: method === "lastIndexOf"
|
||||
? { kind: "bin", op: "/", left: numLit(1, loc), right: numLit(0, loc), type: F64, loc }
|
||||
: numLit(0, loc);
|
||||
// A derived CLASS VALUE against a base-classval element widens (the same
|
||||
// pointer — identity search is exact); the coercion path owns the ABI gate.
|
||||
if (needle.type.kind === "classval" && elem.kind === "classval" && !typeEquals(needle.type, elem)) {
|
||||
@@ -625,13 +629,13 @@ function lowerArraySearchCall(
|
||||
|
||||
// Preserve the existing fast path for an ordinary, non-union needle. Its
|
||||
// runtime implementation already has exact primitive/reference equality.
|
||||
if (call.arguments.length === 1 && elem.kind !== "union" && elem.kind !== "bigint" && typeEquals(needle.type, elem)) {
|
||||
if (method !== "lastIndexOf" && call.arguments.length === 1 && elem.kind !== "union" && elem.kind !== "bigint" && typeEquals(needle.type, elem)) {
|
||||
return {
|
||||
kind: "arrIntrinsic",
|
||||
method,
|
||||
receiver,
|
||||
args: [needle],
|
||||
type: method === "indexOf" ? F64 : BOOL,
|
||||
type: method === "includes" ? BOOL : F64,
|
||||
loc,
|
||||
};
|
||||
}
|
||||
@@ -641,6 +645,7 @@ function lowerArraySearchCall(
|
||||
// static undefined arm. Keep their existing runtime path and its explicit
|
||||
// type fence; typed static arrays take the state-aware helper below.
|
||||
if (valueT.kind !== "union") {
|
||||
if (method === "lastIndexOf") lowerer.noLowering(".lastIndexOf on an array of engine values", call);
|
||||
if (call.arguments.length > 1) lowerer.noLowering(`.${method} with fromIndex on '${lowerer.fmt(elem)}' elements`, call);
|
||||
if (!typeEquals(needle.type, elem)) lowerer.badType(argNode, lowerer.typeOf(argNode));
|
||||
return {
|
||||
@@ -648,7 +653,7 @@ function lowerArraySearchCall(
|
||||
method,
|
||||
receiver,
|
||||
args: [needle],
|
||||
type: method === "indexOf" ? F64 : BOOL,
|
||||
type: method === "includes" ? BOOL : F64,
|
||||
loc,
|
||||
};
|
||||
}
|
||||
@@ -668,10 +673,16 @@ function lowerArraySearchCall(
|
||||
for (const arm of needleArms) if (!arms.some((existing) => typeEquals(existing, arm))) arms.push(arm);
|
||||
if (arms.length !== valueArms.length) valueT = { kind: "union", unionId: lowerer.unions.intern(arms) };
|
||||
}
|
||||
const searchNeedle = lowerer.coerceInto(argNode, needle, valueT);
|
||||
if (!typeEquals(searchNeedle.type, valueT)) lowerer.badType(argNode, lowerer.typeOf(argNode));
|
||||
const helper = arraySearchHelper(lowerer, method, elem, valueT, loc);
|
||||
return { kind: "call", callee: helper, args: [receiver, searchNeedle, fromIndex], type: method === "indexOf" ? F64 : BOOL, loc };
|
||||
const directNeedle = method === "lastIndexOf" && elem.kind !== "union" && typeEquals(needle.type, elem);
|
||||
const searchNeedle = directNeedle ? needle : lowerer.coerceInto(argNode, needle, valueT);
|
||||
// Checked dynamic extraction can copy a composite. Its new identity cannot
|
||||
// be compared to an array element as though it were the original value.
|
||||
if (method === "lastIndexOf" && searchNeedle.kind === "dynCheck" && (elem.kind === "record" || elem.kind === "array")) {
|
||||
lowerer.noLowering(".lastIndexOf with a checked dynamic reference needle", call);
|
||||
}
|
||||
if (!directNeedle && !typeEquals(searchNeedle.type, valueT)) lowerer.badType(argNode, lowerer.typeOf(argNode));
|
||||
const helper = arraySearchHelper(lowerer, method, elem, valueT, searchNeedle.type, directNeedle, fromIndex.type, call.arguments[1] ?? call, loc);
|
||||
return { kind: "call", callee: helper, args: [receiver, searchNeedle, fromIndex], type: method === "includes" ? BOOL : F64, loc };
|
||||
}
|
||||
|
||||
/** SameValueZero over a static value union. `unionEq` supplies strict
|
||||
@@ -723,36 +734,41 @@ function arraySearchEquality(
|
||||
|
||||
function arraySearchHelper(
|
||||
lowerer: Lowerer,
|
||||
method: "indexOf" | "includes",
|
||||
method: "indexOf" | "lastIndexOf" | "includes",
|
||||
elem: IrType,
|
||||
valueT: IrType & { kind: "union" },
|
||||
needleT: IrType,
|
||||
directNeedle: boolean,
|
||||
fromT: IrType,
|
||||
fromNode: ts.Expression,
|
||||
loc: SrcLoc,
|
||||
): string {
|
||||
const key = `${method}:${typeKey(elem)}:${typeKey(valueT)}`;
|
||||
const key = `${method}:${typeKey(elem)}:${typeKey(valueT)}:${typeKey(needleT)}:${directNeedle}:${typeKey(fromT)}`;
|
||||
const existing = lowerer.arrHofHelpers.get(key);
|
||||
if (existing) return existing;
|
||||
const name = `%arr.${method}.${lowerer.arrHofHelpers.size}`;
|
||||
lowerer.arrHofHelpers.set(key, name);
|
||||
const arrT = arrayOf(elem);
|
||||
const resultT = method === "indexOf" ? F64 : BOOL;
|
||||
const resultT = method === "includes" ? BOOL : F64;
|
||||
const arrRef = varRef("a.0", arrT, loc);
|
||||
const indexRef = varRef("i.0", F64, loc);
|
||||
const needleRef = varRef("needle.0", valueT, loc);
|
||||
const needleRef = varRef("needle.0", needleT, loc);
|
||||
const stateRef = varRef("state.0", F64, loc);
|
||||
const nextRef = varRef("next.0", F64, loc);
|
||||
const fromRef = varRef("from.0", F64, loc);
|
||||
const fromRef = varRef("from.0", fromT, loc);
|
||||
const numberRef = varRef("number.0", F64, loc);
|
||||
const integerRef = varRef("integer.0", F64, loc);
|
||||
// ToIntegerOrInfinity, followed by the relative start index. Keeping the
|
||||
// length read inside the helper preserves argument effects before search.
|
||||
// Snapshot length before coercing fromIndex, then apply ToIntegerOrInfinity.
|
||||
const number = positionNumber(lowerer, fromRef, numLit(0, loc), fromNode, "array search position");
|
||||
const integer: IrExpr = {
|
||||
kind: "ternary",
|
||||
cond: { kind: "libCall", fn: "num.isNaN", args: [fromRef], type: BOOL, loc },
|
||||
cond: { kind: "libCall", fn: "num.isNaN", args: [numberRef], type: BOOL, loc },
|
||||
then: numLit(0, loc),
|
||||
else_: { kind: "libCall", fn: "math.trunc", args: [fromRef], type: F64, loc },
|
||||
else_: { kind: "libCall", fn: "math.trunc", args: [numberRef], type: F64, loc },
|
||||
type: F64,
|
||||
loc,
|
||||
};
|
||||
const start: IrExpr = {
|
||||
const forwardStart: IrExpr = {
|
||||
kind: "ternary",
|
||||
cond: { kind: "bin", op: "<", left: integerRef, right: numLit(0, loc), type: BOOL, loc },
|
||||
then: {
|
||||
@@ -765,10 +781,24 @@ function arraySearchHelper(
|
||||
type: F64,
|
||||
loc,
|
||||
};
|
||||
const start: IrExpr = method === "lastIndexOf" ? {
|
||||
kind: "ternary",
|
||||
cond: { kind: "bin", op: "<", left: integerRef, right: numLit(0, loc), type: BOOL, loc },
|
||||
then: { kind: "bin", op: "+", left: varRef("n.0", F64, loc), right: integerRef, type: F64, loc },
|
||||
else_: {
|
||||
kind: "libCall", fn: "math.min",
|
||||
args: [
|
||||
{ kind: "bin", op: "+", left: integerRef, right: numLit(0, loc), type: F64, loc },
|
||||
{ kind: "bin", op: "-", left: varRef("n.0", F64, loc), right: numLit(1, loc), type: F64, loc },
|
||||
],
|
||||
type: F64, loc,
|
||||
},
|
||||
type: F64, loc,
|
||||
} : forwardStart;
|
||||
const rawRead: IrExpr = { kind: "arrayGet", arr: arrRef, index: indexRef, type: elem, loc };
|
||||
const missing = lowerer.wrappedUndefined(valueT, loc);
|
||||
if (!missing) throw new InternalCompilerError("array search helper needs an undefined arm");
|
||||
const value: IrExpr = {
|
||||
const optionalValue: IrExpr = {
|
||||
kind: "ternary",
|
||||
cond: { kind: "bin", op: "===", left: stateRef, right: numLit(1, loc), type: BOOL, loc },
|
||||
then: typeEquals(elem, valueT) ? rawRead : lowerer.coerceToExpected(rawRead, valueT),
|
||||
@@ -776,12 +806,14 @@ function arraySearchHelper(
|
||||
type: valueT,
|
||||
loc,
|
||||
};
|
||||
const equal = arraySearchEquality(lowerer, varRef("value.0", valueT, loc), needleRef, method === "includes", loc);
|
||||
const match = method === "indexOf"
|
||||
const value = directNeedle ? rawRead : optionalValue;
|
||||
const readT = directNeedle ? elem : valueT;
|
||||
const equal = arraySearchEquality(lowerer, varRef("value.0", readT, loc), needleRef, method === "includes", loc);
|
||||
const match = method !== "includes"
|
||||
? {
|
||||
kind: "logical" as const,
|
||||
op: "&&" as const,
|
||||
left: { kind: "bin" as const, op: "!==" as const, left: stateRef, right: numLit(0, loc), type: BOOL, loc },
|
||||
left: { kind: "bin" as const, op: directNeedle ? "===" as const : "!==" as const, left: stateRef, right: numLit(directNeedle ? 1 : 0, loc), type: BOOL, loc },
|
||||
right: equal,
|
||||
type: BOOL,
|
||||
loc,
|
||||
@@ -789,7 +821,7 @@ function arraySearchHelper(
|
||||
: equal;
|
||||
const undefinedTag = lowerer.armTag(valueT.unionId, UNDEFINED_T);
|
||||
if (undefinedTag < 0) throw new InternalCompilerError("array search helper needs an undefined tag");
|
||||
const needleIsUndefined: IrExpr = {
|
||||
const needleIsUndefined: IrExpr = directNeedle ? boolLit(false, loc) : {
|
||||
kind: "unionIsTag",
|
||||
unionId: valueT.unionId,
|
||||
tag: undefinedTag,
|
||||
@@ -801,7 +833,7 @@ function arraySearchHelper(
|
||||
const advance: IrStmt = {
|
||||
kind: "assign",
|
||||
localId: "i.0",
|
||||
value: { kind: "bin", op: "+", left: indexRef, right: numLit(1, loc), type: F64, loc },
|
||||
value: { kind: "bin", op: method === "lastIndexOf" ? "-" : "+", left: indexRef, right: numLit(1, loc), type: F64, loc },
|
||||
loc,
|
||||
};
|
||||
const visitPresent: IrStmt[] = [
|
||||
@@ -809,13 +841,13 @@ function arraySearchHelper(
|
||||
{
|
||||
kind: "if",
|
||||
cond: match,
|
||||
then: [{ kind: "return", value: method === "indexOf" ? indexRef : boolLit(true, loc), loc }],
|
||||
then: [{ kind: "return", value: method === "includes" ? boolLit(true, loc) : indexRef, loc }],
|
||||
else_: null,
|
||||
loc,
|
||||
},
|
||||
advance,
|
||||
];
|
||||
const skipHole: IrStmt[] = [
|
||||
const skipHole: IrStmt[] = method === "lastIndexOf" ? [advance] : [
|
||||
{
|
||||
kind: "varDecl",
|
||||
localId: "next.0",
|
||||
@@ -841,32 +873,36 @@ function arraySearchHelper(
|
||||
name,
|
||||
params: [
|
||||
{ localId: "a.0", name: "a", type: arrT },
|
||||
{ localId: "needle.0", name: "needle", type: valueT },
|
||||
{ localId: "from.0", name: "from", type: F64 },
|
||||
{ localId: "needle.0", name: "needle", type: needleT },
|
||||
{ localId: "from.0", name: "from", type: fromT },
|
||||
],
|
||||
returnType: resultT,
|
||||
locals: [
|
||||
{ id: "a.0", name: "a", type: arrT, mutable: true },
|
||||
{ id: "needle.0", name: "needle", type: valueT, mutable: true },
|
||||
{ id: "from.0", name: "from", type: F64, mutable: false },
|
||||
{ id: "needle.0", name: "needle", type: needleT, mutable: true },
|
||||
{ id: "from.0", name: "from", type: fromT, mutable: false },
|
||||
{ id: "number.0", name: "number", type: F64, mutable: false },
|
||||
{ id: "integer.0", name: "integer", type: F64, mutable: false },
|
||||
{ id: "n.0", name: "n", type: F64, mutable: false },
|
||||
{ id: "i.0", name: "i", type: F64, mutable: true },
|
||||
{ id: "state.0", name: "state", type: F64, mutable: false },
|
||||
{ id: "next.0", name: "next", type: F64, mutable: false },
|
||||
{ id: "value.0", name: "value", type: valueT, mutable: false },
|
||||
{ id: "value.0", name: "value", type: readT, mutable: false },
|
||||
],
|
||||
body: [
|
||||
readLenStmt(arrT, loc),
|
||||
{ kind: "varDecl", localId: "number.0", init: number, loc },
|
||||
{ kind: "varDecl", localId: "integer.0", init: integer, loc },
|
||||
{ kind: "varDecl", localId: "i.0", init: start, loc },
|
||||
{
|
||||
kind: "while",
|
||||
cond: { kind: "bin", op: "<", left: indexRef, right: varRef("n.0", F64, loc), type: BOOL, loc },
|
||||
cond: method === "lastIndexOf"
|
||||
? { kind: "bin", op: ">=", left: indexRef, right: numLit(0, loc), type: BOOL, loc }
|
||||
: { kind: "bin", op: "<", left: indexRef, right: varRef("n.0", F64, loc), type: BOOL, loc },
|
||||
body: loopBody,
|
||||
loc,
|
||||
},
|
||||
{ kind: "return", value: method === "indexOf" ? numLit(-1, loc) : boolLit(false, loc), loc },
|
||||
{ kind: "return", value: method === "includes" ? boolLit(false, loc) : numLit(-1, loc), loc },
|
||||
],
|
||||
loc,
|
||||
});
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import * as ts from "../ts7/adapter.js";
|
||||
import type { IrExpr, IrType } from "../../ir/ir.js";
|
||||
import { typeEquals } from "../../ir/ir.js";
|
||||
import type { IrExpr, IrType, SrcLoc } from "../../ir/ir.js";
|
||||
import { BOOL, F64, isUnitType, typeEquals } from "../../ir/ir.js";
|
||||
import { numLit, strLit } from "../../ir/build.js";
|
||||
import type { Lowerer } from "./lowerer.js";
|
||||
import { isSafeToDiscard } from "./expressions/evaluation-safety.js";
|
||||
|
||||
@@ -42,6 +43,19 @@ export function defaultAfterUndefined(value: IrExpr, defaultValue: IrExpr): IrEx
|
||||
};
|
||||
}
|
||||
|
||||
/** Convert an omitted or supplied string-search value after preserving its effects. */
|
||||
export function lowerStringSearchArgument(lowerer: Lowerer, node: ts.Expression | undefined, loc: SrcLoc): IrExpr {
|
||||
const absent = strLit("undefined", loc);
|
||||
if (!node) return absent;
|
||||
const undefinedArg = lowerStaticallyUndefinedArgument(lowerer, node);
|
||||
if (undefinedArg) return defaultAfterUndefined(undefinedArg, absent);
|
||||
const value = lowerer.lowerExpr(node);
|
||||
if (isUnitType(value.type)) {
|
||||
return defaultAfterUndefined(value, strLit(value.type.kind === "nullT" ? "null" : "undefined", loc));
|
||||
}
|
||||
return lowerer.ensureString(value, node);
|
||||
}
|
||||
|
||||
/** Lower an optional argument, applying its default only to the undefined arm. */
|
||||
export function lowerOptionalArgument(
|
||||
lowerer: Lowerer,
|
||||
@@ -64,3 +78,57 @@ export function lowerOptionalArgument(
|
||||
}
|
||||
return lowerer.coerceInto(node, value, expected);
|
||||
}
|
||||
|
||||
/** Complete an omitted or statically undefined position without dropping argument effects. */
|
||||
export function lowerPositionArgument(lowerer: Lowerer, node: ts.Expression | undefined, defaultValue: IrExpr): IrExpr {
|
||||
if (!node) return defaultValue;
|
||||
const undefinedArg = lowerStaticallyUndefinedArgument(lowerer, node);
|
||||
if (undefinedArg) return defaultAfterUndefined(undefinedArg, defaultValue);
|
||||
const value = lowerer.lowerExpr(node);
|
||||
if (isUnitType(value.type) || value.type.kind === "void") {
|
||||
return defaultAfterUndefined(value, value.type.kind === "nullT" ? numLit(0, value.loc) : defaultValue);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
/** Convert a stabilized position after the method has evaluated its arguments. */
|
||||
export function positionNumber(
|
||||
lowerer: Lowerer,
|
||||
value: IrExpr,
|
||||
defaultValue: IrExpr,
|
||||
node: ts.Expression,
|
||||
subject: string,
|
||||
): IrExpr {
|
||||
const loc = value.loc;
|
||||
switch (value.type.kind) {
|
||||
case "f64": return value;
|
||||
case "string": return { kind: "libCall", fn: "num.fromString", args: [value], type: F64, loc };
|
||||
case "bool": return { kind: "ternary", cond: value, then: numLit(1, loc), else_: numLit(0, loc), type: F64, loc };
|
||||
case "nullT": return numLit(0, loc);
|
||||
case "undefinedT": return defaultValue;
|
||||
case "jsval": return { kind: "jsExit", value, type: F64, loc };
|
||||
case "union": {
|
||||
const unionId = value.type.unionId;
|
||||
const arms = lowerer.unions.get(unionId)!.arms;
|
||||
let result: IrExpr = defaultValue;
|
||||
for (let tag = arms.length - 1; tag >= 0; tag--) {
|
||||
const narrowed: IrExpr = { kind: "unionNarrow", unionId, tag, value, type: arms[tag]!, loc };
|
||||
const converted = positionNumber(lowerer, narrowed, defaultValue, node, subject);
|
||||
result = tag === arms.length - 1 ? converted : {
|
||||
kind: "ternary",
|
||||
cond: { kind: "unionIsTag", unionId, tag, value, negated: false, type: BOOL, loc },
|
||||
then: converted, else_: result, type: F64, loc,
|
||||
};
|
||||
}
|
||||
return result;
|
||||
}
|
||||
case "dyn": return {
|
||||
kind: "ternary",
|
||||
cond: { kind: "dynTest", test: "undefined", value, type: BOOL, loc },
|
||||
then: defaultValue,
|
||||
else_: { kind: "libCall", fn: "dyn.toNumberCoerce", args: [value], type: F64, loc },
|
||||
type: F64, loc,
|
||||
};
|
||||
default: return lowerer.noLowering(`${subject} of '${lowerer.fmt(value.type)}' values`, node);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -315,6 +315,7 @@ export const ARRAY_METHODS = new Set([
|
||||
"reduce",
|
||||
"reduceRight",
|
||||
"indexOf",
|
||||
"lastIndexOf",
|
||||
"includes",
|
||||
"join",
|
||||
"slice",
|
||||
@@ -381,13 +382,13 @@ export const STR_METHODS: Record<
|
||||
> = {
|
||||
charCodeAt: { method: "charCodeAt", result: F64, minArgs: 0, maxArgs: 1 },
|
||||
charAt: { method: "charAt", result: STRING, minArgs: 0, maxArgs: 1 },
|
||||
indexOf: { method: "indexOf", result: F64, minArgs: 1, maxArgs: 2 },
|
||||
indexOf: { method: "indexOf", result: F64, minArgs: 0, maxArgs: 2 },
|
||||
// includes with a position argument is indexOf's clamp exactly (the
|
||||
// spec routes both through StringIndexOf) — the emitter composes
|
||||
// scr_str_index_of(...) != -1 for the two-argument form.
|
||||
includes: { method: "includes", result: BOOL, minArgs: 1, maxArgs: 2 },
|
||||
startsWith: { method: "startsWith", result: BOOL, minArgs: 1, maxArgs: 2 },
|
||||
endsWith: { method: "endsWith", result: BOOL, minArgs: 1, maxArgs: 2 },
|
||||
includes: { method: "includes", result: BOOL, minArgs: 0, maxArgs: 2 },
|
||||
startsWith: { method: "startsWith", result: BOOL, minArgs: 0, maxArgs: 2 },
|
||||
endsWith: { method: "endsWith", result: BOOL, minArgs: 0, maxArgs: 2 },
|
||||
slice: { method: "slice", result: STRING, minArgs: 0, maxArgs: 2 },
|
||||
// substring: slice's clamp-and-swap sibling (negatives clamp to 0
|
||||
// instead of counting from the end; start > end swaps).
|
||||
|
||||
@@ -223,6 +223,8 @@ export interface CompileBaseOptions {
|
||||
/** Native optimization posture. Release is the shipped -O2 default; dev
|
||||
* uses -O0 and stable multi-TU object caching for large LLVM programs. */
|
||||
optimization?: "release" | "dev";
|
||||
/** Remove symbol/debug payload from an executable at link time. */
|
||||
strip?: boolean;
|
||||
/** Windows PE executable subsystem. Console is the default; GUI suppresses
|
||||
* automatic console-window creation. Only valid for Windows executables. */
|
||||
windowsSubsystem?: WindowsSubsystem;
|
||||
@@ -1077,6 +1079,7 @@ async function compileExecutableNative(
|
||||
sanitize: boolean,
|
||||
ffi: FfiProfile | null,
|
||||
windowsSubsystem?: WindowsSubsystem,
|
||||
strip?: boolean,
|
||||
programSplit: ReturnType<typeof splitLlvmProgram> = null,
|
||||
programObjectDependencies: readonly NativeArtifactDependency[] = [],
|
||||
onArtifactReady?: NonNullable<Parameters<typeof compileExternalC>[0]["onArtifactReady"]>,
|
||||
@@ -1093,6 +1096,7 @@ async function compileExecutableNative(
|
||||
features,
|
||||
ffi,
|
||||
optimization: features.optimization ?? "release",
|
||||
...(strip ? { strip: true } : {}),
|
||||
...(windowsSubsystem === undefined ? {} : { windowsSubsystem }),
|
||||
programObjectDependencies,
|
||||
});
|
||||
@@ -1130,6 +1134,7 @@ async function compileExecutableNative(
|
||||
outPath,
|
||||
cacheIdentity: "scriptc-generated-v1",
|
||||
...(features.optimization === "dev" ? { optimization: "dev" as const } : {}),
|
||||
...(strip ? { strip: true } : {}),
|
||||
...(windowsSubsystem === undefined ? {} : { windowsSubsystem }),
|
||||
...(effectiveProgramSplit === null
|
||||
? {}
|
||||
@@ -1243,6 +1248,13 @@ async function compileTracked(
|
||||
sourceTexts: new Map(),
|
||||
};
|
||||
}
|
||||
if (opts.strip === true && outputKind !== "exe") {
|
||||
return {
|
||||
ok: false,
|
||||
diagnostics: [nativeCodegenDiag("SC3002", "--strip is only supported for executable output", entryPath)],
|
||||
sourceTexts: new Map(),
|
||||
};
|
||||
}
|
||||
if (opts.nativeLinkInfo === true && outputKind !== "obj") {
|
||||
return {
|
||||
ok: false,
|
||||
@@ -1387,6 +1399,7 @@ async function compileTracked(
|
||||
dynamic: opts.dynamic ?? false,
|
||||
backend: opts.backend ?? "auto",
|
||||
...(opts.optimization === "dev" ? { optimization: "dev" as const } : {}),
|
||||
...(opts.strip ? { strip: true as const } : {}),
|
||||
...(opts.windowsSubsystem === "gui" ? { windowsSubsystem: "gui" as const } : {}),
|
||||
npmStatic: opts.npmStatic ?? null,
|
||||
ffiProfile:
|
||||
@@ -1486,6 +1499,7 @@ async function compileTracked(
|
||||
opts.sanitize ?? false,
|
||||
ffi,
|
||||
opts.windowsSubsystem,
|
||||
opts.strip,
|
||||
null,
|
||||
nativeProgramObject?.dependencies,
|
||||
opts.nativeProgramObject === true ? undefined : async ({ dependencies }) => {
|
||||
@@ -1775,6 +1789,7 @@ async function compileTracked(
|
||||
opts.sanitize ?? false,
|
||||
ffi,
|
||||
opts.windowsSubsystem,
|
||||
opts.strip,
|
||||
programSplit,
|
||||
nativeProgramObject?.dependencies,
|
||||
opts.nativeProgramObject === true ? undefined : async ({ dependencies }) => {
|
||||
|
||||
@@ -2182,6 +2182,12 @@
|
||||
"name": "Array.prototype.join",
|
||||
"status": "static"
|
||||
},
|
||||
{
|
||||
"id": "stdlib.array.lastIndexOf",
|
||||
"kind": "stdlib",
|
||||
"name": "Array.prototype.lastIndexOf",
|
||||
"status": "static"
|
||||
},
|
||||
{
|
||||
"id": "stdlib.array.map",
|
||||
"kind": "stdlib",
|
||||
@@ -3505,21 +3511,21 @@
|
||||
"kind": "stdlib",
|
||||
"name": "string.prototype.endsWith",
|
||||
"status": "static",
|
||||
"note": "the lowered call form takes 1 to 2 arguments"
|
||||
"note": "the lowered call form takes 0 to 2 arguments"
|
||||
},
|
||||
{
|
||||
"id": "stdlib.string.includes",
|
||||
"kind": "stdlib",
|
||||
"name": "string.prototype.includes",
|
||||
"status": "static",
|
||||
"note": "the lowered call form takes 1 to 2 arguments"
|
||||
"note": "the lowered call form takes 0 to 2 arguments"
|
||||
},
|
||||
{
|
||||
"id": "stdlib.string.indexOf",
|
||||
"kind": "stdlib",
|
||||
"name": "string.prototype.indexOf",
|
||||
"status": "static",
|
||||
"note": "the lowered call form takes 1 to 2 arguments"
|
||||
"note": "the lowered call form takes 0 to 2 arguments"
|
||||
},
|
||||
{
|
||||
"id": "stdlib.string.isWellFormed",
|
||||
@@ -3582,7 +3588,7 @@
|
||||
"kind": "stdlib",
|
||||
"name": "string.prototype.startsWith",
|
||||
"status": "static",
|
||||
"note": "the lowered call form takes 1 to 2 arguments"
|
||||
"note": "the lowered call form takes 0 to 2 arguments"
|
||||
},
|
||||
{
|
||||
"id": "stdlib.string.substring",
|
||||
|
||||
@@ -6925,15 +6925,27 @@
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/3026-string-search-needle-coercion.js": {
|
||||
"<repo>/tests/corpus/3026-array-last-index-of.js": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/3026-string-search-needle-coercion.js"
|
||||
"<repo>/tests/corpus/3026-array-last-index-of.js"
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/3027-arguments-with-parameters.js": {
|
||||
"<repo>/tests/corpus/3027-array-last-index-of-records.ts": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/3027-arguments-with-parameters.js"
|
||||
"<repo>/tests/corpus/3027-array-last-index-of-records.ts"
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/3028-string-search-dynamic-needle.js": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/3028-string-search-dynamic-needle.js"
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/3029-string-search-union-needle.js": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/3029-string-search-union-needle.js"
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
@@ -6943,6 +6955,36 @@
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/3030-array-search-no-arg.js": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/3030-array-search-no-arg.js"
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/3031-string-last-index-search-value.js": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/3031-string-last-index-search-value.js"
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/3032-string-search-value.js": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/3032-string-search-value.js"
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/3033-string-search-needle-coercion.js": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/3033-string-search-needle-coercion.js"
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/3034-arguments-with-parameters.js": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/3034-arguments-with-parameters.js"
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/304-compound-incdec.ts": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/304-compound-incdec.ts"
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
let trace = "";
|
||||
function receiver() { trace += "r"; return [0, 1, 0]; }
|
||||
function needle() { trace += "n"; return 0; }
|
||||
function position() { trace += "p"; return "1.9"; }
|
||||
console.log(receiver().lastIndexOf(needle(), position()), trace);
|
||||
trace = "";
|
||||
console.log(receiver().lastIndexOf(needle(), void position()), trace);
|
||||
|
||||
const numbers = [0, 1, 0, 1, 0];
|
||||
/** @type {(number | string | boolean | null | undefined)[]} */
|
||||
const positions = [undefined, null, false, true, "", "2.9", "bad", NaN, -Infinity, -3, -1, -0, 0, 1, 2, 4, Infinity, 99];
|
||||
for (const value of positions) {
|
||||
console.log(numbers.lastIndexOf(0, value), numbers.lastIndexOf(1, value));
|
||||
}
|
||||
console.log(numbers.lastIndexOf(0), numbers.lastIndexOf(0, undefined));
|
||||
console.log([NaN, 0, -0, NaN].lastIndexOf(NaN), [NaN, 0, -0].lastIndexOf(0));
|
||||
console.log([undefined].lastIndexOf(undefined), [null, undefined].lastIndexOf(null));
|
||||
|
||||
const sparse = [1, , undefined, 1];
|
||||
console.log(sparse.lastIndexOf(undefined), sparse.lastIndexOf(undefined, 1), sparse.lastIndexOf(1, 2));
|
||||
@@ -0,0 +1,7 @@
|
||||
interface Item { id: number }
|
||||
const first: Item = { id: 1 };
|
||||
const second: Item = { id: 2 };
|
||||
const refs: Item[] = [first, second, first];
|
||||
console.log(refs.lastIndexOf(first), refs.lastIndexOf(second), refs.lastIndexOf({ id: 1 }));
|
||||
console.log(refs.lastIndexOf(first, 1), refs.lastIndexOf(second, 0));
|
||||
console.log(refs.lastIndexOf(refs[0]), refs.lastIndexOf(refs[1]));
|
||||
@@ -0,0 +1,13 @@
|
||||
function probe(needle, position) {
|
||||
const value = "ababa";
|
||||
console.log(
|
||||
value.indexOf(needle, position),
|
||||
value.includes(needle, position),
|
||||
value.startsWith(needle, position),
|
||||
value.endsWith(needle, position),
|
||||
);
|
||||
}
|
||||
|
||||
probe("ba", 1);
|
||||
probe(1, 0);
|
||||
probe(undefined, 0);
|
||||
@@ -0,0 +1,17 @@
|
||||
/** @param {string | undefined} needle */
|
||||
function probe(needle) {
|
||||
const value = "ababa";
|
||||
console.log(
|
||||
value.indexOf(needle),
|
||||
value.indexOf(needle, 1),
|
||||
value.includes(needle),
|
||||
value.includes(needle, 1),
|
||||
value.startsWith(needle),
|
||||
value.startsWith(needle, 1),
|
||||
value.endsWith(needle),
|
||||
value.endsWith(needle, 2),
|
||||
);
|
||||
}
|
||||
|
||||
probe("ba");
|
||||
probe(undefined);
|
||||
@@ -0,0 +1,18 @@
|
||||
console.log([0].includes(), [undefined].includes(), [null].includes(), [].includes());
|
||||
console.log([undefined, 0, undefined].includes());
|
||||
|
||||
const holes = [2];
|
||||
holes[2] = 2;
|
||||
console.log(holes.includes());
|
||||
|
||||
const untyped = [];
|
||||
untyped[2] = 2;
|
||||
console.log(untyped.includes());
|
||||
|
||||
const present = [undefined, 1];
|
||||
present[3] = undefined;
|
||||
console.log(present.includes());
|
||||
|
||||
let evaluations = 0;
|
||||
function receiver() { evaluations++; return [undefined, 1]; }
|
||||
console.log(receiver().includes(), evaluations);
|
||||
@@ -0,0 +1,13 @@
|
||||
console.log("undefined".lastIndexOf(), "".lastIndexOf(), "undefinedundefined".lastIndexOf());
|
||||
console.log("gnullunazzgnull".lastIndexOf(null));
|
||||
console.log("undefined".lastIndexOf(undefined), "undefined".lastIndexOf(void 0));
|
||||
console.log("truefalse".lastIndexOf(true), "42".lastIndexOf(42), "1".lastIndexOf(1n));
|
||||
|
||||
const missing = undefined;
|
||||
const nil = null;
|
||||
console.log("undefined".lastIndexOf(missing), "nullnull".lastIndexOf(nil));
|
||||
|
||||
let trace = "";
|
||||
function receiver() { trace += "r"; return "xundefined"; }
|
||||
function needleEffect() { trace += "n"; return 1; }
|
||||
console.log(receiver().lastIndexOf(void needleEffect()), trace);
|
||||
@@ -0,0 +1,36 @@
|
||||
console.log("undefined".indexOf(), "x".indexOf(), "undefined".includes(), "x".includes());
|
||||
console.log("undefined".startsWith(), "x".startsWith(), "xundefined".endsWith(), "x".endsWith());
|
||||
console.log("undefined".indexOf(undefined), "undefined".includes(void 0));
|
||||
console.log("undefined".startsWith(undefined), "undefined".endsWith(undefined));
|
||||
console.log("anullb".indexOf(null), "anullb".includes(null));
|
||||
console.log("nulla".startsWith(null), "anull".endsWith(null));
|
||||
console.log("truefalse".indexOf(true), "truefalse".includes(false));
|
||||
console.log("truefalse".startsWith(true), "truefalse".endsWith(false));
|
||||
console.log("42".indexOf(42), "42".includes(42), "42".startsWith(42), "42".endsWith(42));
|
||||
console.log("1".indexOf(1n), "1".includes(1n), "1".startsWith(1n), "1".endsWith(1n));
|
||||
|
||||
/** @param {string | number | boolean | null | undefined} value */
|
||||
function search(value) {
|
||||
const text = "undefined-null-1-true";
|
||||
console.log(text.indexOf(value), text.includes(value), text.startsWith(value), text.endsWith(value));
|
||||
}
|
||||
search(undefined);
|
||||
search(null);
|
||||
search(1);
|
||||
search(true);
|
||||
search("true");
|
||||
|
||||
let trace = "";
|
||||
function receiver() { trace += "r"; return "undefined"; }
|
||||
function needleEffect() { trace += "n"; return 1; }
|
||||
console.log(receiver().indexOf(void needleEffect()), trace);
|
||||
trace = "";
|
||||
console.log(receiver().includes(void needleEffect()), trace);
|
||||
trace = "";
|
||||
console.log(receiver().startsWith(void needleEffect()), trace);
|
||||
trace = "";
|
||||
console.log(receiver().endsWith(void needleEffect()), trace);
|
||||
|
||||
function nullEffect() { trace += "n"; return null; }
|
||||
trace = "";
|
||||
console.log(receiver().indexOf(nullEffect()), trace);
|
||||
@@ -6,6 +6,7 @@
|
||||
"snapshotSha256": "584d260eaa9aef0c42d18b6b03e8b3755ecd54bdf43e01701f5b3eca3043c4c3",
|
||||
"testFiles": 53597,
|
||||
"tests": [
|
||||
"test/built-ins/Array/prototype/includes/no-arg.js",
|
||||
"test/built-ins/Array/prototype/indexOf/15.4.4.14-4-1.js",
|
||||
"test/built-ins/Array/prototype/indexOf/15.4.4.14-5-13.js",
|
||||
"test/built-ins/Array/prototype/indexOf/15.4.4.14-5-14.js",
|
||||
@@ -29,6 +30,14 @@
|
||||
"test/built-ins/Array/prototype/indexOf/15.4.4.14-9-b-ii-1.js",
|
||||
"test/built-ins/Array/prototype/indexOf/15.4.4.14-9-b-ii-4.js",
|
||||
"test/built-ins/Array/prototype/indexOf/fromindex-zero-conversion.js",
|
||||
"test/built-ins/Array/prototype/lastIndexOf/15.4.4.15-5-1.js",
|
||||
"test/built-ins/Array/prototype/lastIndexOf/15.4.4.15-5-2.js",
|
||||
"test/built-ins/Array/prototype/lastIndexOf/15.4.4.15-5-3.js",
|
||||
"test/built-ins/Array/prototype/lastIndexOf/15.4.4.15-5-4.js",
|
||||
"test/built-ins/Array/prototype/lastIndexOf/15.4.4.15-5-5.js",
|
||||
"test/built-ins/Array/prototype/lastIndexOf/15.4.4.15-5-6.js",
|
||||
"test/built-ins/Array/prototype/lastIndexOf/15.4.4.15-5-7.js",
|
||||
"test/built-ins/Array/prototype/lastIndexOf/fromindex-zero-conversion.js",
|
||||
"test/built-ins/BigInt/prototype/toString/default-radix.js",
|
||||
"test/built-ins/Math/abs/S15.8.2.1_A1.js",
|
||||
"test/built-ins/Math/abs/S15.8.2.1_A2.js",
|
||||
@@ -54,6 +63,12 @@
|
||||
"test/built-ins/String/prototype/includes/coerced-values-of-position.js",
|
||||
"test/built-ins/String/prototype/includes/return-false-with-out-of-bounds-position.js",
|
||||
"test/built-ins/String/prototype/includes/return-true-if-searchstring-is-empty.js",
|
||||
"test/built-ins/String/prototype/indexOf/S15.5.4.7_A1_T4.js",
|
||||
"test/built-ins/String/prototype/indexOf/S15.5.4.7_A1_T5.js",
|
||||
"test/built-ins/String/prototype/indexOf/S15.5.4.7_A1_T7.js",
|
||||
"test/built-ins/String/prototype/lastIndexOf/S15.5.4.8_A1_T4.js",
|
||||
"test/built-ins/String/prototype/lastIndexOf/S15.5.4.8_A1_T5.js",
|
||||
"test/built-ins/String/prototype/lastIndexOf/S15.5.4.8_A1_T7.js",
|
||||
"test/built-ins/String/prototype/lastIndexOf/not-a-substring.js",
|
||||
"test/built-ins/String/prototype/slice/S15.5.4.13_A1_T4.js",
|
||||
"test/built-ins/String/prototype/startsWith/out-of-bounds-position.js",
|
||||
@@ -87,6 +102,7 @@
|
||||
"LICENSE": "4dd9244dfe8197c75348c4b24ab53d29d3b1cfad143ac76b5a3d8942aa354ce0",
|
||||
"harness/assert.js": "206e274ca325eb8a652e3911c3fbd090e2480d11ed7579dc17a5d17a2360ed48",
|
||||
"harness/sta.js": "1930c54af79455c484799f43e9a28e2b2f15c40d0917c9941ca54e26db243f35",
|
||||
"test/built-ins/Array/prototype/includes/no-arg.js": "e38e7db529094b08ac13cc8a53cdbf59b5b2537711922af9bf8b1178f615e511",
|
||||
"test/built-ins/Array/prototype/indexOf/15.4.4.14-4-1.js": "ef762d69cbdf878821a6f998064590c2c19aa9e3db297968a5db02a6f5cd455a",
|
||||
"test/built-ins/Array/prototype/indexOf/15.4.4.14-5-13.js": "3c1ebd36e0e4657c42abbb76a707546a7b46ad954d3e569a84b796695d10da02",
|
||||
"test/built-ins/Array/prototype/indexOf/15.4.4.14-5-14.js": "7bb87d45a34bffcc61b39131c30289d6da7b51cd6b2a3db4af38674864fbd15e",
|
||||
@@ -110,6 +126,14 @@
|
||||
"test/built-ins/Array/prototype/indexOf/15.4.4.14-9-b-ii-1.js": "db248a7b40b4285a0331fa7eaee0a78e6a9af4e57294c9ce9e89fe392845ca2e",
|
||||
"test/built-ins/Array/prototype/indexOf/15.4.4.14-9-b-ii-4.js": "07b115c3b90dfb5bcb9e38990fd1db57720c4ac423e052082828ed8ccb6b4214",
|
||||
"test/built-ins/Array/prototype/indexOf/fromindex-zero-conversion.js": "ababfd2ad12b4099f9d2108ba6e58ff33feb358522f3bbd4ddde5144cbf0bc7f",
|
||||
"test/built-ins/Array/prototype/lastIndexOf/15.4.4.15-5-1.js": "58e752c5eda47f23bdc281f13ac7f3d3df37a0051b0ccd194198987796c8a841",
|
||||
"test/built-ins/Array/prototype/lastIndexOf/15.4.4.15-5-2.js": "70cf040ba435efad144dcdbab8dc29547174ea3af9a7a5750eb0b0939110f497",
|
||||
"test/built-ins/Array/prototype/lastIndexOf/15.4.4.15-5-3.js": "448016548d98ed3c4cefb81f0d995cd12e3d9d13fd90a5d119dd072108979419",
|
||||
"test/built-ins/Array/prototype/lastIndexOf/15.4.4.15-5-4.js": "888ef295cf236543663ac873c2ca3c9c43e9b9c9a15cc35e4757d34b19356ca9",
|
||||
"test/built-ins/Array/prototype/lastIndexOf/15.4.4.15-5-5.js": "599f01a3f1d5bfa8a070fc7839620c0b694e18730ba13c013ce9235554fa9ba2",
|
||||
"test/built-ins/Array/prototype/lastIndexOf/15.4.4.15-5-6.js": "d389d03867aef471519b23d75bf6b24e68038a91de9f4489ac0d08da9f393bbb",
|
||||
"test/built-ins/Array/prototype/lastIndexOf/15.4.4.15-5-7.js": "5afb53c527ddb49da7a505af67cd59ad49d8b47d5af9280ed0e5578672a94c40",
|
||||
"test/built-ins/Array/prototype/lastIndexOf/fromindex-zero-conversion.js": "8231b818adf4123a4e5c9b65206b0b72984894b825319da669b99f820c75f963",
|
||||
"test/built-ins/BigInt/prototype/toString/default-radix.js": "d901ef7e3fab5b8c528cb60a12c80145a6c5b7fbd02f829fe657259eddbe711a",
|
||||
"test/built-ins/Math/abs/S15.8.2.1_A1.js": "c5b508cdc6130c8670e8ac6d822ca93e5dbead7566543cb65b793602b7203d7c",
|
||||
"test/built-ins/Math/abs/S15.8.2.1_A2.js": "a625e117f7858319c2ff25b959539266a6f29473925bb47ad5d8bbfe88e7b31e",
|
||||
@@ -135,6 +159,12 @@
|
||||
"test/built-ins/String/prototype/includes/coerced-values-of-position.js": "292b6f29f5f223027deb54dafe62726790e2a61ddf2eb5e5036688b7088f105f",
|
||||
"test/built-ins/String/prototype/includes/return-false-with-out-of-bounds-position.js": "4e65326e88ae9b915771fcc70c52fd7e4aa1f7a6aa8ca047010806555b57d668",
|
||||
"test/built-ins/String/prototype/includes/return-true-if-searchstring-is-empty.js": "c5552ee08afae1dd6147d388e987a1083571d2faf18ace56b0324204d1ea50b2",
|
||||
"test/built-ins/String/prototype/indexOf/S15.5.4.7_A1_T4.js": "dee63261be6e97a1a42a9267c98b21144489caf800d6dcc3e93bde61c2cf5103",
|
||||
"test/built-ins/String/prototype/indexOf/S15.5.4.7_A1_T5.js": "b3e357871ed5fee11206e1c7ecef7c7cc094a3a2a37eacbcfbdaec600ccca754",
|
||||
"test/built-ins/String/prototype/indexOf/S15.5.4.7_A1_T7.js": "31ee4085c1ab60c5ace3f4c4adfca8afdbc6b35d029f843fc8041d38f31a9435",
|
||||
"test/built-ins/String/prototype/lastIndexOf/S15.5.4.8_A1_T4.js": "db9a202295d7f2f48cb023cf6774e3e23084e05dbb9f1071f4aa7e3bd93710b3",
|
||||
"test/built-ins/String/prototype/lastIndexOf/S15.5.4.8_A1_T5.js": "fc3de1c70005ef91a3ab573220e0660255a33cf1bcf8d169d7845622747f02fe",
|
||||
"test/built-ins/String/prototype/lastIndexOf/S15.5.4.8_A1_T7.js": "d9392644874a8a2ffb6549f52aa2447d6d03c3e43762008fc6a32c18d35dea6a",
|
||||
"test/built-ins/String/prototype/lastIndexOf/not-a-substring.js": "1fa0e88ffe6a342e6204c925918ae6fc29b003d47007210fc377c24bc56bd41d",
|
||||
"test/built-ins/String/prototype/slice/S15.5.4.13_A1_T4.js": "1f67b7089a1e53f850b6ceb962193f49d8a2c114fc37a0377ac6553108c83ac0",
|
||||
"test/built-ins/String/prototype/startsWith/out-of-bounds-position.js": "2c27bbb2505d166a6a0fbab388ca9660780c6b396e0634e87a3e855c11865cb3",
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
// Copyright (C) 2016 the V8 project authors. All rights reserved.
|
||||
// This code is governed by the BSD license found in the LICENSE file.
|
||||
|
||||
/*---
|
||||
esid: sec-array.prototype.includes
|
||||
description: no argument searches for a undefined value
|
||||
info: |
|
||||
22.1.3.11 Array.prototype.includes ( searchElement [ , fromIndex ] )
|
||||
|
||||
...
|
||||
7. Repeat, while k < len
|
||||
a. Let elementK be the result of ? Get(O, ! ToString(k)).
|
||||
b. If SameValueZero(searchElement, elementK) is true, return true.
|
||||
c. Increase k by 1.
|
||||
...
|
||||
features: [Array.prototype.includes]
|
||||
---*/
|
||||
|
||||
assert.sameValue([0].includes(), false, "[0].includes()");
|
||||
assert.sameValue([undefined].includes(), true, "[undefined].includes()");
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
// Copyright (c) 2012 Ecma International. All rights reserved.
|
||||
// This code is governed by the BSD license found in the LICENSE file.
|
||||
|
||||
/*---
|
||||
esid: sec-array.prototype.lastindexof
|
||||
description: Array.prototype.lastIndexOf when fromIndex is string
|
||||
---*/
|
||||
|
||||
var a = new Array(0, 1, 1);
|
||||
|
||||
assert.sameValue(a.lastIndexOf(1, "1"), 1, '"1" resolves to 1');
|
||||
assert.sameValue(a.lastIndexOf(1, "one"), -1, 'NaN string resolves to 01');
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
// Copyright (c) 2012 Ecma International. All rights reserved.
|
||||
// This code is governed by the BSD license found in the LICENSE file.
|
||||
|
||||
/*---
|
||||
esid: sec-array.prototype.lastindexof
|
||||
description: Array.prototype.lastIndexOf when fromIndex is floating point number
|
||||
---*/
|
||||
|
||||
var a = new Array(1, 2, 1);
|
||||
|
||||
assert.sameValue(a.lastIndexOf(2, 1.49), 1, '1.49 resolves to 1');
|
||||
assert.sameValue(a.lastIndexOf(2, 0.51), -1, '0.51 resolves to 0');
|
||||
assert.sameValue(a.lastIndexOf(1, 0.51), 0, '0.51 resolves to 0');
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
// Copyright (c) 2012 Ecma International. All rights reserved.
|
||||
// This code is governed by the BSD license found in the LICENSE file.
|
||||
|
||||
/*---
|
||||
esid: sec-array.prototype.lastindexof
|
||||
description: Array.prototype.lastIndexOf when fromIndex is boolean
|
||||
---*/
|
||||
|
||||
var a = new Array(1, 2, 1);
|
||||
|
||||
assert.sameValue(a.lastIndexOf(2, true), 1, 'true resolves to 1');
|
||||
assert.sameValue(a.lastIndexOf(2, false), -1, 'false resolves to 0');
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
// Copyright (c) 2012 Ecma International. All rights reserved.
|
||||
// This code is governed by the BSD license found in the LICENSE file.
|
||||
|
||||
/*---
|
||||
esid: sec-array.prototype.lastindexof
|
||||
description: Array.prototype.lastIndexOf when fromIndex is undefined
|
||||
---*/
|
||||
|
||||
var a = new Array(1, 2, 1);
|
||||
|
||||
// undefined resolves to 0, no second argument resolves to len
|
||||
assert.sameValue(a.lastIndexOf(2, undefined), -1, 'a.lastIndexOf(2,undefined)');
|
||||
assert.sameValue(a.lastIndexOf(1, undefined), 0, 'a.lastIndexOf(1,undefined)');
|
||||
assert.sameValue(a.lastIndexOf(1), 2, 'a.lastIndexOf(1)');
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
// Copyright (c) 2012 Ecma International. All rights reserved.
|
||||
// This code is governed by the BSD license found in the LICENSE file.
|
||||
|
||||
/*---
|
||||
esid: sec-array.prototype.lastindexof
|
||||
description: Array.prototype.lastIndexOf when fromIndex is null
|
||||
---*/
|
||||
|
||||
var a = new Array(1, 2, 1);
|
||||
|
||||
// null resolves to 0
|
||||
assert.sameValue(a.lastIndexOf(2, null), -1, 'a.lastIndexOf(2,null)');
|
||||
assert.sameValue(a.lastIndexOf(1, null), 0, 'a.lastIndexOf(1,null)');
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
// Copyright (c) 2012 Ecma International. All rights reserved.
|
||||
// This code is governed by the BSD license found in the LICENSE file.
|
||||
|
||||
/*---
|
||||
esid: sec-array.prototype.lastindexof
|
||||
description: Array.prototype.lastIndexOf when 'fromIndex' isn't passed
|
||||
---*/
|
||||
|
||||
var arr = [0, 1, 2, 3, 4];
|
||||
//'fromIndex' will be set as 4 if not passed by default
|
||||
|
||||
assert.sameValue(arr.lastIndexOf(0), arr.lastIndexOf(0, 4), 'arr.lastIndexOf(0)');
|
||||
assert.sameValue(arr.lastIndexOf(2), arr.lastIndexOf(2, 4), 'arr.lastIndexOf(2)');
|
||||
assert.sameValue(arr.lastIndexOf(4), arr.lastIndexOf(4, 4), 'arr.lastIndexOf(4)');
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
// Copyright (c) 2012 Ecma International. All rights reserved.
|
||||
// This code is governed by the BSD license found in the LICENSE file.
|
||||
|
||||
/*---
|
||||
esid: sec-array.prototype.lastindexof
|
||||
description: >
|
||||
Array.prototype.lastIndexOf - value of 'fromIndex' is a number
|
||||
(value is 0)
|
||||
---*/
|
||||
|
||||
assert.sameValue([0, 100].lastIndexOf(100, 0), -1, 'verify fromIndex is not more than 0');
|
||||
assert.sameValue([200, 0].lastIndexOf(200, 0), 0, 'verify fromIndex is not less than 0');
|
||||
Vendored
+16
@@ -0,0 +1,16 @@
|
||||
// Copyright 2016 The V8 project authors. All rights reserved.
|
||||
// This code is governed by the license found in the LICENSE file.
|
||||
/*---
|
||||
esid: sec-array.prototype.lastindexof
|
||||
description: >
|
||||
Return +0 when fromIndex is -0 and return index refers to the first position
|
||||
info: |
|
||||
22.1.3.15 Array.prototype.lastIndexOf ( searchElement [ , fromIndex ] )
|
||||
|
||||
...
|
||||
5. If n ≥ 0, then
|
||||
a. If n is -0, let k be +0; else let k be min(n, len - 1).
|
||||
...
|
||||
---*/
|
||||
|
||||
assert.sameValue(1 / [true].lastIndexOf(true, -0), +Infinity);
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
// Copyright 2009 the Sputnik authors. All rights reserved.
|
||||
// This code is governed by the BSD license found in the LICENSE file.
|
||||
|
||||
/*---
|
||||
info: String.prototype.indexOf(searchString, position)
|
||||
es5id: 15.5.4.7_A1_T4
|
||||
description: >
|
||||
Call indexOf(searchString, position) function without arguments of
|
||||
string
|
||||
---*/
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
//CHECK#1
|
||||
//since ToString() evaluates to "" indexOf() evaluates to indexOf("",0)
|
||||
if ("".indexOf() !== -1) {
|
||||
throw new Test262Error('#1: "".indexOf() === -1. Actual: ' + ("".indexOf()));
|
||||
}
|
||||
//
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
// Copyright 2009 the Sputnik authors. All rights reserved.
|
||||
// This code is governed by the BSD license found in the LICENSE file.
|
||||
|
||||
/*---
|
||||
info: String.prototype.indexOf(searchString, position)
|
||||
es5id: 15.5.4.7_A1_T5
|
||||
description: >
|
||||
Call indexOf(searchString, position) function with null argument
|
||||
of function object
|
||||
---*/
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
//CHECK#1
|
||||
//since ToString(null) evaluates to "null" indexOf(null) evaluates to indexOf("",0)
|
||||
if (function() {
|
||||
return "gnulluna"
|
||||
}().indexOf(null) !== 1) {
|
||||
throw new Test262Error('#1: function(){return "gnulluna"}().indexOf(null) === 1. Actual: ' + function() {
|
||||
return "gnulluna"
|
||||
}().indexOf(null));
|
||||
}
|
||||
//
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
// Copyright 2009 the Sputnik authors. All rights reserved.
|
||||
// This code is governed by the BSD license found in the LICENSE file.
|
||||
|
||||
/*---
|
||||
info: String.prototype.indexOf(searchString, position)
|
||||
es5id: 15.5.4.7_A1_T7
|
||||
description: >
|
||||
Call indexOf(searchString, position) function with undefined
|
||||
argument of string object
|
||||
---*/
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
//CHECK#1
|
||||
//since ToString(undefined) evaluates to "undefined" indexOf(undefined) evaluates to indexOf("undefined",0)
|
||||
if (String("undefined").indexOf(undefined) !== 0) {
|
||||
throw new Test262Error('#1: String("undefined").indexOf(undefined) === 0. Actual: ' + String("undefined").indexOf(undefined));
|
||||
}
|
||||
//
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
// Copyright 2009 the Sputnik authors. All rights reserved.
|
||||
// This code is governed by the BSD license found in the LICENSE file.
|
||||
|
||||
/*---
|
||||
info: String.prototype.lastIndexOf(searchString, position)
|
||||
es5id: 15.5.4.8_A1_T4
|
||||
description: >
|
||||
Call lastIndexOf(searchString, position) function without
|
||||
arguments of string
|
||||
---*/
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
//CHECK#1
|
||||
//since ToString() evaluates to "" lastIndexOf() evaluates to lastIndexOf("",0)
|
||||
if ("".lastIndexOf() !== -1) {
|
||||
throw new Test262Error('#1: "".lastIndexOf() === -1. Actual: ' + ("".lastIndexOf()));
|
||||
}
|
||||
//
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
// Copyright 2009 the Sputnik authors. All rights reserved.
|
||||
// This code is governed by the BSD license found in the LICENSE file.
|
||||
|
||||
/*---
|
||||
info: String.prototype.lastIndexOf(searchString, position)
|
||||
es5id: 15.5.4.8_A1_T5
|
||||
description: >
|
||||
Call lastIndexOf(searchString, position) function with null
|
||||
argument of function object
|
||||
---*/
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
//CHECK#1
|
||||
//since ToString(null) evaluates to "null" lastIndexOf(null) evaluates to lastIndexOf("",0)
|
||||
if (function() {
|
||||
return "gnullunazzgnull"
|
||||
}().lastIndexOf(null) !== 11) {
|
||||
throw new Test262Error('#1: function(){return "gnullunazzgnull"}().lastIndexOf(null) === 11. Actual: ' + function() {
|
||||
return "gnullunazzgnull"
|
||||
}().lastIndexOf(null));
|
||||
}
|
||||
//
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
// Copyright 2009 the Sputnik authors. All rights reserved.
|
||||
// This code is governed by the BSD license found in the LICENSE file.
|
||||
|
||||
/*---
|
||||
info: String.prototype.lastIndexOf(searchString, position)
|
||||
es5id: 15.5.4.8_A1_T7
|
||||
description: >
|
||||
Call lastIndexOf(searchString, position) function with undefined
|
||||
argument of string object
|
||||
---*/
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
//CHECK#1
|
||||
//since ToString(undefined) evaluates to "undefined" lastIndexOf(undefined) evaluates to lastIndexOf("undefined",0)
|
||||
if (String("undefined").lastIndexOf(undefined) !== 0) {
|
||||
throw new Test262Error('#1: String("undefined").lastIndexOf(undefined) === 0. Actual: ' + String("undefined").lastIndexOf(undefined));
|
||||
}
|
||||
//
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
Reference in New Issue
Block a user