Files
scriptc/tests/corpus/1592-js-classes.js
Chris Tate 521ba8f1ef Preserve JavaScript values beyond null initializers
- Keep null-initialized fields and inferred returns in mutable native storage, including inherited resets and saved callbacks.
- Convert checked native values with Number using JavaScript coercion and catchable errors.
2026-09-28 09:01:13 -05:00

119 lines
3.7 KiB
JavaScript

// JavaScript classes: fields declared by CONSTRUCTOR ASSIGNMENT (checkJs
// infers the property types), methods, getters, single inheritance with a
// virtual override dispatched through a base-typed element.
'use strict';
class Shape {
/** @param {string} name */
constructor(name) {
this.name = name;
this.hits = 0;
}
area() {
return 0;
}
describe() {
this.hits += 1;
return `${this.name}: ${this.area()}`;
}
get tag() {
return this.name.toUpperCase();
}
}
class Rect extends Shape {
/** @param {number} w @param {number} h */
constructor(w, h) {
super("rect");
this.w = w;
this.h = h;
}
area() {
return this.w * this.h;
}
}
class Square extends Rect {
/** @param {number} side */
constructor(side) {
super(side, side);
}
}
const shapes = [new Shape("blob"), new Rect(3, 4), new Square(5)];
for (const s of shapes) console.log(s.describe(), s.tag);
const r = new Rect(2, 6);
r.describe();
r.describe();
console.log("hits:", r.hits, "square?", shapes[2] instanceof Square, shapes[1] instanceof Square);
// An unused incompatible JS override must not prevent construction or
// unrelated methods. A deeper compatible override still dispatches normally.
class ReturnBase {
value = 10;
insert(value, anchor) { return 1; }
selected() { return "base"; }
read() { return this.value; }
dispatch(value) { return this.insert(value, undefined); }
}
class DormantReturns extends ReturnBase {
constructor() { super(); this.value = 20; }
// @ts-expect-error Exercise a valid JS override with a different return.
insert(value, anchor) { console.log("unused", value, anchor); }
// @ts-expect-error Exercise a valid JS override with a different return.
selected() { return 42; }
}
class RepairedReturns extends DormantReturns {
insert(value, anchor) { console.log("repaired", value, anchor); return 3; }
// @ts-expect-error The deeper override matches the original base ABI.
selected() { return "repaired"; }
}
class ShortDormantReturn extends ReturnBase {
// @ts-expect-error Both the return type and the unused tail differ.
insert(value) { console.log("unused short", value); }
}
const dormant = new DormantReturns();
console.log("dormant", dormant.read(), new ShortDormantReturn().read());
const repaired = new RepairedReturns();
console.log("repaired", repaired.dispatch("item"), repaired.selected());
// A null initializer does not restrict an inferred JS field to null forever.
class MutableNull {
value = null;
set(value) { this.value = value; }
read() { return this.value; }
get current() { return this.value; }
set current(value) { this.value = value; }
}
class ResetNull extends MutableNull {
value;
constructor(value) {
super();
console.log('reset', this.value === undefined);
this.value = value;
}
}
class ConstructorNull {
constructor() { this.value = null; }
set(value) { this.value = value; }
read() { return this.value; }
}
const mutableNull = new MutableNull();
console.log('null initial', mutableNull.read(), mutableNull.current);
mutableNull.set('updated');
console.log('null written', mutableNull.read(), mutableNull.current);
mutableNull.current = 42;
console.log('accessor', mutableNull.read());
console.log('numeric field', Number(mutableNull.value));
const resetNull = new ResetNull('derived');
console.log('inherited', resetNull.read());
const readMutableNull = () => mutableNull.value;
function readNullField() { return mutableNull.value; }
const savedNullReader = readNullField;
console.log('readers', readMutableNull(), savedNullReader());
const constructorNull = new ConstructorNull();
constructorNull.set('constructor');
console.log('constructor field', constructorNull.read());
mutableNull.set(null);
console.log('null reset', readMutableNull(), savedNullReader());