Files
scriptc/tests/corpus/3063-runtime-optional-class-guards.ts
Chris TateandGianluca Esposito 12f7580203 fix(compiler): preserve optional receiver storage when narrowing (#479)
- Compile record predicates and class guards over array-derived values.
- Preserve missing-value checks and report unsupported union refinements.
- Cover narrowing in differential tests, coverage analysis, and frontend baselines.

Fixes #477

Co-authored-by: Gianluca Esposito <735227+gesposito@users.noreply.github.com>
2026-09-26 19:26:39 -05:00

82 lines
2.8 KiB
TypeScript

class Animal {
name: string;
constructor(name: string) { this.name = name; }
}
class Dog extends Animal {
volume: number = 2;
bark(): string { return "woof:" + this.name; }
get sound(): string { return this.bark(); }
}
function isDog(animal: Animal): animal is Dog {
return animal instanceof Dog;
}
function parameter(animal: Animal): string {
return animal instanceof Dog ? animal.bark() : "-";
}
function optionalParameter(animal: Animal | undefined): string {
return animal instanceof Dog ? animal.bark() : "-";
}
function loop(animals: Animal[]): void {
for (const animal of animals) {
if (!(animal instanceof Dog)) continue;
console.log("instanceof", animal.bark());
}
for (const animal of animals) {
if (!isDog(animal)) continue;
console.log("predicate", animal.bark());
}
}
function local(animals: Animal[], index: number): string {
const animal = animals[index];
if (animal === undefined || !isDog(animal)) return "-";
return animal.bark() + ":" + animal.volume + ":" + animal.sound;
}
function truthy(animals: Animal[], index: number): string {
const animal: Animal | undefined = animals[index];
return animal && isDog(animal) ? animal.bark() : "-";
}
function captured(animals: Animal[]): () => string {
const animal = animals[0];
if (!(animal instanceof Dog)) return () => "-";
return () => animal.bark();
}
const animals: Animal[] = [new Animal("plain"), new Dog("first"), new Dog("second")];
loop(animals);
console.log("map", animals.map((animal) => animal instanceof Dog ? animal.bark() : "-").join(","));
console.log("from", Array.from(animals, (animal) => isDog(animal) ? animal.bark() : "-").join(","));
animals.forEach((animal) => { if (animal instanceof Dog) console.log("forEach", animal.bark()); });
for (let index = 0; index < 4; index++) console.log("local", index, local(animals, index), truthy(animals, index));
console.log("parameter", parameter(new Animal("plain")), parameter(new Dog("param")));
console.log("optional parameter", optionalParameter(new Dog("optional")), optionalParameter(undefined));
console.log("capture", captured([new Dog("captured")])(), captured([])());
// Module storage also carries the array element's hidden undefined arm.
const globalAnimal = animals[1];
if (isDog(globalAnimal)) console.log("global", globalAnimal.bark());
let effects = 0;
class Speaker extends Animal {
speak(message: string): string { return this.name + ":" + message; }
}
function claimsSpeaker(animal: Animal): animal is Speaker { return true; }
function missing(animals: Animal[]): void {
const animal = animals[8];
if (claimsSpeaker(animal)) console.log(animal.speak((effects++, "unreachable")));
}
try {
missing(animals);
} catch (error) {
console.log("missing", error instanceof TypeError, (error as Error).message, effects);
}