mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-11-05 08:13:06 +08:00
Map a failing type exactly from its canonical form where localized failure would degrade it
Stage N2 of the localized-failure design (canonical type normalization). At localized failure's commit points, before a failing type degrades to its pushed-down or whole-slot dynamic form, its canonical form maps through mapCanonical under the current bindings. When that gives a static IR type, the type maps exactly: no dynamic slot, no SC6104. Opaque and unbound forms, and forms that map only to the checked dynamic, degrade as before. The coverage remainder re-derives canonical forms in its own tables. Because it runs only at commit points, a program that compiles today never consults it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
177b4cd326
commit
c81758a08d
@@ -7,7 +7,8 @@ import {
|
||||
arrayRootOf,
|
||||
lowerWithArraySpecies,
|
||||
} from "./builtins/array-classes.js";
|
||||
import { canonShadowHook } from "../canon-map.js";
|
||||
import { canonShadowHook, mapCanonical } from "../canon-map.js";
|
||||
import { Normalizer } from "../canon.js";
|
||||
import {
|
||||
compactDeclOrigins,
|
||||
inPackageFile,
|
||||
@@ -750,6 +751,9 @@ export interface DynamicSlot {
|
||||
/** The slot keeps its type's compilable shape; only the failing
|
||||
* components are checked-dynamic (push-down, stage L3). */
|
||||
partial?: true;
|
||||
/** The type mapped exactly from its canonical form (stage N2): a static
|
||||
* slot, never reported as dynamic. */
|
||||
canonical?: true;
|
||||
}
|
||||
|
||||
/** Localized failure: rounds of the declaration flush retry. */
|
||||
@@ -1007,6 +1011,9 @@ export interface DegradedType {
|
||||
lenient: IrType | null;
|
||||
code: string;
|
||||
reason: string;
|
||||
/** The type maps exactly from its canonical form (stage N2): its slots
|
||||
* are static. */
|
||||
canonical?: true;
|
||||
}
|
||||
|
||||
/** The Lowerer's pass configuration (see lowerToIr). */
|
||||
@@ -4178,9 +4185,12 @@ export class Lowerer {
|
||||
// checked dynamic cuts the knot while it does.
|
||||
if (degraded.lenient === null) {
|
||||
degraded.lenient = DYN;
|
||||
degraded.lenient = this.lenientType(type);
|
||||
degraded.lenient =
|
||||
(degraded.canonical === true ? this.canonicalType(type) : null) ??
|
||||
this.lenientType(type);
|
||||
}
|
||||
if (degraded.lenient.kind !== "dyn") this.partialHits[this.partialHits.length - 1]?.add(id);
|
||||
if (degraded.lenient.kind !== "dyn" && degraded.canonical !== true)
|
||||
this.partialHits[this.partialHits.length - 1]?.add(id);
|
||||
return degraded.lenient;
|
||||
},
|
||||
...(shadow !== undefined ? { canonShadow: shadow } : {}),
|
||||
@@ -9833,8 +9843,10 @@ export class Lowerer {
|
||||
}
|
||||
if (committed === undefined) return null;
|
||||
this.degradeType(type, committed.code, committed.message);
|
||||
const canonical = this.isCanonicalDegraded(id);
|
||||
const partial = this.degradedTypes.get(id)?.lenient?.kind !== "dyn";
|
||||
return {
|
||||
...(canonical ? { canonical: true as const } : {}),
|
||||
...(partial ? { partial: true as const } : {}),
|
||||
loc: committed.loc,
|
||||
failure,
|
||||
@@ -9966,6 +9978,14 @@ export class Lowerer {
|
||||
this.degradedTypes.set(id, { lenient: DYN, code, reason });
|
||||
return true;
|
||||
}
|
||||
// Canonical normalization (stage N2): a type the per-form rules miss
|
||||
// maps exactly from its canonical form when that form maps; no dynamic
|
||||
// slot then. Otherwise the lenient (pushed-down) form.
|
||||
const canonical = this.canonicalType(type);
|
||||
if (canonical !== null) {
|
||||
this.degradedTypes.set(id, { lenient: canonical, code, reason, canonical: true });
|
||||
return true;
|
||||
}
|
||||
this.degradedTypes.set(id, {
|
||||
lenient: this.wholeSlotDegradation ? DYN : this.lenientType(type),
|
||||
code,
|
||||
@@ -9974,10 +9994,38 @@ export class Lowerer {
|
||||
return true;
|
||||
}
|
||||
|
||||
/** The IR type of a type's canonical form (canon-map.ts), or null when
|
||||
* the form is irreducible, unbound, or only the checked dynamic. Runs
|
||||
* only at localized failure's commit points, so a program that compiles
|
||||
* today never consults it. */
|
||||
canonicalType(type: ts.Type): IrType | null {
|
||||
try {
|
||||
const resolve = this.typeCtx.resolveTypeParamTs;
|
||||
const c = new Normalizer(this.checker).canon(type, resolve ? (t) => resolve(t) : new Map());
|
||||
if (c.k === "opaque" || c.k === "param") return null;
|
||||
const { typeMemo: _strictMemo, ...strict } = this.typeCtx;
|
||||
const mapped = mapCanonical(c, { ...strict, canMemoizeType: () => false });
|
||||
return mapped === null || mapped.kind === "void" || mapped.kind === "dyn" ? null : mapped;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/** The degraded type of `id` maps exactly (canonical form): its slots
|
||||
* are static, not dynamic. */
|
||||
isCanonicalDegraded(id: number): boolean {
|
||||
return this.degradedTypes.get(id)?.canonical === true;
|
||||
}
|
||||
|
||||
/** Some degraded type kept a pushed-down (partial) shape. */
|
||||
hasPartialDegradedTypes(): boolean {
|
||||
for (const degraded of this.degradedTypes.values())
|
||||
if (degraded.lenient !== null && degraded.lenient.kind !== "dyn") return true;
|
||||
if (
|
||||
degraded.canonical !== true &&
|
||||
degraded.lenient !== null &&
|
||||
degraded.lenient.kind !== "dyn"
|
||||
)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -10011,6 +10059,7 @@ export class Lowerer {
|
||||
|
||||
/** Records a dynamic slot whose unit lowered with it (one per site). */
|
||||
noteDynamicSlot(slot: DynamicSlot): void {
|
||||
if (slot.canonical === true) return;
|
||||
const seen = this.dynamicSlots.some(
|
||||
(s) =>
|
||||
s.loc.file === slot.loc.file &&
|
||||
@@ -13166,18 +13215,17 @@ export class Lowerer {
|
||||
const prior = this.degradedTypes.get(id);
|
||||
degraded.push({
|
||||
id,
|
||||
prior:
|
||||
prior === undefined
|
||||
? undefined
|
||||
: { lenient: prior.lenient, code: prior.code, reason: prior.reason },
|
||||
prior: prior === undefined ? undefined : { ...prior },
|
||||
});
|
||||
this.degradeType(type, diag.code, diag.message);
|
||||
const canonical = this.isCanonicalDegraded(id);
|
||||
const partial = this.degradedTypes.get(id)?.lenient?.kind !== "dyn";
|
||||
slots.push({
|
||||
loc: diag.loc,
|
||||
failure,
|
||||
code: diag.code,
|
||||
reason: diag.message,
|
||||
...(canonical ? { canonical: true as const } : {}),
|
||||
...(partial ? { partial: true as const } : {}),
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user