mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-10-28 06:53:42 +08:00
Address review comments
This commit is contained in:
@@ -225,6 +225,11 @@ function classConversion(
|
||||
plan.method === "sym:toPrimitive" ? [{ kind: "strLit", value: hint, type: STRING, loc }] : [];
|
||||
return finish(accessorCall(lowerer, cls.def.name, plan.method, object, args, plan.ret, loc));
|
||||
};
|
||||
// "[object Object]" reads nothing of the instance, but the receiver
|
||||
// expression still runs, once.
|
||||
const own = plans.get(info)!;
|
||||
if (tops.length === 0 && own.method === null && !own.date && !own.tag && !isSafeToRepeat(value))
|
||||
prefix.push({ kind: "exprStmt", expr: value, loc });
|
||||
let result = convert(info, receiver);
|
||||
for (let i = tops.length - 1; i >= 0; i--) {
|
||||
const top = tops[i]!;
|
||||
|
||||
@@ -18,6 +18,7 @@ import { jsFuncNameOf, type Lowerer } from "./lowerer.js";
|
||||
import { isJsSourceFile } from "../program.js";
|
||||
import {
|
||||
BOOL,
|
||||
CAUGHT,
|
||||
DYN,
|
||||
F64,
|
||||
type IrExpr,
|
||||
@@ -1876,7 +1877,8 @@ function inspectHelper(lowerer: Lowerer, t: IrType, loc: SrcLoc): string {
|
||||
const visible = info.def.fields.filter((f) => isClassOwnEnumerableFieldName(f.name));
|
||||
// A Symbol.toStringTag getter (an own tag field is listed instead)
|
||||
// brackets the tag after the name, `Name [tag] {`, when it is a
|
||||
// nonempty string other than the name.
|
||||
// nonempty string other than the name. A getter that throws shows
|
||||
// no tag, as in Node.
|
||||
const tagGetter = info.fields.has(TO_STRING_TAG_FIELD)
|
||||
? null
|
||||
: findMethodOn(lowerer, info, "get:sym:toStringTag");
|
||||
@@ -1884,21 +1886,36 @@ function inspectHelper(lowerer: Lowerer, t: IrType, loc: SrcLoc): string {
|
||||
strLit(plain, loc);
|
||||
if (tagGetter?.sig.ret.kind === "string") {
|
||||
const owner = tagGetter.declarer.def.name;
|
||||
locals.push({ id: "tag.0", name: "tag", type: STRING, mutable: false });
|
||||
body.push({
|
||||
kind: "varDecl",
|
||||
localId: "tag.0",
|
||||
init: accessorCall(
|
||||
lowerer,
|
||||
owner,
|
||||
"get:sym:toStringTag",
|
||||
lowerer.upcastTo(v(), owner),
|
||||
[],
|
||||
STRING,
|
||||
locals.push(
|
||||
{ id: "tag.0", name: "tag", type: STRING, mutable: true },
|
||||
{ id: "tagError.0", name: "tagError", type: CAUGHT, mutable: false },
|
||||
);
|
||||
body.push(
|
||||
{ kind: "varDecl", localId: "tag.0", init: strLit("", loc), loc },
|
||||
{
|
||||
kind: "tryCatch",
|
||||
tryBody: [
|
||||
{
|
||||
kind: "assign",
|
||||
localId: "tag.0",
|
||||
value: accessorCall(
|
||||
lowerer,
|
||||
owner,
|
||||
"get:sym:toStringTag",
|
||||
lowerer.upcastTo(v(), owner),
|
||||
[],
|
||||
STRING,
|
||||
loc,
|
||||
),
|
||||
loc,
|
||||
},
|
||||
],
|
||||
catchBody: [],
|
||||
catchLocalId: "tagError.0",
|
||||
finallyBody: null,
|
||||
loc,
|
||||
),
|
||||
loc,
|
||||
});
|
||||
},
|
||||
);
|
||||
const tag = (): IrExpr => varRef("tag.0", STRING, loc);
|
||||
const shown: IrExpr = {
|
||||
kind: "logical",
|
||||
|
||||
@@ -144,6 +144,10 @@ static void scr_dyn_display_buf(ScrJsonBuf *b, const ScrDyn *d, bool to_string)
|
||||
return;
|
||||
|
||||
case SCR_DYN_TYPED_REF: {
|
||||
/* ToString of a class instance that converts through
|
||||
* Object.prototype.toString shows its Symbol.toStringTag getter's
|
||||
* string. */
|
||||
if (to_string && scr_dyn_typed_ref_put_tag(b, d)) return;
|
||||
ScrDyn *materialized = scr_dyn_typed_ref_materialize(d);
|
||||
scr_dyn_display_buf(b, materialized, to_string);
|
||||
scr_dyn_release(materialized);
|
||||
|
||||
@@ -1210,9 +1210,10 @@ static bool insp_tag_names_class(const char *tag, const char *cls) {
|
||||
/* The Symbol.toStringTag a class instance inherits from its class's
|
||||
* getter (+1), which Node brackets after the class name when it is a
|
||||
* nonempty string other than the name. An own tag field is listed with
|
||||
* the fields instead. */
|
||||
* the fields instead. A getter that throws shows no tag, as in Node. */
|
||||
static ScrStr *insp_inherited_tag(const ScrDyn *instance, const ScrStr *name) {
|
||||
ScrStr *tag = scr_dyn_typed_ref_class_tag(instance);
|
||||
if (!tag && scr_exc_pending()) scr_exc_clear();
|
||||
if (tag && (tag->len == 0 || (tag->len == name->len && memcmp(tag->data, name->data, name->len) == 0))) {
|
||||
scr_str_release(tag);
|
||||
return NULL;
|
||||
|
||||
@@ -4034,6 +4034,9 @@ ScrDyn *scr_dyn_index_get(const ScrDyn *d, double index) {
|
||||
return scr_dyn_typed_ref_element(d, i);
|
||||
}
|
||||
|
||||
static ScrStr *scr_dyn_typed_ref_conversion_tag(const ScrDyn *d);
|
||||
static ScrStr *scr_dyn_tag_string(const ScrStr *tag);
|
||||
|
||||
ScrDyn *scr_dyn_typed_ref_string_view(const ScrDyn *d) {
|
||||
/* An instance of a class declared `extends Date` converts through its
|
||||
* toString, declared or inherited. With an exception pending the view is
|
||||
@@ -4047,6 +4050,16 @@ ScrDyn *scr_dyn_typed_ref_string_view(const ScrDyn *d) {
|
||||
scr_str_release(empty);
|
||||
return out;
|
||||
}
|
||||
/* A class instance converting through Object.prototype.toString shows
|
||||
* its Symbol.toStringTag getter's string. */
|
||||
ScrStr *class_tag = scr_dyn_typed_ref_conversion_tag(d);
|
||||
if (class_tag || scr_exc_pending()) {
|
||||
ScrStr *text = class_tag ? scr_dyn_tag_string(class_tag) : scr_str_new("", 0);
|
||||
scr_str_release(class_tag);
|
||||
ScrDyn *out = scr_dyn_new_str(text);
|
||||
scr_str_release(text);
|
||||
return out;
|
||||
}
|
||||
ScrDyn *view = scr_dyn_typed_ref_materialize(d);
|
||||
const char *tag = scr_event_ref_hooks ? scr_event_ref_hooks->string_tag(d) : NULL;
|
||||
if (!tag || view->kind != SCR_DYN_OBJ || scr_dyn_property_owner(view, "toString", 8)) return view;
|
||||
@@ -8333,6 +8346,27 @@ static ScrStr *scr_dyn_tag_string(const ScrStr *tag) {
|
||||
return scr_jb_finish(&b);
|
||||
}
|
||||
|
||||
/* The Symbol.toStringTag getter's string of a class instance whose
|
||||
* ToString is Object.prototype.toString (its class has no toString; the
|
||||
* string hint never reaches valueOf, since that answers a string): +1, or
|
||||
* NULL otherwise or with the getter's exception pending. */
|
||||
static ScrStr *scr_dyn_typed_ref_conversion_tag(const ScrDyn *d) {
|
||||
ScrStr *to_string_key = scr_str_new("toString", 8);
|
||||
bool own_conversion = scr_dyn_typed_ref_class_has(d, to_string_key);
|
||||
scr_str_release(to_string_key);
|
||||
return own_conversion ? NULL : scr_dyn_typed_ref_class_tag(d);
|
||||
}
|
||||
|
||||
bool scr_dyn_typed_ref_put_tag(ScrJsonBuf *b, const ScrDyn *d) {
|
||||
ScrStr *tag = scr_dyn_typed_ref_conversion_tag(d);
|
||||
if (!tag) return scr_exc_pending();
|
||||
scr_jb_puts(b, "[object ");
|
||||
scr_jb_write(b, tag->data, tag->len);
|
||||
scr_jb_putc(b, ']');
|
||||
scr_str_release(tag);
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Object.prototype.toString of an object whose own or inherited
|
||||
* @@toStringTag is a string (+1), NULL otherwise or with a pending
|
||||
* exception from its getter. */
|
||||
@@ -9676,22 +9710,13 @@ ScrStr *scr_dyn_string_coerce_js(const ScrDyn *d) {
|
||||
return NULL;
|
||||
}
|
||||
if (d->kind == SCR_DYN_TYPED_REF) {
|
||||
/* A class instance without its own conversion methods converts with
|
||||
* Object.prototype.toString, which reads its toStringTag getter. */
|
||||
ScrStr *to_string_key = scr_str_new("toString", 8), *value_of_key = scr_str_new("valueOf", 7);
|
||||
bool own_conversion = scr_dyn_typed_ref_class_has(d, to_string_key) ||
|
||||
scr_dyn_typed_ref_class_has(d, value_of_key);
|
||||
scr_str_release(to_string_key);
|
||||
scr_str_release(value_of_key);
|
||||
if (!own_conversion) {
|
||||
ScrStr *tag = scr_dyn_typed_ref_class_tag(d);
|
||||
if (tag) {
|
||||
ScrStr *out = scr_dyn_tag_string(tag);
|
||||
scr_str_release(tag);
|
||||
return out;
|
||||
}
|
||||
if (scr_exc_pending()) return NULL;
|
||||
ScrStr *tag = scr_dyn_typed_ref_conversion_tag(d);
|
||||
if (tag) {
|
||||
ScrStr *out = scr_dyn_tag_string(tag);
|
||||
scr_str_release(tag);
|
||||
return out;
|
||||
}
|
||||
if (scr_exc_pending()) return NULL;
|
||||
ScrDyn *materialized = scr_dyn_typed_ref_string_view(d);
|
||||
ScrStr *out = scr_dyn_string_coerce_js(materialized);
|
||||
scr_dyn_release(materialized);
|
||||
|
||||
@@ -119,6 +119,11 @@ _Static_assert(offsetof(ScrJsonBuf, data) == 0 && offsetof(ScrJsonBuf, len) == s
|
||||
"LLVM backend expects ScrJsonBuf to start with { data, len, cap }");
|
||||
|
||||
void scr_jb_init(ScrJsonBuf *b);
|
||||
/* String conversion of a class instance whose ToString is
|
||||
* Object.prototype.toString and whose class has a Symbol.toStringTag
|
||||
* getter: appends "[object Tag]" and answers true, also with the getter's
|
||||
* exception pending; false when the ordinary conversion applies. */
|
||||
bool scr_dyn_typed_ref_put_tag(ScrJsonBuf *b, const ScrDyn *d);
|
||||
/* Release a buffer's text without finishing it (error paths). */
|
||||
void scr_jb_abandon(ScrJsonBuf *b);
|
||||
/* Append a borrowed byte span verbatim, including embedded NUL bytes. */
|
||||
|
||||
@@ -74,3 +74,42 @@ console.log(values);
|
||||
|
||||
const literal: unknown = { [Symbol.toStringTag]: "Literal", a: 1 };
|
||||
console.log(Object.prototype.toString.call(literal), String(literal), `${literal}`);
|
||||
|
||||
// util.inspect ignores a Symbol.toStringTag getter that throws, typed or
|
||||
// held as an untyped value.
|
||||
class Throwing {
|
||||
get [Symbol.toStringTag](): string {
|
||||
throw new Error("tag");
|
||||
}
|
||||
}
|
||||
const throwing: unknown = new Throwing();
|
||||
console.log(new Throwing(), [new Throwing()], throwing, [throwing]);
|
||||
|
||||
// A conversion to "[object Object]" still evaluates its receiver.
|
||||
let made = 0;
|
||||
function make(): Plain {
|
||||
made++;
|
||||
return new Plain();
|
||||
}
|
||||
function fail(): Plain {
|
||||
throw new Error("receiver");
|
||||
}
|
||||
console.log(Object.prototype.toString.call(make()), String(make()), made);
|
||||
try {
|
||||
console.log(Object.prototype.toString.call(fail()));
|
||||
} catch (error) {
|
||||
console.log((error as Error).message);
|
||||
}
|
||||
|
||||
// ToString of a class with valueOf and no toString reads its tag; the
|
||||
// default hint calls valueOf.
|
||||
class Valued {
|
||||
valueOf(): number {
|
||||
return 1;
|
||||
}
|
||||
get [Symbol.toStringTag](): string {
|
||||
return "Valued";
|
||||
}
|
||||
}
|
||||
const valued: unknown = new Valued();
|
||||
console.log(String(valued), `${valued}`, [valued].join(), valued + "", Object.prototype.toString.call(valued));
|
||||
|
||||
Reference in New Issue
Block a user