Repository navigation
Design Meeting Notes, 2026-09-01 #64129
Description
Conditional Distributions Allow Constraint Violation
type Issue<A, B extends A> =
A extends unknown ?
B extends unknown ?
Show<A, B> :
never :
never;
type Show<A, B> = [A, B] & {};
type X = Issue<0 | 1, 0 | 1>;
// ^?-
The problem is that when we distribute on
AandB, then we should create a new type parameter for each one with an identical name. -
So we are prototyping this.
-
This is a little complicated by the fact that this now also becomes a declaration site rather than just a usage.
- We would need to introduce something in the binder for this.
- So that would say "this is a distribution site with a new type variable".
-
But that would have broader implications beyond just type variables on the left side of
extends. -
Doing something like this would changing the semantics of type distribution because we'd now operate over any identifier-named type.
// TODO -
When explaining to others, we've explained this as "there's a new type variable that is created for distribution", so surprised it didn't work this way.
- Really there's a mix of substitution types and other instantiation mechanics at play here.
-
Do we need substitution types?
- Yes. You need "learned information"/"conditional narrowing" to be tracked along to satisfy other constraints in true branches.
- Wait really? Why can't
T extends numberbecomeT' extends T & number?
-
How does this work over type references that alias bare identifiers
type Foo<T> = T; type Blah<T> = Foo<T> extends any ? { b: Foo<T> } : undefined; type What = Blah<1 | 0>;
- Today, this does distribute; it no longer would with the suggested change...
- Oh... that's probably very breaky!
- Who does this?
- Someone, guaranteed.
-
Feels like people should at least have a way to distribute explicitly since we might be changing things here?
-
You are binding a new type parameter at the top; there's no reason you couldn't other than efficiency.
- No - we use substitution types to track learned information and conditional narrowing.
- There are differences in type comparisons when you have substitution types, and they differ in how they act on type comparisons.
- So while they feel similar, type parameters with added constraints are treated differently in practice.
T'' extends T' & (1 | 2) extends T & numberisn't reasoned about quite the same asT'' & (1 | 2) & number.
-
Why don't we just introduce syntax to avoid breaking people?
- The problem is that we are trying to fix a soundness hole and we want that to be fixed by default.
-
Prototype will tell us what breaks in top 999.
API Bikeshed
import { version, versionMajorMinor } from "typescript";
import * as ts from "typescript/async"; // or sync
import * as ast from "typescript/ast";- Currently there's a bunch of subpath exports in the
typescriptpackage.astfactoryis- ...
- As we prototyped replacements with the new API, we noticed old API usage was annoying to go through different imports.
- Feedback from early API users is that it's less overwhelming than having one import with everything.
- One of the things we don't like with the "siloing" is that it kind of draws boundaries that we might be fixing ourselves into.
-
Can't really have a single barrel given the sync/async split.
-
If you want a single barrel, you can do it yourself!
// ./src/typescript.ts // Single barrel export for convenience export * from "typescript"; export * from "typescript/ast"; export * from "typescript/async";
// package.json { // ... "imports": { "#ts": "./dist/typescript.js" } }
// Other usage import * as ts from "#ts";
-
- What about identical types imported through different paths?
- e.g.
ModuleKindcomes from both"typescript"and"typescript/async". - What do people want from this?
- e.g.
- Do the names overlap between sync/async modules?
- Yes, they have identical names.
- It'd be annoying if they all had an
Asyncpostifx.
- Could we just simplify this all into
typescript/asyncandtypescript/sync, and re-export common stuff from both?- What do you do if you have a function that acts only on the common stuff?
- Any AST walker.
- Aside: do we have
forEachChildAsync?- Currently a problem because
forEachChildshort-circuits on truthy results.Promises are always truthy, so a walk always exits early.
- Currently a problem because
- If we had the FIFO prototype resurrected (come back to this Jake Bailey (@jakebailey)?), would people even want async because the context switching is so high?
- Async is just painful
- But it's the only way to easily do things concurrently.
- Wonder how easy it is to use workers and just use the sync layer.
- Out of time - really need to build some prototypes and get feedback from others.
Activity