Skip to content

fix: print the trait and variant builders by name in a consumer's declarations - #116

Merged
jo16oh merged 8 commits into
mainfrom
fix/trait-builder-elided-any
Nov 3, 2026
Merged

jo16oh merged 8 commits into
mainfrom
fix/trait-builder-elided-any

Conversation

@jo16oh

@jo16oh jo16oh commented Nov 3, 2026

Copy link
Copy Markdown
Owner

Currently, exporting a builder without .build() breaks the declaration emit. The CallBuildFirst error that points to the missing .build() appears only where the builder is used:

export const Greetable = Trait.companion<Greetable>().impl(() => ({ … })); // `.build()` forgotten
// 5.9–7.0: emits `/*elided*/ any` without an error
// 7.1: error TS7056 (microsoft/TypeScript#64461)

export const Shape = Enum.companion<Shape>().Circle.impl(() => ({ … })); // `.build()` forgotten
// 5.9–7.1: error TS7056

The compiler cannot print these builder types by name, so it writes the builder out again inside each step. A built companion is not affected.

  • Split TraitBuilder into two interfaces, TraitSteps and TraitImplStep, so the declaration prints it by name after impl
  • Return EnumBuilder<…> from enum variant steps instead of the non-exported After, and export VariantSteps from valof/internal/enum
  • Export every state of every builder in ts-compatibility, and fail a TypeScript version whose declarations contain /*elided*/
  • Type cost barely changes: trait +5, enum −12 instantiations, index.d.ts +0.19 kB

@jo16oh
jo16oh merged commit 1c96f07 into main Nov 3, 2026
2 checks passed
@jo16oh
jo16oh deleted the fix/trait-builder-elided-any branch November 3, 2026 08:25
jo16oh added a commit that referenced this pull request Nov 3, 2026
…claration (#118)

Currently, `vp run ts-compatibility` searches the emitted `.d.ts` for
`/*elided*/`. That text search catches one way a declaration loses a
type. Compare the types themselves instead.

- For each export of the book's code samples and the fixtures, assert
that the source type and the type read back from its `.d.ts` are
identical:
  ```ts
  import type * as S0 from "../fixture-builders.js";
  import type * as D0 from "../dts/fixture-builders.js";
export type _0 = Expect<Equal<typeof S0.traitImpl, typeof
D0.traitImpl>>;
  ```
- `Equal` distinguishes `any` from every other type. Against the dist
before #116, `greetableBuilder`, whose declaration contains `/*elided*/
any`, fails on TypeScript 5.9, 6.0 and 7.0
- List the exports with the type checker from `typescript-5`. Compare a
generic type as a function of its parameters: `<T0>() => S0.Result<T0>`
- The repo's `scripts/` keep the `/*elided*/` search. Their classes
extend `ErrorFactory(…)`, and their declarations print the aliases of
`@praha/error-factory` expanded, which the comparison counts as a change
- Export `ByTag`, `Frame` and `VariantSupplied` from
`valof/internal/enum`. Currently, a consumer's declarations print them
expanded, so `Shape[Tag]`, `Shape.Circle`, and `Shape.Circle` after its
last step fail the comparison. The fixtures' declarations shrink from
36.8 kB to 33.8 kB. `index.d.ts` grows 0.02 kB

- Run the TypeScript lines concurrently, each emitting to `dts/<alias>`
and `round-trip/<alias>`. The reports print in the order of `aliases`

`vp run ts-compatibility` takes 5.2 s instead of 4.4 s locally.
Comparing the types costs 2.4 s per TypeScript line on 5.9 and 1.1 s on
7.0, mostly for the enum builders. With the lines run one after another,
it takes 9.1 s.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant