mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-10-02 00:25:34 +08:00
Handle omitted and primitive string search values in static calls (#435)
This commit is contained in:
@@ -6,7 +6,7 @@ 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, lowerPositionArgument, lowerStaticallyUndefinedArgument, positionNumber } 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 };
|
||||
@@ -271,8 +271,13 @@ 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 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) {
|
||||
|
||||
@@ -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";
|
||||
@@ -8946,19 +8946,7 @@ function staticTextDecoderEncoding(label: string): StaticTextDecoderEncoding | n
|
||||
const receiver = lowerer.lowerExprExpecting(access.expression, STRING);
|
||||
const positionNode = call.arguments[1];
|
||||
if (!positionNode) {
|
||||
const needleNode = call.arguments[0];
|
||||
let needle: IrExpr = strLit("undefined", loc);
|
||||
if (needleNode) {
|
||||
const undefinedArg = lowerStaticallyUndefinedArgument(lowerer, needleNode);
|
||||
if (undefinedArg) {
|
||||
needle = defaultAfterUndefined(undefinedArg, needle);
|
||||
} else {
|
||||
const value = lowerer.lowerExpr(needleNode);
|
||||
needle = isUnitType(value.type)
|
||||
? defaultAfterUndefined(value, strLit(value.type.kind === "nullT" ? "null" : "undefined", loc))
|
||||
: lowerer.ensureString(value, needleNode);
|
||||
}
|
||||
}
|
||||
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);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import * as ts from "../ts7/adapter.js";
|
||||
import type { IrExpr, IrType } from "../../ir/ir.js";
|
||||
import type { IrExpr, IrType, SrcLoc } from "../../ir/ir.js";
|
||||
import { BOOL, F64, isUnitType, typeEquals } from "../../ir/ir.js";
|
||||
import { numLit } from "../../ir/build.js";
|
||||
import { numLit, strLit } from "../../ir/build.js";
|
||||
import type { Lowerer } from "./lowerer.js";
|
||||
import { isSafeToDiscard } from "./expressions/evaluation-safety.js";
|
||||
|
||||
@@ -43,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,
|
||||
|
||||
@@ -382,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).
|
||||
|
||||
@@ -3511,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",
|
||||
@@ -3588,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",
|
||||
|
||||
@@ -6967,6 +6967,12 @@
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/3032-string-search-value.js": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/3032-string-search-value.js"
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/304-compound-incdec.ts": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/304-compound-incdec.ts"
|
||||
|
||||
@@ -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);
|
||||
@@ -63,6 +63,9 @@
|
||||
"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",
|
||||
@@ -156,6 +159,9 @@
|
||||
"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",
|
||||
|
||||
+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));
|
||||
}
|
||||
//
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
Reference in New Issue
Block a user