mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-10-02 00:25:34 +08:00
Support static String.prototype.at and codePointAt (#454)
* Support static string at and codePointAt * Update static at diagnostics and island contracts
This commit is contained in:
@@ -55,6 +55,7 @@ import {
|
||||
STATIC_MATH_FNS,
|
||||
STATIC_MATH_PROPS,
|
||||
STATIC_NUMBER_METHODS,
|
||||
STRING_INDEX_METHODS,
|
||||
STR_METHODS,
|
||||
UNSUPPORTED_EXPR,
|
||||
UNSUPPORTED_STMT,
|
||||
@@ -199,6 +200,9 @@ export function generateSurfaceManifest(compilerVersion: string): SurfaceManifes
|
||||
note: arityNote(entry.minArgs, entry.maxArgs),
|
||||
});
|
||||
}
|
||||
for (const name of STRING_INDEX_METHODS) {
|
||||
add({ id: `stdlib.string.${name}`, kind: "stdlib", name: `string.prototype.${name}`, status: "static", note: arityNote(0, 1) });
|
||||
}
|
||||
for (const name of [...ARRAY_METHODS]) {
|
||||
add({ id: `stdlib.array.${name}`, kind: "stdlib", name: `Array.prototype.${name}`, status: "static" });
|
||||
}
|
||||
@@ -283,6 +287,7 @@ export function generateSurfaceManifest(compilerVersion: string): SurfaceManifes
|
||||
}
|
||||
}
|
||||
for (const name of Object.keys(ISLAND_SURFACE.string)) {
|
||||
if (STRING_INDEX_METHODS.has(name)) continue;
|
||||
add({
|
||||
id: `stdlib.string.${name}`,
|
||||
kind: "stdlib",
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import * as ts from "../../ts7/adapter.js";
|
||||
import { BOOL, DYN, F64, IrExpr, IrFunction, IrStmt, IrType, STRING, SrcLoc, arrayOf, isUnitType, typeEquals, typeKey } from "../../../ir/ir.js";
|
||||
import { BOOL, DYN, F64, IrExpr, IrFunction, IrStmt, IrType, STRING, SrcLoc, UNDEFINED_T, arrayOf, isUnitType, typeEquals, typeKey } from "../../../ir/ir.js";
|
||||
import { numLit, strLit, varRef } from "../../../ir/build.js";
|
||||
import { locOf } from "../../program.js";
|
||||
import type { Lowerer } from "../lowerer.js";
|
||||
import { nodeThrowExpr, own } from "../lowerer.js";
|
||||
import { isRequireMainFilename } from "../expressions/optional-chains.js";
|
||||
import { STR_METHODS } from "../surfaces.js";
|
||||
import { STRING_INDEX_METHODS, STR_METHODS } from "../surfaces.js";
|
||||
import { coerceStringSearchValue, defaultAfterUndefined, lowerOptionalArgument, lowerPositionArgument, lowerStaticallyUndefinedArgument, lowerStringSearchArgument, positionNumber } from "../optional-arguments.js";
|
||||
|
||||
function lowerSplitLimitArg(lowerer: Lowerer, node: ts.Expression | undefined, loc: SrcLoc): IrExpr {
|
||||
@@ -143,6 +143,99 @@ function lowerMethodReceiver(
|
||||
return lowerer.coerceInto(node, lowered, expected);
|
||||
}
|
||||
|
||||
/** Relative string indexing and code-point decoding over the UTF-16 intrinsics. */
|
||||
export function lowerStringIndexCall(
|
||||
lowerer: Lowerer,
|
||||
call: ts.CallExpression,
|
||||
method: "at" | "codePointAt",
|
||||
receiver: IrExpr,
|
||||
receiverNode: ts.Node,
|
||||
argumentNodes: readonly ts.Expression[],
|
||||
): IrExpr {
|
||||
const loc = locOf(call);
|
||||
if (argumentNodes.length > 1 || argumentNodes.some(ts.isSpreadElement)) {
|
||||
return lowerer.noLowering(`String.prototype.${method} with ${argumentNodes.length} arguments`, call);
|
||||
}
|
||||
const indexNode = argumentNodes[0];
|
||||
let index = lowerPositionArgument(lowerer, indexNode, numLit(0, loc));
|
||||
if (indexNode && (index.type.kind === "record" || index.type.kind === "array" || index.type.kind === "func")) {
|
||||
index = lowerer.coerceInto(indexNode, index, DYN);
|
||||
}
|
||||
const valueType = method === "at" ? STRING : F64;
|
||||
const resultType = lowerer.withUndefinedArmOf(valueType);
|
||||
if (!resultType || resultType.kind !== "union") return lowerer.noLowering(`String.prototype.${method} result`, call);
|
||||
const undefinedTag = lowerer.armTag(resultType.unionId, UNDEFINED_T);
|
||||
const valueTag = lowerer.armTag(resultType.unionId, valueType);
|
||||
const key = `str.index:${method}:${typeKey(receiver.type)}:${typeKey(index.type)}`;
|
||||
let helper = lowerer.widthHelpers.get(key);
|
||||
if (!helper) {
|
||||
helper = `%str.index.${lowerer.widthHelpers.size}`;
|
||||
lowerer.widthHelpers.set(key, helper);
|
||||
const rawReceiver = varRef("arg.0", receiver.type, loc);
|
||||
const rawIndex = varRef("arg.1", index.type, loc);
|
||||
const stringReceiver = varRef("receiver.0", STRING, loc);
|
||||
const length = varRef("length.0", F64, loc);
|
||||
const position = varRef("position.0", F64, loc);
|
||||
const first = varRef("first.0", F64, loc);
|
||||
const second = varRef("second.0", F64, loc);
|
||||
const bin = (op: "<" | ">=" | ">" | "+" | "-" | "*", left: IrExpr, right: IrExpr): IrExpr =>
|
||||
({ kind: "bin", op, left, right, type: op === "<" || op === ">=" || op === ">" ? BOOL : F64, loc });
|
||||
const either = (left: IrExpr, right: IrExpr): IrExpr => ({ kind: "logical", op: "||", left, right, type: BOOL, loc });
|
||||
const wrap = (value: IrExpr): IrExpr => ({ kind: "unionWrap", unionId: resultType.unionId, tag: valueTag, value, type: resultType, loc });
|
||||
const miss: IrExpr = {
|
||||
kind: "unionWrap", unionId: resultType.unionId, tag: undefinedTag,
|
||||
value: { kind: "unitLit", unit: "undefined", type: UNDEFINED_T, loc }, type: resultType, loc,
|
||||
};
|
||||
const numeric = positionNumber(lowerer, rawIndex, numLit(0, loc), indexNode ?? call, "string index");
|
||||
const numericValue = varRef("numeric.0", F64, loc);
|
||||
const body: IrStmt[] = [
|
||||
{ kind: "varDecl", localId: "receiver.0", init: lowerer.ensureString(rawReceiver, receiverNode), loc },
|
||||
{ kind: "varDecl", localId: "length.0", init: { kind: "strIntrinsic", method: "length", receiver: stringReceiver, args: [], type: F64, loc }, loc },
|
||||
{ kind: "varDecl", localId: "numeric.0", init: numeric, loc },
|
||||
{ kind: "varDecl", localId: "position.0", init: {
|
||||
kind: "ternary", cond: { kind: "libCall", fn: "num.isNaN", args: [numericValue], type: BOOL, loc },
|
||||
then: numLit(0, loc), else_: { kind: "libCall", fn: "math.trunc", args: [numericValue], type: F64, loc }, type: F64, loc,
|
||||
}, loc },
|
||||
];
|
||||
if (method === "at") {
|
||||
body.push({ kind: "if", cond: bin("<", position, numLit(0, loc)),
|
||||
then: [{ kind: "assign", localId: "position.0", value: bin("+", position, length), loc }], else_: null, loc });
|
||||
}
|
||||
body.push({ kind: "if", cond: either(bin("<", position, numLit(0, loc)), bin(">=", position, length)),
|
||||
then: [{ kind: "return", value: miss, loc }], else_: null, loc });
|
||||
if (method === "at") {
|
||||
body.push({ kind: "return", value: wrap({ kind: "strIntrinsic", method: "charAt", receiver: stringReceiver, args: [position], type: STRING, loc }), loc });
|
||||
} else {
|
||||
body.push({ kind: "varDecl", localId: "first.0", init: { kind: "strIntrinsic", method: "charCodeAt", receiver: stringReceiver, args: [position], type: F64, loc }, loc });
|
||||
body.push({ kind: "if", cond: either(either(bin("<", first, numLit(0xd800, loc)), bin(">", first, numLit(0xdbff, loc))), bin(">=", bin("+", position, numLit(1, loc)), length)),
|
||||
then: [{ kind: "return", value: wrap(first), loc }], else_: null, loc });
|
||||
body.push({ kind: "varDecl", localId: "second.0", init: { kind: "strIntrinsic", method: "charCodeAt", receiver: stringReceiver, args: [bin("+", position, numLit(1, loc))], type: F64, loc }, loc });
|
||||
body.push({ kind: "if", cond: either(bin("<", second, numLit(0xdc00, loc)), bin(">", second, numLit(0xdfff, loc))),
|
||||
then: [{ kind: "return", value: wrap(first), loc }], else_: null, loc });
|
||||
body.push({ kind: "return", value: wrap(bin("+", bin("+", bin("*", bin("-", first, numLit(0xd800, loc)), numLit(1024, loc)), bin("-", second, numLit(0xdc00, loc))), numLit(0x10000, loc))), loc });
|
||||
}
|
||||
lowerer.liftedFns.push({
|
||||
name: helper,
|
||||
params: [{ localId: "arg.0", name: "receiver", type: receiver.type }, { localId: "arg.1", name: "index", type: index.type }],
|
||||
returnType: resultType,
|
||||
locals: [
|
||||
{ id: "arg.0", name: "receiver", type: receiver.type, mutable: false },
|
||||
{ id: "arg.1", name: "index", type: index.type, mutable: false },
|
||||
{ id: "receiver.0", name: "stringReceiver", type: STRING, mutable: false },
|
||||
{ id: "length.0", name: "length", type: F64, mutable: false },
|
||||
{ id: "numeric.0", name: "numeric", type: F64, mutable: false },
|
||||
{ id: "position.0", name: "position", type: F64, mutable: method === "at" },
|
||||
...(method === "codePointAt" ? [
|
||||
{ id: "first.0", name: "first", type: F64, mutable: false },
|
||||
{ id: "second.0", name: "second", type: F64, mutable: false },
|
||||
] : []),
|
||||
],
|
||||
body, loc,
|
||||
});
|
||||
}
|
||||
return { kind: "call", callee: helper, args: [receiver, index], type: resultType, loc };
|
||||
}
|
||||
|
||||
function paddingFillString(lowerer: Lowerer, value: IrExpr, node: ts.Node): IrExpr {
|
||||
const loc = value.loc;
|
||||
if (value.type.kind === "undefinedT" || value.type.kind === "void") return strLit(" ", loc);
|
||||
@@ -453,7 +546,8 @@ export function lowerStringMethodCall(lowerer: Lowerer, call: ts.CallExpression,
|
||||
if (lowerer.chainBlocked(access, call)) return null;
|
||||
if (dynReceiver === undefined && access.name.text === "localeCompare") return lowerLocaleCompareCall(lowerer, call, access);
|
||||
const entry = own(STR_METHODS, access.name.text);
|
||||
if (!entry) return null;
|
||||
const indexMethod = STRING_INDEX_METHODS.has(access.name.text) ? access.name.text as "at" | "codePointAt" : null;
|
||||
if (!entry && !indexMethod) return null;
|
||||
// A validated dyn receiver (`pkg.name.replace(...)` on a JSON.parse
|
||||
// value) arrives pre-extracted through `dynReceiver`; its checker type
|
||||
// is `any`, so the type/symbol gates don't apply — the dyn value's
|
||||
@@ -473,7 +567,7 @@ export function lowerStringMethodCall(lowerer: Lowerer, call: ts.CallExpression,
|
||||
}
|
||||
// The lib declares optional parameters beyond some lowered forms; fence
|
||||
// those arities instead of passing arguments the runtime doesn't take.
|
||||
if (argumentNodes.length < entry.minArgs || argumentNodes.length > entry.maxArgs) {
|
||||
if (argumentNodes.length < (entry?.minArgs ?? 0) || argumentNodes.length > (entry?.maxArgs ?? 1)) {
|
||||
lowerer.noLowering(
|
||||
`.${access.name.text} with ${argumentNodes.length} argument${argumentNodes.length === 1 ? "" : "s"} on strings`,
|
||||
call,
|
||||
@@ -483,6 +577,8 @@ export function lowerStringMethodCall(lowerer: Lowerer, call: ts.CallExpression,
|
||||
? dynReceiver()
|
||||
: lowerMethodReceiver(lowerer, access.expression, STRING, access.name.text);
|
||||
const loc = locOf(call);
|
||||
if (indexMethod) return lowerStringIndexCall(lowerer, call, indexMethod, receiver, access.expression, argumentNodes);
|
||||
if (!entry) return null;
|
||||
if (entry.method === "split") return lowerStringSplitCall(lowerer, call, receiver, access.expression, argumentNodes);
|
||||
if (entry.method === "padStart" || entry.method === "padEnd") {
|
||||
return lowerStringPaddingCall(lowerer, call, entry.method, receiver, access.expression, argumentNodes);
|
||||
|
||||
@@ -12,13 +12,13 @@ import { isJsSourceFile, isNodeEsmFile, locOf } from "../program.js";
|
||||
import { genResultRecord, isGenericCallableMemberType, typeKey } from "../type-mapper.js";
|
||||
import { PoisonError, dynFallbackType, dynUndefinedExpr, importCallHandleType, jsFuncNameOf, newFnCtx, nodeThrowExpr, staticImportNamespaceType } from "./lowerer.js";
|
||||
import { enforceLibBoundary } from "./lib-boundary.js";
|
||||
import { NARROW_FIRST, STR_METHODS, builtinFenceHintOf, builtinModuleFnOf } from "./surfaces.js";
|
||||
import { NARROW_FIRST, STRING_INDEX_METHODS, STR_METHODS, builtinFenceHintOf, builtinModuleFnOf } from "./surfaces.js";
|
||||
import { ffiBindingDiag, ffiSignatureDiag, libCallbackDiag, requiresDynamicDiag } from "../../diagnostics/diagnostic.js";
|
||||
import type { ScrDiagnostic } from "../../diagnostics/diagnostic.js";
|
||||
import { mixinFnShapeOf } from "./lower-mixins.js";
|
||||
import { dynStringReceiver, lowerArrayFromCall, lowerDynArrayFilterCall, lowerDynArrayFlatMapCall, lowerGroupByStaticCall, lowerIteratorHelperCall, lowerObjectAssignIndexShape, lowerObjectFromEntriesCall, lowerObjectIterOverIndexShape, lowerTupleReadMethodCall } from "./lower-containers.js";
|
||||
import { bufEncoding } from "./containers/bytes.js";
|
||||
import { lowerRegexMethodCall, lowerStringMethodCall, lowerStringPaddingCall, lowerStringSplitCall } from "./containers/string-and-regexp.js";
|
||||
import { lowerRegexMethodCall, lowerStringIndexCall, lowerStringMethodCall, lowerStringPaddingCall, lowerStringSplitCall } from "./containers/string-and-regexp.js";
|
||||
import { lowerChildStreamMethodCall, lowerChildWriterMethodCall, lowerCreateRequireCall, lowerCryptoHashMethodCall, lowerDirentMethodCall, lowerFileHandleMethodCall, lowerImportMetaResolveCall, lowerNodeModuleCall, lowerPerfHooksCall, lowerProcStreamMethodCall, lowerReflectApplyCall, lowerRequireResolveCall, lowerWatcherMethodCall } from "./lower-builtins.js";
|
||||
import { lowerAbsenceProbe, lowerPromiseAllTupleCall, lowerPromiseRejectCall, stringWrapperToString, templateRawTextOf } from "./lower-exprs.js";
|
||||
import { isSafeToDiscard } from "./expressions/evaluation-safety.js";
|
||||
@@ -5111,18 +5111,22 @@ function lowerStringPrototypeCall(
|
||||
const prototypeAccess = methodAccess.expression;
|
||||
if (!ts.isPropertyAccessExpression(prototypeAccess) || prototypeAccess.name.text !== "prototype" ||
|
||||
!ts.isIdentifier(prototypeAccess.expression) || prototypeAccess.expression.text !== "String" ||
|
||||
!Object.hasOwn(STR_METHODS, methodAccess.name.text) || !lowerer.isStdlibMember(methodAccess)) return null;
|
||||
(!Object.hasOwn(STR_METHODS, methodAccess.name.text) && !STRING_INDEX_METHODS.has(methodAccess.name.text)) ||
|
||||
!lowerer.isStdlibMember(methodAccess)) return null;
|
||||
if (call.arguments.some(ts.isSpreadElement)) return null;
|
||||
const entry = STR_METHODS[methodAccess.name.text]!;
|
||||
const entry = STR_METHODS[methodAccess.name.text];
|
||||
const indexMethod = STRING_INDEX_METHODS.has(methodAccess.name.text) ? methodAccess.name.text as "at" | "codePointAt" : null;
|
||||
const resultType = indexMethod ? lowerer.withUndefinedArmOf(indexMethod === "at" ? STRING : F64) : entry?.result;
|
||||
if (!resultType) return null;
|
||||
const method = methodAccess.name.text === "trimStart" ? "trimLeft" :
|
||||
methodAccess.name.text === "trimEnd" ? "trimRight" : methodAccess.name.text;
|
||||
const loc = locOf(call);
|
||||
const nullishError = nodeThrowExpr(1, "", `String.prototype.${method} called on null or undefined`, entry.result, loc);
|
||||
const nullishError = nodeThrowExpr(1, "", `String.prototype.${method} called on null or undefined`, resultType, loc);
|
||||
const receiverNode = call.arguments[0];
|
||||
if (!receiverNode) return nullishError;
|
||||
// Receiver coercion runs after .call has evaluated its arguments; keep
|
||||
// contextual wrapper lowering on forms without later method arguments.
|
||||
const immediateWrapper = entry.maxArgs === 0 && call.arguments.length === 1
|
||||
const immediateWrapper = entry?.maxArgs === 0 && call.arguments.length === 1
|
||||
? immediatePrimitiveWrapperToString(lowerer, receiverNode) : null;
|
||||
const receiverValue = immediateWrapper ?? lowerer.lowerExpr(receiverNode);
|
||||
const receiverType = receiverValue.type;
|
||||
@@ -5135,24 +5139,26 @@ function lowerStringPrototypeCall(
|
||||
kind: "seqExpr",
|
||||
stmts: values.filter((value) => !isSafeToDiscard(value)).map((value) => ({ kind: "exprStmt", expr: value, loc: value.loc })),
|
||||
result: nullishError,
|
||||
type: entry.result,
|
||||
type: resultType,
|
||||
loc,
|
||||
};
|
||||
}
|
||||
const padding = entry.method === "padStart" || entry.method === "padEnd";
|
||||
const padding = entry?.method === "padStart" || entry?.method === "padEnd";
|
||||
const scalar = receiverType.kind === "string" || receiverType.kind === "f64" ||
|
||||
receiverType.kind === "bool" || receiverType.kind === "bigint" ||
|
||||
(receiverType.kind === "union" && (lowerer.unions.get(receiverType.unionId)?.arms.every((arm) =>
|
||||
arm.kind === "string" || arm.kind === "f64" || arm.kind === "bool" || arm.kind === "bigint") ?? false));
|
||||
// Other methods convert object receivers before lowering their arguments;
|
||||
// padding uses a helper that delays conversion until every argument is ready.
|
||||
const objectWithoutMethodArgs = entry.maxArgs === 0 && call.arguments.length === 1 &&
|
||||
const objectWithoutMethodArgs = entry?.maxArgs === 0 && call.arguments.length === 1 &&
|
||||
(receiverType.kind === "record" || receiverType.kind === "array" || receiverType.kind === "object");
|
||||
const dynObjectWithoutMethodArgs = entry.maxArgs === 0 && call.arguments.length === 1 &&
|
||||
const dynObjectWithoutMethodArgs = entry?.maxArgs === 0 && call.arguments.length === 1 &&
|
||||
receiverType.kind === "dyn" && plainObjectCoercionReceiver(lowerer, receiverNode);
|
||||
if (!scalar && !objectWithoutMethodArgs && !dynObjectWithoutMethodArgs && !(padding && receiverType.kind === "dyn")) {
|
||||
return lowerer.noLowering(`String.prototype.${methodAccess.name.text}.call with ${lowerer.fmt(receiverType)} receivers`, call);
|
||||
}
|
||||
if (indexMethod) return lowerStringIndexCall(lowerer, call, indexMethod, receiverValue, receiverNode, call.arguments.slice(1));
|
||||
if (!entry) return null;
|
||||
if (padding) {
|
||||
return lowerStringPaddingCall(lowerer, call, entry.method as "padStart" | "padEnd", receiverValue, receiverNode, call.arguments.slice(1));
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ import type { Lowerer } from "./lowerer.js";
|
||||
import { wasiGuestPath } from "../../wasi-paths.js";
|
||||
import { BIGINT_T, BOOL, CAUGHT, DYN, DYN_HANDLE_KINDS, F64, IrExpr, IrFunction, IrJsOp, IrLocal, IrRecordShape, IrStmt, IrType, JSVAL, NULL_T, REF_TRUTHY_KINDS, REGEX, RUNTIME_ERROR_CLASSES, SEARCH_PARAMS_T, STRING, SrcLoc, UNDEFINED_T, VOID, arrayOf, canAdaptDynFuncTo, canBoxFuncIntoDyn, funcOf, isDynTypedRefType, isJsonSafeType, isSupportedArrayElem, isUnitType, jsOpResultKind, shapeHasAccessorSlots, typeEquals, typeKey, unionFuncSetArmsOk } from "../../ir/ir.js";
|
||||
import { cjsClassExprWholeExportOf, cjsExportAssignmentOf, cjsExportDiscardReason, isCjsExportTableLiteral, isCjsJsFile, isJsSourceFile, isModuleExportsAccess, isNodeEsmFile, locOf } from "../program.js";
|
||||
import { ARRAY_METHODS, builtinConstLit, builtinFenceHintOf, builtinModuleConstOf, builtinModulesArrayLit, builtinModuleFnOf, COMPOUND_ASSIGN_OPS, CompoundOp, ISLAND_SURFACE, isChildSurfaceMember, MAP_METHODS, NARROW_FIRST, SET_METHODS, STR_METHODS, UNSUPPORTED_EXPR, sideEffectFreeOptionValue, stdlibGlobalNameOf } from "./surfaces.js";
|
||||
import { ARRAY_METHODS, builtinConstLit, builtinFenceHintOf, builtinModuleConstOf, builtinModulesArrayLit, builtinModuleFnOf, COMPOUND_ASSIGN_OPS, CompoundOp, ISLAND_SURFACE, isChildSurfaceMember, MAP_METHODS, NARROW_FIRST, SET_METHODS, STRING_INDEX_METHODS, STR_METHODS, UNSUPPORTED_EXPR, sideEffectFreeOptionValue, stdlibGlobalNameOf } from "./surfaces.js";
|
||||
import { UNSUPPORTED, blockedBindingUseDiag, requiresDynamicPackageDiag, unsupportedDiag } from "../../diagnostics/diagnostic.js";
|
||||
import { PoisonError, dynUndefinedExpr, jsFuncNameOf, neverTaintedJsType, nodeThrowExpr, own } from "./lowerer.js";
|
||||
import { lowerNpmStaticSafeIndexRead, lowerSafeIndexRead, strCharsCall, tryLowerNumericIndexRead } from "./lower-containers.js";
|
||||
@@ -615,6 +615,13 @@ function lowerExprInner(lowerer: Lowerer, expr: ts.Expression): IrExpr {
|
||||
return { kind: "strLit", value: "object", type: STRING, loc };
|
||||
}
|
||||
if (ts.isPropertyAccessExpression(expr.expression)) {
|
||||
const member = expr.expression;
|
||||
const prototype = member.expression;
|
||||
if (STRING_INDEX_METHODS.has(member.name.text) && ts.isPropertyAccessExpression(prototype) &&
|
||||
prototype.name.text === "prototype" && ts.isIdentifier(prototype.expression) &&
|
||||
lowerer.isStdlibGlobal(prototype.expression, "String") && lowerer.isStdlibMember(member)) {
|
||||
return { kind: "strLit", value: "function", type: STRING, loc };
|
||||
}
|
||||
const presence = lowerPromiseThenPresence(
|
||||
lowerer,
|
||||
expr.expression,
|
||||
@@ -3636,7 +3643,7 @@ function lowerPromiseThenPresence(
|
||||
}
|
||||
if (
|
||||
kind === "string"
|
||||
? own(STR_METHODS, name) !== undefined || own(ISLAND_SURFACE.string, name) !== undefined
|
||||
? own(STR_METHODS, name) !== undefined || STRING_INDEX_METHODS.has(name) || own(ISLAND_SURFACE.string, name) !== undefined
|
||||
: ARRAY_METHODS.has(name)
|
||||
) {
|
||||
lowerer.unsupported("SC1090", expr, `${kind} methods as values (call '${name}' directly)`);
|
||||
|
||||
@@ -424,6 +424,9 @@ export const STR_METHODS: Record<
|
||||
toWellFormed: { method: "toWellFormed", result: STRING, minArgs: 0, maxArgs: 0 },
|
||||
};
|
||||
|
||||
/** String index reads return an undefined arm and compose the existing UTF-16 intrinsics. */
|
||||
export const STRING_INDEX_METHODS = new Set(["at", "codePointAt"]);
|
||||
|
||||
/** One member of the island-backed ambient surface: declared argument
|
||||
* types (tsc enforces them at call sites; the arity double-checks the
|
||||
* table against the ambient file) and the validated island-exit target the
|
||||
|
||||
@@ -3489,8 +3489,8 @@
|
||||
"id": "stdlib.string.at",
|
||||
"kind": "stdlib",
|
||||
"name": "string.prototype.at",
|
||||
"status": "dynamic-only",
|
||||
"code": "SC2012"
|
||||
"status": "static",
|
||||
"note": "the lowered call form takes 0 to 1 arguments"
|
||||
},
|
||||
{
|
||||
"id": "stdlib.string.charAt",
|
||||
@@ -3506,6 +3506,13 @@
|
||||
"status": "static",
|
||||
"note": "the lowered call form takes 0 to 1 arguments"
|
||||
},
|
||||
{
|
||||
"id": "stdlib.string.codePointAt",
|
||||
"kind": "stdlib",
|
||||
"name": "string.prototype.codePointAt",
|
||||
"status": "static",
|
||||
"note": "the lowered call form takes 0 to 1 arguments"
|
||||
},
|
||||
{
|
||||
"id": "stdlib.string.endsWith",
|
||||
"kind": "stdlib",
|
||||
|
||||
@@ -3487,6 +3487,12 @@
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/2113-string-index-methods.js": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/2113-string-index-methods.js"
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/212-string-aliasing-append.ts": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/212-string-aliasing-append.ts"
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
const word = "café";
|
||||
console.log(word.at(0), word.at(-1), word.at(-5), word.at(4));
|
||||
console.log("abcdef".at(-1.9), "abcdef".at(1.9), "abcdef".at(-Infinity), "abcdef".at(NaN));
|
||||
console.log("abc".at("1"), "abc".at(false), "abc".at(null), "abc".at(undefined));
|
||||
console.log("abc".at([1]), "abc".at([]), "abc".at(function () {}));
|
||||
|
||||
const astral = "A😀B";
|
||||
console.log(astral.codePointAt(0), astral.codePointAt(1), astral.codePointAt(2), astral.codePointAt(3), astral.codePointAt(4));
|
||||
console.log(astral.codePointAt(-1), astral.codePointAt(Infinity), astral.codePointAt(NaN));
|
||||
console.log(astral.codePointAt("1.9"), astral.codePointAt([3]), astral.codePointAt([]));
|
||||
console.log(String.prototype.at.call(12345, -1), String.prototype.codePointAt.call(true, 0));
|
||||
|
||||
let order = "";
|
||||
function receiver() { order += "r"; return "abc"; }
|
||||
function argument() { order += "a"; return { valueOf() { order += "v"; return 1.8; } }; }
|
||||
console.log(receiver().at(argument()), order);
|
||||
order = "";
|
||||
console.log(String.prototype.codePointAt.call(receiver(), argument()), order);
|
||||
|
||||
order = "";
|
||||
function touched() { order += "x"; return 0; }
|
||||
try { String.prototype.at.call(undefined, touched()); } catch (error) { console.log(error.name, error.message, order); }
|
||||
try { String.prototype.codePointAt.call(null, touched()); } catch (error) { console.log(error.name, error.message, order); }
|
||||
@@ -1,5 +1,5 @@
|
||||
// The island-backed ambient surface (number methods, string-pattern
|
||||
// replace/at, the Number statics, ...)
|
||||
// replace, the Number statics, ...)
|
||||
// typechecks against real static types but executes in the embedded
|
||||
// engine: in a static build every use site is its own SC2012 naming the
|
||||
// flag — never an ICE, never a link error. Static Math methods include
|
||||
@@ -11,5 +11,4 @@ const up = Math.cbrt(2);
|
||||
const tau = Math.PI * 2;
|
||||
const price = (19.99).toPrecision(4);
|
||||
const swapped = "banana".replace("an", "AN");
|
||||
const ch = "hello".at(0);
|
||||
const n = Number.parseFloat("3.14");
|
||||
|
||||
@@ -12,24 +12,15 @@ dynamic-surface.ts:13:17 - error SC2012: '.replace()' on strings runs in the emb
|
||||
12 | const price = (19.99).toPrecision(4);
|
||||
13 | const swapped = "banana".replace("an", "AN");
|
||||
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
14 | const ch = "hello".at(0);
|
||||
14 | const n = Number.parseFloat("3.14");
|
||||
|
||||
hint: build with --dynamic to run this call in the embedded engine (adds ~620KB to the binary); static builds never include it
|
||||
|
||||
dynamic-surface.ts:14:12 - error SC2012: '.at()' on strings runs in the embedded dynamic engine, which this build does not include
|
||||
dynamic-surface.ts:14:11 - error SC2012: 'Number.parseFloat' runs in the embedded dynamic engine, which this build does not include
|
||||
|
||||
13 | const swapped = "banana".replace("an", "AN");
|
||||
14 | const ch = "hello".at(0);
|
||||
| ^~~~~~~~~~~~~
|
||||
15 | const n = Number.parseFloat("3.14");
|
||||
|
||||
hint: build with --dynamic to run this call in the embedded engine (adds ~620KB to the binary); static builds never include it
|
||||
|
||||
dynamic-surface.ts:15:11 - error SC2012: 'Number.parseFloat' runs in the embedded dynamic engine, which this build does not include
|
||||
|
||||
14 | const ch = "hello".at(0);
|
||||
15 | const n = Number.parseFloat("3.14");
|
||||
14 | const n = Number.parseFloat("3.14");
|
||||
| ^~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
16 |
|
||||
15 |
|
||||
|
||||
hint: build with --dynamic to run this call in the embedded engine (adds ~620KB to the binary); static builds never include it
|
||||
@@ -463,32 +463,21 @@ console.log(recovered);
|
||||
});
|
||||
|
||||
/* ── the island-backed ambient surface (Math, methods, globals) ───────
|
||||
* Value results are differential (corpus 1110–1116); what lives here is
|
||||
* behavior Node cannot oracle: the `.at()` undefined-refusal divergence
|
||||
* and the fiber re-anchor for surface calls inside async bodies. */
|
||||
* Value results are differential (corpus 1110–1116 and 2113); this
|
||||
* checks that --dynamic leaves static .at() behavior unchanged and that
|
||||
* surface calls re-anchor the engine on fibers inside async bodies. */
|
||||
|
||||
test("out-of-range .at() throws a catchable TypeError instead of returning undefined", async () => {
|
||||
// `at` is declared returning `string` (undefined is unrepresentable);
|
||||
// out of range the engine yields undefined and the validated exit
|
||||
// refuses it — strictly, catchably — where Node would hand back
|
||||
// undefined. The documented divergence, pinned.
|
||||
const r = await compileAndRun(
|
||||
"at-out-of-range",
|
||||
`const s = "abc";
|
||||
try {
|
||||
console.log("in range", s.at(1), s.at(-1));
|
||||
const c = s.at(99);
|
||||
console.log("unreachable", c);
|
||||
} catch {
|
||||
console.log("caught");
|
||||
}
|
||||
const direct = s.at(-4);
|
||||
console.log("unreachable", direct);
|
||||
`,
|
||||
);
|
||||
expect(r.exitCode).toBe(1);
|
||||
expect(r.stdout).toBe("in range b c\ncaught\n");
|
||||
expect(r.stderr).toContain("TypeError: expected string, got undefined");
|
||||
test("string .at() returns undefined out of range in both build modes", async () => {
|
||||
const source = `const s = "abc";
|
||||
console.log("in range", s.at(1), s.at(-1));
|
||||
console.log("out of range", s.at(99), s.at(-4));
|
||||
`;
|
||||
for (const dynamic of [false, true]) {
|
||||
const r = await compileAndRun("at-out-of-range", source, { dynamic });
|
||||
expect(r.exitCode).toBe(0);
|
||||
expect(r.stdout).toBe("in range b c\nout of range undefined undefined\n");
|
||||
expect(r.stderr).toBe("");
|
||||
}
|
||||
});
|
||||
|
||||
test("island-backed calls re-anchor the engine on fibers (Math after await)", async () => {
|
||||
|
||||
@@ -52,6 +52,11 @@
|
||||
"test/built-ins/Number/S9.3_A4.2_T1.js",
|
||||
"test/built-ins/Number/isNaN/nan.js",
|
||||
"test/built-ins/RegExp/prototype/test/S15.10.6.3_A1_T16.js",
|
||||
"test/built-ins/String/prototype/at/index-argument-tointeger.js",
|
||||
"test/built-ins/String/prototype/at/index-non-numeric-argument-tointeger.js",
|
||||
"test/built-ins/String/prototype/at/returns-item-relative-index.js",
|
||||
"test/built-ins/String/prototype/at/returns-item.js",
|
||||
"test/built-ins/String/prototype/at/returns-undefined-for-out-of-range-index.js",
|
||||
"test/built-ins/String/prototype/charAt/S15.5.4.4_A1_T4.js",
|
||||
"test/built-ins/String/prototype/charAt/S15.5.4.4_A1_T7.js",
|
||||
"test/built-ins/String/prototype/charAt/S9.4_A1.js",
|
||||
@@ -61,6 +66,10 @@
|
||||
"test/built-ins/String/prototype/charCodeAt/S15.5.4.5_A1_T7.js",
|
||||
"test/built-ins/String/prototype/charCodeAt/pos-coerce-string.js",
|
||||
"test/built-ins/String/prototype/charCodeAt/pos-rounding.js",
|
||||
"test/built-ins/String/prototype/codePointAt/return-code-unit-coerced-position.js",
|
||||
"test/built-ins/String/prototype/codePointAt/return-utf16-decode.js",
|
||||
"test/built-ins/String/prototype/codePointAt/returns-undefined-on-position-equal-or-more-than-size.js",
|
||||
"test/built-ins/String/prototype/codePointAt/returns-undefined-on-position-less-than-zero.js",
|
||||
"test/built-ins/String/prototype/endsWith/return-false-if-search-start-is-less-than-zero.js",
|
||||
"test/built-ins/String/prototype/endsWith/return-true-if-searchstring-is-empty.js",
|
||||
"test/built-ins/String/prototype/endsWith/searchstring-found-with-position.js",
|
||||
@@ -192,6 +201,11 @@
|
||||
"test/built-ins/Number/S9.3_A4.2_T1.js": "fdf435ae47263f0372cce90bf25eb8ddae8d0abdc9dce30d4a3b098727a9ce6b",
|
||||
"test/built-ins/Number/isNaN/nan.js": "d513a909b333f40762bc08a9e2d1435d4ca097ed8b6da76497d0201a7264589e",
|
||||
"test/built-ins/RegExp/prototype/test/S15.10.6.3_A1_T16.js": "c96f0402289d55e41a2e0ef322060c5a143b599f456b94dc5ebe0493bc689f42",
|
||||
"test/built-ins/String/prototype/at/index-argument-tointeger.js": "563b86b614bb22f0bdc35ab51d67bf1ea68395e71bb22699f92ab48eed02337f",
|
||||
"test/built-ins/String/prototype/at/index-non-numeric-argument-tointeger.js": "37a09110b92218ff022e8e84abee3259c6083d880f193fc679f1bbfb803de03e",
|
||||
"test/built-ins/String/prototype/at/returns-item-relative-index.js": "a4be170547d99a5eed0dd4e86710ed8b4ae2b22246212f98eacd44f357744bae",
|
||||
"test/built-ins/String/prototype/at/returns-item.js": "c15c0d8635a9255f3ea7a82a57974a5973352be043c6834be296080e4324aa23",
|
||||
"test/built-ins/String/prototype/at/returns-undefined-for-out-of-range-index.js": "ec8106380287ef3f7a15adfc6fd1a351191078bff47102574be2500d3c2ced8a",
|
||||
"test/built-ins/String/prototype/charAt/S15.5.4.4_A1_T4.js": "5b69e2580d17931baf5c9780a1372f863d52077e7e75eae8d8f9f5e09e633a14",
|
||||
"test/built-ins/String/prototype/charAt/S15.5.4.4_A1_T7.js": "9f9e11e908365fa55a64b9f8e3939a278a750e5a8c1afb665dcd875e0bdb5612",
|
||||
"test/built-ins/String/prototype/charAt/S9.4_A1.js": "5df5b9b1420a1152c0a37f2a60d603e29655a91fe953a733450e024cd9ac3966",
|
||||
@@ -201,6 +215,10 @@
|
||||
"test/built-ins/String/prototype/charCodeAt/S15.5.4.5_A1_T7.js": "db261622d446903e8675e41b10365e0ffd7310e5ff16836324b34f6e20346d2e",
|
||||
"test/built-ins/String/prototype/charCodeAt/pos-coerce-string.js": "bbfab35f15f7b86d54c77259365199c92535df80dddd51c8c1cbee67bbdadac3",
|
||||
"test/built-ins/String/prototype/charCodeAt/pos-rounding.js": "e1a10338edd67a2cdf64e9e8478a3c9a27556a28976df5933f5d49b2139fdaa5",
|
||||
"test/built-ins/String/prototype/codePointAt/return-code-unit-coerced-position.js": "7240a5a919f9561de06b1a9f0d82585726435e5ed203e9c5201cd83f4ec968b6",
|
||||
"test/built-ins/String/prototype/codePointAt/return-utf16-decode.js": "3ab42fb5ae478fb230f2d2197fa432aa8eaffe03f34865daf4a29d7cec226c41",
|
||||
"test/built-ins/String/prototype/codePointAt/returns-undefined-on-position-equal-or-more-than-size.js": "f1b02e76f433e28a6f6b2f1a299e15fb38fc50ff570a1c26917a171ab72b3310",
|
||||
"test/built-ins/String/prototype/codePointAt/returns-undefined-on-position-less-than-zero.js": "c66bcc050ac6c05bf020190fa70c7f371cfdae9bde36d77f51980e0cb40354c2",
|
||||
"test/built-ins/String/prototype/endsWith/return-false-if-search-start-is-less-than-zero.js": "fae2241ee1b7c8e85c8ac351710f109069f407041f851ab39ae3691e35e8b696",
|
||||
"test/built-ins/String/prototype/endsWith/return-true-if-searchstring-is-empty.js": "708b505a6dc830c907bb483b99c124582de700a9f2c0251985ace72361afaa72",
|
||||
"test/built-ins/String/prototype/endsWith/searchstring-found-with-position.js": "532eb90be62d4faa2faa230ee91180ea260dc330b4427b9a08a696ac7417a3a4",
|
||||
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
// Copyright (C) 2020 Rick Waldron. All rights reserved.
|
||||
// This code is governed by the BSD license found in the LICENSE file.
|
||||
/*---
|
||||
esid: sec-string.prototype.at
|
||||
description: >
|
||||
Property type and descriptor.
|
||||
info: |
|
||||
String.prototype.at( index )
|
||||
|
||||
Let relativeIndex be ? ToInteger(index).
|
||||
|
||||
features: [String.prototype.at]
|
||||
---*/
|
||||
assert.sameValue(typeof String.prototype.at, 'function');
|
||||
|
||||
let valueOfCallCount = 0;
|
||||
let index = {
|
||||
valueOf() {
|
||||
valueOfCallCount++;
|
||||
return 1;
|
||||
}
|
||||
};
|
||||
|
||||
let s = "01";
|
||||
|
||||
assert.sameValue(s.at(index), '1', 's.at({valueOf() {valueOfCallCount++; return 1;}}) must return 1');
|
||||
assert.sameValue(valueOfCallCount, 1, 'The value of `valueOfCallCount` is 1');
|
||||
Vendored
+26
@@ -0,0 +1,26 @@
|
||||
// Copyright (C) 2020 Rick Waldron. All rights reserved.
|
||||
// This code is governed by the BSD license found in the LICENSE file.
|
||||
/*---
|
||||
esid: sec-string.prototype.at
|
||||
description: >
|
||||
Property type and descriptor.
|
||||
info: |
|
||||
String.prototype.at( index )
|
||||
|
||||
Let relativeIndex be ? ToInteger(index).
|
||||
|
||||
features: [String.prototype.at]
|
||||
---*/
|
||||
assert.sameValue(typeof String.prototype.at, 'function');
|
||||
|
||||
let s = "01";
|
||||
|
||||
assert.sameValue(s.at(false), '0', 's.at(false) must return 0');
|
||||
assert.sameValue(s.at(null), '0', 's.at(null) must return 0');
|
||||
assert.sameValue(s.at(undefined), '0', 's.at(undefined) must return 0');
|
||||
assert.sameValue(s.at(""), '0', 's.at("") must return 0');
|
||||
assert.sameValue(s.at(function() {}), '0', 's.at(function() {}) must return 0');
|
||||
assert.sameValue(s.at([]), '0', 's.at([]) must return 0');
|
||||
|
||||
assert.sameValue(s.at(true), '1', 's.at(true) must return 1');
|
||||
assert.sameValue(s.at("1"), '1', 's.at("1") must return 1');
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
// Copyright (C) 2020 Rick Waldron. All rights reserved.
|
||||
// This code is governed by the BSD license found in the LICENSE file.
|
||||
/*---
|
||||
esid: sec-string.prototype.at
|
||||
description: >
|
||||
Returns the item value at the specified relative index
|
||||
info: |
|
||||
String.prototype.at ( )
|
||||
|
||||
Let O be ? ToObject(this value).
|
||||
Let len be ? LengthOfStringLike(O).
|
||||
Let relativeIndex be ? ToInteger(index).
|
||||
If relativeIndex ≥ 0, then
|
||||
Let k be relativeIndex.
|
||||
Else,
|
||||
Let k be len + relativeIndex.
|
||||
If k < 0 or k ≥ len, then return undefined.
|
||||
Return ? Get(O, ! ToString(k)).
|
||||
|
||||
features: [String.prototype.at]
|
||||
---*/
|
||||
assert.sameValue(typeof String.prototype.at, 'function');
|
||||
|
||||
let s = "12345";
|
||||
|
||||
assert.sameValue(s.at(0), "1", 's.at(0) must return "1"');
|
||||
assert.sameValue(s.at(-1), "5", 's.at(-1) must return "5"');
|
||||
assert.sameValue(s.at(-3), "3", 's.at(-3) must return "3"');
|
||||
assert.sameValue(s.at(-4), "2", 's.at(-4) must return "2"');
|
||||
@@ -0,0 +1,30 @@
|
||||
// Copyright (C) 2020 Rick Waldron. All rights reserved.
|
||||
// This code is governed by the BSD license found in the LICENSE file.
|
||||
/*---
|
||||
esid: sec-string.prototype.at
|
||||
description: >
|
||||
Returns the item value at the specified index
|
||||
info: |
|
||||
String.prototype.at ( )
|
||||
|
||||
Let O be ? ToObject(this value).
|
||||
Let len be ? LengthOfStringLike(O).
|
||||
Let relativeIndex be ? ToInteger(index).
|
||||
If relativeIndex ≥ 0, then
|
||||
Let k be relativeIndex.
|
||||
Else,
|
||||
Let k be len + relativeIndex.
|
||||
If k < 0 or k ≥ len, then return undefined.
|
||||
Return ? Get(O, ! ToString(k)).
|
||||
|
||||
features: [String.prototype.at]
|
||||
---*/
|
||||
assert.sameValue(typeof String.prototype.at, 'function');
|
||||
|
||||
let s = "12345";
|
||||
|
||||
assert.sameValue(s.at(0), "1", 's.at(0) must return "1"');
|
||||
assert.sameValue(s.at(1), "2", 's.at(1) must return "2"');
|
||||
assert.sameValue(s.at(2), "3", 's.at(2) must return "3"');
|
||||
assert.sameValue(s.at(3), "4", 's.at(3) must return "4"');
|
||||
assert.sameValue(s.at(4), "5", 's.at(4) must return "5"');
|
||||
Vendored
+20
@@ -0,0 +1,20 @@
|
||||
// Copyright (C) 2020 Rick Waldron. All rights reserved.
|
||||
// This code is governed by the BSD license found in the LICENSE file.
|
||||
/*---
|
||||
esid: sec-string.prototype.at
|
||||
description: >
|
||||
Creates an iterator from a custom object.
|
||||
info: |
|
||||
String.prototype.at( index )
|
||||
|
||||
If k < 0 or k ≥ len, then return undefined.
|
||||
features: [String.prototype.at]
|
||||
---*/
|
||||
assert.sameValue(typeof String.prototype.at, 'function');
|
||||
|
||||
let s = "";
|
||||
|
||||
assert.sameValue(s.at(-2), undefined, 's.at(-2) must return undefined'); // wrap around the end
|
||||
assert.sameValue(s.at(0), undefined, 's.at(0) must return undefined');
|
||||
assert.sameValue(s.at(1), undefined, 's.at(1) must return undefined');
|
||||
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
// Copyright (C) 2015 the V8 project authors. All rights reserved.
|
||||
// This code is governed by the BSD license found in the LICENSE file.
|
||||
/*---
|
||||
es6id: 21.1.3.3
|
||||
description: >
|
||||
Return value on coerced values on ToInteger(position).
|
||||
info: |
|
||||
21.1.3.3 String.prototype.codePointAt ( pos )
|
||||
|
||||
...
|
||||
4. Let position be ToInteger(pos).
|
||||
...
|
||||
|
||||
---*/
|
||||
|
||||
assert.sameValue('\uD800\uDC00'.codePointAt(''), 65536);
|
||||
assert.sameValue('\uD800\uDC00'.codePointAt('0'), 65536);
|
||||
assert.sameValue('\uD800\uDC00'.codePointAt(NaN), 65536);
|
||||
assert.sameValue('\uD800\uDC00'.codePointAt(false), 65536);
|
||||
assert.sameValue('\uD800\uDC00'.codePointAt(null), 65536);
|
||||
assert.sameValue('\uD800\uDC00'.codePointAt(undefined), 65536);
|
||||
assert.sameValue('\uD800\uDC00'.codePointAt([]), 65536);
|
||||
|
||||
assert.sameValue('\uD800\uDC00'.codePointAt('1'), 56320);
|
||||
assert.sameValue('\uD800\uDC00'.codePointAt(true), 56320);
|
||||
assert.sameValue('\uD800\uDC00'.codePointAt([1]), 56320);
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
// Copyright (C) 2015 the V8 project authors. All rights reserved.
|
||||
// This code is governed by the BSD license found in the LICENSE file.
|
||||
/*---
|
||||
es6id: 21.1.3.3
|
||||
description: >
|
||||
Return UTF16 Decode value of the lead and trail elements at index position.
|
||||
info: |
|
||||
21.1.3.3 String.prototype.codePointAt ( pos )
|
||||
|
||||
...
|
||||
8. Let first be the code unit value of the element at index position in the
|
||||
String S.
|
||||
9. If first < 0xD800 or first > 0xDBFF or position+1 = size, return first.
|
||||
10. Let second be the code unit value of the element at index position+1 in
|
||||
the String S.
|
||||
11. If second < 0xDC00 or second > 0xDFFF, return first.
|
||||
12. Return UTF16Decode(first, second).
|
||||
|
||||
10.1.2 Static Semantics: UTF16Decode( lead, trail )
|
||||
|
||||
Two code units, lead and trail, that form a UTF-16 surrogate pair are
|
||||
converted to a code point by performing the following steps:
|
||||
|
||||
1. Assert: 0xD800 ≤ lead ≤ 0xDBFF and 0xDC00 ≤ trail ≤ 0xDFFF.
|
||||
2. Let cp be (lead – 0xD800) × 1024 + (trail – 0xDC00) + 0x10000.
|
||||
3. Return the code point cp.
|
||||
---*/
|
||||
|
||||
assert.sameValue('\uD800\uDC00'.codePointAt(0), 65536, 'U+10000');
|
||||
assert.sameValue('\uD800\uDDD0'.codePointAt(0), 66000, 'U+101D0');
|
||||
assert.sameValue('\uD800\uDFFF'.codePointAt(0), 66559, 'U+103FF');
|
||||
|
||||
assert.sameValue('\uDAAA\uDC00'.codePointAt(0), 763904, 'U+BA800');
|
||||
assert.sameValue('\uDAAA\uDDD0'.codePointAt(0), 764368, 'U+BA9D0');
|
||||
assert.sameValue('\uDAAA\uDFFF'.codePointAt(0), 764927, 'U+BABFF');
|
||||
|
||||
assert.sameValue('\uDBFF\uDC00'.codePointAt(0), 1113088, 'U+10FC00');
|
||||
assert.sameValue('\uDBFF\uDDD0'.codePointAt(0), 1113552, 'U+10FDD0');
|
||||
assert.sameValue('\uDBFF\uDFFF'.codePointAt(0), 1114111, 'U+10FFFF');
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
// Copyright (C) 2015 the V8 project authors. All rights reserved.
|
||||
// This code is governed by the BSD license found in the LICENSE file.
|
||||
/*---
|
||||
es6id: 21.1.3.3
|
||||
description: >
|
||||
If pos >= size, return undefined
|
||||
info: |
|
||||
21.1.3.3 String.prototype.codePointAt ( pos )
|
||||
|
||||
1. Let O be RequireObjectCoercible(this value).
|
||||
2. Let S be ToString(O).
|
||||
3. ReturnIfAbrupt(S).
|
||||
4. Let position be ToInteger(pos).
|
||||
5. ReturnIfAbrupt(position).
|
||||
6. Let size be the number of elements in S.
|
||||
7. If position < 0 or position ≥ size, return undefined.
|
||||
---*/
|
||||
|
||||
assert.sameValue('abc'.codePointAt(3), undefined);
|
||||
assert.sameValue('abc'.codePointAt(4), undefined);
|
||||
assert.sameValue('abc'.codePointAt(Infinity), undefined);
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
// Copyright (C) 2015 the V8 project authors. All rights reserved.
|
||||
// This code is governed by the BSD license found in the LICENSE file.
|
||||
/*---
|
||||
es6id: 21.1.3.3
|
||||
description: >
|
||||
If pos < size, return undefined
|
||||
info: |
|
||||
21.1.3.3 String.prototype.codePointAt ( pos )
|
||||
|
||||
1. Let O be RequireObjectCoercible(this value).
|
||||
2. Let S be ToString(O).
|
||||
3. ReturnIfAbrupt(S).
|
||||
4. Let position be ToInteger(pos).
|
||||
5. ReturnIfAbrupt(position).
|
||||
6. Let size be the number of elements in S.
|
||||
7. If position < 0 or position ≥ size, return undefined.
|
||||
---*/
|
||||
|
||||
assert.sameValue('abc'.codePointAt(-1), undefined);
|
||||
assert.sameValue('abc'.codePointAt(-Infinity), undefined);
|
||||
Reference in New Issue
Block a user