mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-11-01 07:33:12 +08:00
Test262 group B: generator method ABI, empty-pattern throws (#2505)
This commit is contained in:
@@ -3451,7 +3451,15 @@ export function collectClassShapeInner(
|
||||
if (shape.bodyType) shape.bodyType = DYN;
|
||||
}
|
||||
ft.params = shapes.map((shape) => shape.type);
|
||||
if (ft.ret.kind !== "void") ft.ret = DYN;
|
||||
// A GENERATOR method's result is the suspended generator its spawn
|
||||
// wrapper answers, so the generator kind IS the checked-crossable
|
||||
// ABI: dynFrom boxes a generator by reference and dyn calls resume
|
||||
// it through the dyn generator ops. Replacing it with plain DYN
|
||||
// would leave the method no representation at all (the generator
|
||||
// branch below needs the channels to build its metadata), which
|
||||
// fenced every `c.m` read on a prototype-accessed class. Only the
|
||||
// ARGUMENTS cross as checked values here.
|
||||
if (ft.ret.kind !== "void" && member.asteriskToken === undefined) ft.ret = DYN;
|
||||
}
|
||||
// JS subclasses can add parameters to a method. Reserve checked
|
||||
// argument slots in the base before constructing its vtable; shorter
|
||||
|
||||
@@ -2617,7 +2617,7 @@ export function lowerDestructuringDecl(
|
||||
init.type.kind === "dyn" &&
|
||||
ts.isObjectBindingPattern(decl.name) &&
|
||||
isJsSourceFile(ts.getSourceFileOfNode(decl)) &&
|
||||
startsWithRuntimeKey(lowerer, decl.name) &&
|
||||
(decl.name.elements.nodes.length === 0 || startsWithRuntimeKey(lowerer, decl.name)) &&
|
||||
destrSpellingOf(decl.initializer) !== null;
|
||||
if (
|
||||
jsRuntimeKeyCheck ||
|
||||
@@ -3181,6 +3181,39 @@ export function lowerBindingPattern(
|
||||
// every statically non-nullish kind, record or not (the assignment
|
||||
// path's rule); possibly-nullish sources would throw in JS and keep
|
||||
// a fence.
|
||||
//
|
||||
// A CHECKED-DYNAMIC source still owes that step at runtime: an empty
|
||||
// object pattern over null/undefined throws RequireObjectCoercible,
|
||||
// and an empty array pattern throws GetIterator's "is not iterable".
|
||||
// Nothing else about the pattern is observable, so the fence is the
|
||||
// whole lowering.
|
||||
if (srcType.kind === "dyn") {
|
||||
const unitLoc = locOf(pattern);
|
||||
const isObject = ts.isObjectBindingPattern(pattern);
|
||||
for (const unit of ["null", "undefined"] as const) {
|
||||
// V8 describes an argument-derived source by its type ("object
|
||||
// null"); a bare non-empty pattern names its source instead, but
|
||||
// those reads throw on their own before this check could fire.
|
||||
const described = unit === "null" ? "object null" : "undefined";
|
||||
const message = isObject
|
||||
? `Cannot destructure '${described}' as it is ${unit}.`
|
||||
: `${described} is not iterable (cannot read property Symbol(Symbol.iterator))`;
|
||||
out.push({
|
||||
kind: "if",
|
||||
cond: { kind: "dynTest", test: unit, value: srcRef(), type: BOOL, loc: unitLoc },
|
||||
then: [
|
||||
{
|
||||
kind: "exprStmt",
|
||||
expr: nodeThrowExpr(1, "", message, VOID, unitLoc),
|
||||
loc: unitLoc,
|
||||
},
|
||||
],
|
||||
else_: null,
|
||||
loc: unitLoc,
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (
|
||||
isUnitType(srcType) ||
|
||||
(srcType.kind === "union" &&
|
||||
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/8516-empty-pattern-requires-object-coercible.js"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/dyn-generator-method-value.js"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
// An empty destructuring pattern still performs its one observable step:
|
||||
// RequireObjectCoercible for `{}` and GetIterator for `[]`. A checked-dynamic
|
||||
// source reaches that step at runtime, so null and undefined throw the same
|
||||
// TypeError Node does while every other value — including primitives, which
|
||||
// ToObject wraps — is accepted. The throws are printed through
|
||||
// `instanceof` rather than the message, since V8 spells the source
|
||||
// differently per position (an argument is "object null", a declaration
|
||||
// names its expression).
|
||||
|
||||
function report(label, fn) {
|
||||
try {
|
||||
fn();
|
||||
console.log(label, "no-throw");
|
||||
} catch (error) {
|
||||
console.log(label, error instanceof TypeError ? "TypeError" : "other");
|
||||
}
|
||||
}
|
||||
|
||||
function acceptsWhatever(value) {
|
||||
return value;
|
||||
}
|
||||
|
||||
report("object-null", () => {
|
||||
const source = acceptsWhatever(null);
|
||||
const {} = source;
|
||||
});
|
||||
report("object-undefined", () => {
|
||||
const source = acceptsWhatever(undefined);
|
||||
const {} = source;
|
||||
});
|
||||
|
||||
function objectParameter({}) {
|
||||
return "bound";
|
||||
}
|
||||
report("param-null", () => {
|
||||
objectParameter(null);
|
||||
});
|
||||
report("param-undefined", () => {
|
||||
objectParameter(undefined);
|
||||
});
|
||||
report("param-number", () => {
|
||||
console.log("param-number returned", objectParameter(7));
|
||||
});
|
||||
report("param-string", () => {
|
||||
console.log("param-string returned", objectParameter("text"));
|
||||
});
|
||||
|
||||
function arrayParameter([]) {
|
||||
return "bound";
|
||||
}
|
||||
report("array-param-null", () => {
|
||||
arrayParameter(null);
|
||||
});
|
||||
report("array-param-undefined", () => {
|
||||
arrayParameter(undefined);
|
||||
});
|
||||
|
||||
function nestedObject({ inner: {} }) {
|
||||
return "nested";
|
||||
}
|
||||
report("nested-null", () => {
|
||||
nestedObject({ inner: null });
|
||||
});
|
||||
report("nested-undefined", () => {
|
||||
nestedObject({ inner: undefined });
|
||||
});
|
||||
report("nested-object", () => {
|
||||
console.log("nested-object returned", nestedObject({ inner: {} }));
|
||||
});
|
||||
|
||||
// A loop head rebinds the pattern per iteration, exactly like a declaration.
|
||||
report("for-of-object", () => {
|
||||
for (const {} of acceptsWhatever([null])) {
|
||||
console.log("never");
|
||||
}
|
||||
});
|
||||
|
||||
// Non-nullish primitives are accepted by both pattern kinds.
|
||||
report("primitives", () => {
|
||||
const numbers = [0, "", false, 1, "x"];
|
||||
for (const value of numbers) {
|
||||
const {} = value;
|
||||
console.log("destructured", JSON.stringify(value), typeof value);
|
||||
}
|
||||
});
|
||||
|
||||
// The assignment form keeps its own (already exact) check.
|
||||
report("assignment-null", () => {
|
||||
let bound;
|
||||
({} = bound);
|
||||
});
|
||||
report("assignment-number", () => {
|
||||
let bound = 5;
|
||||
({} = bound);
|
||||
console.log("assignment-number finished");
|
||||
});
|
||||
@@ -0,0 +1,53 @@
|
||||
/* A generator method VALUE crossing the checked-value boundary.
|
||||
*
|
||||
* A class whose methods are reachable through its prototype object answers
|
||||
* its methods as checked (dynamic) values: the value read off an instance is
|
||||
* the same function object the prototype carries, a checked callback can hold
|
||||
* it and call it, and the generator object it produces keeps the generator
|
||||
* protocol across the box. Node is the oracle. */
|
||||
class Seq {
|
||||
constructor(tag) {
|
||||
this.tag = tag;
|
||||
}
|
||||
*items(n) {
|
||||
for (let i = 0; i < n; i++) yield this.tag + ":" + i;
|
||||
return n;
|
||||
}
|
||||
async *later(n) {
|
||||
yield n + 1;
|
||||
return n;
|
||||
}
|
||||
}
|
||||
|
||||
function sameValue(actual, expected, label) {
|
||||
if (actual !== expected) throw new Error("not the same: " + label);
|
||||
}
|
||||
function typeofOf(value) {
|
||||
return typeof value;
|
||||
}
|
||||
function callWith(receiver, fn, ...args) {
|
||||
return fn.apply(receiver, args);
|
||||
}
|
||||
|
||||
const seq = new Seq("a");
|
||||
sameValue(seq.items, Seq.prototype.items, "instance method value");
|
||||
sameValue(seq.items, seq.items, "repeated read");
|
||||
sameValue(seq.later, Seq.prototype.later, "async instance method value");
|
||||
console.log(typeofOf(seq.items), typeofOf(seq.later));
|
||||
|
||||
const gen = callWith(seq, seq.items, 2);
|
||||
console.log(gen.next().value, gen.next().value);
|
||||
const finished = gen.next();
|
||||
console.log(finished.value, finished.done);
|
||||
|
||||
// The direct call answers its own generator object. (Identity and reflection
|
||||
// on a generator object are separate, still-fenced operations.)
|
||||
const direct = seq.items(1);
|
||||
console.log(direct.next().value);
|
||||
|
||||
// An async generator value crosses the same boundary.
|
||||
callWith(seq, seq.later, 4)
|
||||
.next()
|
||||
.then(function (step) {
|
||||
console.log("async", step.value, step.done);
|
||||
});
|
||||
@@ -0,0 +1 @@
|
||||
{ "plain": 2400, "san": 24000 }
|
||||
@@ -0,0 +1 @@
|
||||
{ "plain": 1349, "san": 2585 }
|
||||
Reference in New Issue
Block a user