Attribute slot causes to the owner only for types without their own slot

This commit is contained in:
Chris Tate
2026-10-26 07:59:53 -05:00
parent 572e7e3eac
commit acb2d23df5
3 changed files with 36 additions and 2 deletions
@@ -223,8 +223,10 @@ function slotCause(lowerer: Lowerer, n: ts.Node): UntypedCause | null {
? lowerer.checker.getTypeOfSymbol(symbol)
: lowerer.checker.getTypeAtLocation(n);
const id = (type as { id?: number }).id ?? -1;
// A whole degraded type, or a dynamic piece of a pushed-down one.
const owner = lowerer.lenientPieces.get(id);
// A whole degraded type, or a dynamic piece of a pushed-down one. A type
// that is also a slot of its own belongs to that slot, not to a container
// it is a piece of.
const owner = lowerer.degradedTypes.has(id) ? undefined : lowerer.lenientPieces.get(id);
if (!lowerer.degradedTypes.has(id) && owner === undefined) return null;
const text = ts.getTextOfNode(n);
const declaredType = lowerer.checker.typeToString(type);
@@ -0,0 +1,23 @@
// A type that degrades as a piece of a container (`Plugin.hook`) and as a
// slot of its own (`current`): the operations on `current` belong to
// `current`, not to the container.
type Hook = (() => number) | AbortSignal | Map<string, (x: number) => string>;
interface Plugin {
name: string;
hook: Hook;
}
const plugins: Plugin[] = [{ name: "one", hook: () => 1 }];
let current: Hook = () => 2;
function run(rounds: number): number {
let total = 0;
for (let i = 0; i < rounds; i++) {
if (typeof current === "function") total += current();
}
return total;
}
console.log(run(10), plugins.length);
+9
View File
@@ -496,6 +496,15 @@ test("dynamic slots rank by their dynamic operations and name themselves in SC61
expect(hot?.message).toContain("`hook` is a dynamic piece of `Plugin[]` (declared `Hook`");
});
test("a type that is a dynamic piece and a slot of its own counts toward its own slot", () => {
const { coverage } = analyze(fixture("dynamic-slot-shared.ts"));
const slots = coverage.dynamicSlots ?? [];
expect(slots.find((s) => s.name === "current")?.operations).toBeGreaterThan(0);
expect(slots.find((s) => s.name === "plugins")?.operations ?? 0).toBe(0);
const hot = coverage.performance?.find((d) => d.code === "SC6101");
expect(hot?.message).toContain("`current` is a dynamic slot (declared `Hook`");
});
test("CLI: --tier and --explain-dynamic", () => {
const scriptcCli = join(repoRoot, "packages/cli/src/main.ts");
const outDir = mkdtempSync(join(tmpdir(), "scriptc-tier-"));