mirror of
https://github.com/anomalyco/opencode.git
synced 2026-09-29 17:09:40 +08:00
fix(codemode): coerce match/search patterns, allow any for...in target, bind the last duplicate parameter (#50802)
This commit is contained in:
@@ -41,8 +41,8 @@ ultimate source of truth. Upstream test262 files run verbatim from `test/test262
|
||||
- [x] A trailing comma after a rest parameter is a syntax error, with or without `"use strict"`.
|
||||
- [x] A program that begins with `"use strict"` rejects `yield` as an identifier and duplicate parameter names at
|
||||
parse time. Without it, `yield` is an ordinary binding.
|
||||
- [ ] Duplicate parameter names in non-strict code throw when the function is called, instead of binding the last
|
||||
parameter as JavaScript does.
|
||||
- [x] Duplicate parameter names in non-strict code bind the last parameter, as in JS (`function f(a, a)` called
|
||||
with `(1, 2)` sees `a === 2`).
|
||||
|
||||
## Values and literals
|
||||
|
||||
@@ -89,7 +89,8 @@ ultimate source of truth. Upstream test262 files run verbatim from `test/test262
|
||||
sources are rejected.
|
||||
- [x] Destructuring reads through the prototype chain like member access: `const { constructor } = error` and
|
||||
`const { slice } = values` find the inherited built-in.
|
||||
- [ ] Member expressions as `for...in` targets (`for (x.y in obj)`).
|
||||
- [x] Any assignment target as a `for...in` head, like `for...of`: `for (x.y in obj)`, `for (a[i++] in obj)`, and
|
||||
destructuring patterns.
|
||||
|
||||
## Statements and control flow
|
||||
|
||||
@@ -354,11 +355,12 @@ reject }` object.
|
||||
native JS, `split(undefined)` returns the whole string, and `includes`/`startsWith`/`endsWith` reject regular
|
||||
expressions with a native-style `TypeError`. Opaque runtime references still reject as data errors, and
|
||||
`repeat` still requires a finite non-negative count.
|
||||
- [x] Native no-argument parity for `match()`, `matchAll()`, and `search()`; all behave as an empty pattern. Present
|
||||
arguments must still be a regular expression or string pattern.
|
||||
- [x] Native no-argument parity for `match()`, `matchAll()`, and `search()`; all behave as an empty pattern.
|
||||
- [x] `String.raw`, on a template object or any `{ raw }` object; raw strings and substitutions coerce through their own
|
||||
`toString`.
|
||||
- [ ] `match`, `search`, and `split` accept any value and coerce it (objects via `toString`), like JavaScript.
|
||||
- [x] `match`, `matchAll`, `search`, and `split` read any non-RegExp argument as a pattern string, as `new RegExp(arg)`
|
||||
would: `"a1b".match(1)` matches `/1/`, `search(null)` looks for `"null"`, and `undefined` is the empty pattern.
|
||||
Objects use their built-in string form until ToPrimitive lands.
|
||||
|
||||
## Numbers and Math
|
||||
|
||||
|
||||
@@ -937,10 +937,10 @@ class Frame<R> {
|
||||
|
||||
const keys = self.enumerableKeys(right, node.right)
|
||||
|
||||
if (left.type !== "Identifier" && left.type !== "VariableDeclaration") {
|
||||
if (left.type === "RestElement" || left.type === "AssignmentPattern") {
|
||||
throw typeError("Unsupported for...in binding.", left)
|
||||
}
|
||||
const assignmentName = left.type === "Identifier" ? left.name : undefined
|
||||
const assignment = left.type === "VariableDeclaration" ? undefined : left
|
||||
|
||||
for (const key of keys) {
|
||||
const result = yield* Effect.gen(function* () {
|
||||
@@ -950,8 +950,8 @@ class Frame<R> {
|
||||
yield* self.declarePattern(declared.pattern, key, declared.mutable, left, true)
|
||||
} else if (declared) {
|
||||
yield* self.assignPattern(declared.pattern, key, left)
|
||||
} else if (assignmentName) {
|
||||
self.scopes.set(assignmentName, key, left)
|
||||
} else if (assignment) {
|
||||
yield* self.assignPattern(assignment, key, left)
|
||||
}
|
||||
return yield* self.evaluateStatement(node.body)
|
||||
}).pipe(
|
||||
@@ -1672,6 +1672,9 @@ class Frame<R> {
|
||||
paramScope.set(name, { mutable: true, value: undefined, initialized: false })
|
||||
}
|
||||
}
|
||||
const parameters = fn.parameters.map((parameter) =>
|
||||
parameter.type === "Identifier" ? parameter.name : undefined,
|
||||
)
|
||||
const bind = Effect.gen(function* () {
|
||||
for (const [index, parameter] of fn.parameters.entries()) {
|
||||
if (parameter.type === "RestElement") {
|
||||
@@ -1684,6 +1687,8 @@ class Frame<R> {
|
||||
)
|
||||
break
|
||||
}
|
||||
// A sloppy simple parameter list may repeat a name; the last occurrence wins, as in JS.
|
||||
if (parameter.type === "Identifier" && parameters.lastIndexOf(parameter.name) !== index) continue
|
||||
yield* invocation.declarePattern(parameter, args[index], true, parameter, true)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -33,21 +33,16 @@ const escapeRegexHint =
|
||||
'To match special characters like ( ) [ ] { } + * ? . literally, escape them with a backslash (e.g. "\\\\(") or test for them with String.includes instead.'
|
||||
|
||||
export const toHostRegex = (arg: Value, method: string, extraFlags = ""): RegExp => {
|
||||
// Native parity: an undefined pattern behaves as an empty pattern.
|
||||
if (arg === undefined) return new RegExp("", extraFlags)
|
||||
if (arg instanceof RegExpObj) return arg.regex
|
||||
if (typeof arg === "string") {
|
||||
try {
|
||||
return new RegExp(arg, extraFlags)
|
||||
} catch (error) {
|
||||
throw syntaxError(
|
||||
`String.${method} received the string ${JSON.stringify(arg)}, which is not a valid regular expression pattern (${regexFailureReason(error)}). ${escapeRegexHint}`,
|
||||
)
|
||||
}
|
||||
// Anything else is a pattern string, as `new RegExp(arg)` would read it: undefined is the empty pattern.
|
||||
const source = arg === undefined ? "" : coerceToString(arg)
|
||||
try {
|
||||
return new RegExp(source, extraFlags)
|
||||
} catch (error) {
|
||||
throw syntaxError(
|
||||
`String.${method} received the string ${JSON.stringify(source)}, which is not a valid regular expression pattern (${regexFailureReason(error)}). ${escapeRegexHint}`,
|
||||
)
|
||||
}
|
||||
throw typeError(
|
||||
`String.${method} expects a regular expression (a /pattern/flags literal or new RegExp(...)) or a string pattern, not ${arg === null ? "null" : typeof arg}.`,
|
||||
)
|
||||
}
|
||||
|
||||
export const matchToValue = (builtins: Builtins, match: RegExpMatchArray): Arr => {
|
||||
|
||||
@@ -1156,3 +1156,25 @@ describe("String.raw", () => {
|
||||
expect(failure.message).toContain("String.raw expects a template object with a raw array")
|
||||
})
|
||||
})
|
||||
|
||||
describe("sloppy duplicate parameters and for...in targets", () => {
|
||||
test("a repeated parameter name binds the last argument", async () => {
|
||||
expect(await value(`function f(a, b, a) { return [a, b] } return [f(1, 2, 3), f(1)]`)).toEqual([
|
||||
[3, 2],
|
||||
[null, null],
|
||||
])
|
||||
})
|
||||
|
||||
test("for...in assigns to any target: members, computed members, and patterns", async () => {
|
||||
expect(
|
||||
await value(`
|
||||
const x = {}, seen = [], a = []
|
||||
let i = 0, first
|
||||
for (x.y in { p: 1, q: 2 }) seen.push(x.y)
|
||||
for (a[i++] in { p: 1, q: 2 });
|
||||
for ([first] in { ab: 1 });
|
||||
return [seen, x.y, a, first]
|
||||
`),
|
||||
).toEqual([["p", "q"], "q", ["p", "q"], "a"])
|
||||
})
|
||||
})
|
||||
|
||||
@@ -452,10 +452,12 @@ describe("RegExp", () => {
|
||||
expect((await error(`return "aa".matchAll(/a/)`)).message).toContain("write /a/g, or use String.match")
|
||||
})
|
||||
|
||||
test("a non-pattern argument names the expected shapes", async () => {
|
||||
const err = await error(`return "abc".match(42)`)
|
||||
expect(err.message).toContain("expects a regular expression")
|
||||
expect(err.message).toContain("not number")
|
||||
test("any argument is a pattern string, as new RegExp(arg) reads it", async () => {
|
||||
expect(
|
||||
await value(
|
||||
`return ["a42b".match(42)[0], "xnullx".search(null), "abc".match(undefined), [..."1a1".matchAll(1)].length]`,
|
||||
),
|
||||
).toEqual(["42", 1, [""], 2])
|
||||
})
|
||||
|
||||
test("source and flags properties read through", async () => {
|
||||
|
||||
@@ -215,7 +215,6 @@ language/statements/for-await-of/async-gen-decl-dstr-obj-empty-num.js # TypeErr
|
||||
language/statements/for-await-of/async-gen-decl-dstr-obj-empty-string.js # TypeError: Object destructuring requires a data object or array value, received a string.
|
||||
language/statements/for-await-of/async-gen-decl-dstr-obj-rest-number.js # TypeError: Object destructuring requires a data object or array value, received a number.
|
||||
language/statements/for-await-of/async-gen-decl-dstr-obj-rest-str-val.js # TypeError: Object destructuring requires a data object or array value, received a string.
|
||||
language/statements/for-in/head-lhs-member.js # Unsupported for...in binding.
|
||||
language/statements/for-of/dstr/array-elem-iter-rtrn-close-err.js # Iterator next must be a function.
|
||||
language/statements/for-of/dstr/array-elem-iter-thrw-close-err.js # Expected SameValue(«1», «0») to be true
|
||||
language/statements/for-of/dstr/array-elem-trlg-iter-list-thrw-close-err.js # Expected SameValue(«1», «0») to be true
|
||||
|
||||
Reference in New Issue
Block a user