microsoft / microsoft/TypeScript
Design Meeting Notes, 2026-09-01
Nobody has claimed this yet.
- Dominant language
- Go
- Stars
- 111k
- Forks
- 14.3k
- Avg merge
- 2d 4h
- Merged PRs (30d)
- 132
Description
# Conditional Distributions Allow Constraint Violation
https://github.com/microsoft/TypeScript/issues/63708
```ts
type Issue =
A extends unknown ?
B extends unknown ?
Show :
never :
never;
type Show = [A, B] & {};
type X = Issue<0 | 1, 0 | 1>;
// ^?
```
* The problem is that when we distribute on `A` and `B`, 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.
```ts
// 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 number` become `T' extends T & number`?
* How does this work over type references that alias bare identifiers
```ts
type Foo = T;
type Blah = Foo extends any ? { b: Foo } : 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 & number` isn't reasoned about quite the same as `T'' & (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
```ts
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 `typescript` package.
* `ast`
* `factory`
* `is`
* ...
* 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!
```ts
// ./src/typescript.ts
// Single barrel export for convenience
export * from "typescript";
export * from "typescript/ast";
export * from "typescript/async";
```
```json5
// package.json
{
// ...
"imports": {
"#ts": "./dist/typescript.js"
}
}
```
```ts
// Other usage
import * as ts from "#ts";
```
* What about identical types imported through different paths?
* e.g. `ModuleKind` comes from both `"typescript"` and `"typescript/async"`.
* What do people want from this?
* Do the names overlap between sync/async modules?
* Yes, they have identical names.
* It'd be annoying if they all had an `Async` postifx.
* Could we just simplify this all into `typescript/async` and `typescript/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 `forEachChild` short-circuits on truthy results. `Promise`s are always truthy, so a walk always exits early.
* If we had the FIFO prototype resurrected (come back to this @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.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with the linked TypeScript issue 63708 and the conditional-distribution examples, then review the notes on binder changes, substitution types, and compatibility. Separately, examine the proposed sync/async and AST entry points and the top-999 prototype plan. There is no defined implementation scope or completion criterion yet; prototypes and external feedback are the stated next steps.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- api, compilers
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Needs clarification
- Newbie friendliness
- 25/100