microsoft / microsoft/TypeScript
TS 7.0.2 (tsgo): augmentation of a type-only re-exported interface resolves order-dependently — same file set passes via explicit include list, fails via directory glob
- Dominant language
- Go
- Stars
- 111k
- Forks
- 14.3k
- PR merge metrics
- PR metrics pending
Description
## Versions
- Fails: `typescript@7.0.2` (tsgo)
- Works: `typescript@6.0.3` on the identical program and dependency tree
- Module resolution: `bundler`; `strict`, `verbatimModuleSyntax` on
## Context
Real-world case from a TanStack Start 1.168 app:
- `@tanstack/router-core` **declares** the interface:
```ts
// @tanstack/router-core/dist/esm/router.d.ts
export interface Register { /* ... */ }
```
- `@tanstack/react-router` only **re-exports** it:
```ts
// @tanstack/react-router/dist/esm/index.d.ts
export type { Register /* ... */ } from '@tanstack/router-core';
```
- App code augments the re-exporting module name:
```ts
// src/server.ts
declare module "@tanstack/react-router" {
interface Register {
server: { requestContext: { env: Env; ctx: Ctx } }
}
}
```
- A third package's declaration imports the re-export and feeds it through a conditional type:
```ts
// @tanstack/react-start .../server-entry.d.ts
import { Register } from '@tanstack/react-router';
export type ServerEntry = { fetch: RequestHandler };
// start-server-core request-handler.d.ts
export type RequestOptions = EarlyHintsOptions & InlineCssOptions &
(TRegister extends { server: { requestContext: infer TRequestContext } }
? { context: TRequestContext & BaseContext } // augmented branch
: { context?: BaseContext }); // unaugmented fallback
```
## Actual behavior (7.0.2)
Whether the conditional picks the augmented or unaugmented branch depends on how the **same root files** enter the program:
| Program configuration | Result under 7.0.2 | Under 6.0.3 |
| --- | --- | --- |
| `"include": ["src"]` (+ exclude patterns), ~26 .ts/.tsx roots | ❌ unaugmented branch — excess-property error at the handler call site | ✅ augmented |
| byte-identical root set enumerated as an explicit `include` array of file paths | ✅ augmented | ✅ augmented |
| single-file programs / small synthetic programs using the same packages | ✅ augmented | ✅ augmented |
Each row is deterministic across repeated runs. Probing `RequestOptions` with a discriminating assignment confirms TS 6 reduces it to `{ context: TRequestContext & BaseContext }` while TS 7 reduces it to `{ context?: BaseContext }` in the failing configuration.
Notably, a second augmentation of the *declaring* module (`declare module "@tanstack/router-core"`) does land somewhere — conflicting duplicate declarations across files produce TS2717 "Subsequent property declarations must have the same type", naming the first declaration's shape — yet the conditional still resolves to the unaugmented branch, suggesting two distinct `Register` symbols end up existing per program state.
## Minimization attempts
A synthetic two-package repro (local `@fake/core` declaring the interface + `@fake/reexport` doing `export type { Register }`, one augmenting file + one consumer probing the conditional) compiles fine under 7.0.2 even with 150 additional modules mixed into the glob. The flip appears to require the scale/structure of the real program (route-tree codegen that also augments `Register.router`, dozens of cross-importing route modules, etc.).
## Workaround we shipped
Typing the single affected call site explicitly against our own shape and keeping the augmentation for documentation value:
```ts
const fetchHandler = handler.fetch as unknown as (
request: Request,
opts: { context: QuesonaWebRequestContext }
) => Promise
```
Happy to provide the full failing program snapshot or run diagnostics (`--explainFiles` output comparing both configurations shows an identical resolved file set for the router packages) if useful.
Contributor guide
Research direction
Start by comparing the two include configurations and the `--explainFiles` output, then obtain the full failing program snapshot mentioned in the issue. Focus on the declarations in `router.d.ts`, `index.d.ts`, `server-entry.d.ts`, and `request-handler.d.ts`, plus `src/server.ts`; done means the same root files resolve the augmented branch consistently under TypeScript 7.0.2, with a regression test covering the directory-glob case.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- compilers
- Issue type
- Bug
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 25/100