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

Aperta
#63,960 1 commento 0 reazioni 0 assegnatari Vedi su GitHub
Needs More Info
Lingua principale
Go
Stelle
111k
Fork
14.3k
Merge medio
2g 4h
PR unite (30g)
132

Descrizione

## 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.

Guida per i contributori

Apri la guida per i contributori

Direzione di ricerca

Inizia confrontando le due configurazioni di include e l’output di `--explainFiles`, quindi ottieni lo snapshot completo del programma che fallisce menzionato nell’issue. Concentrati sulle dichiarazioni in `router.d.ts`, `index.d.ts`, `server-entry.d.ts` e `request-handler.d.ts`, oltre che su `src/server.ts`; il lavoro sarà completato quando gli stessi file radice risolveranno in modo coerente il ramo augmentato con TypeScript 7.0.2, con un test di regressione che copra il caso di glob di directory.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Valutazione

Stack tecnologico
typescript
Ambito
compilers
Tipo di issue
Bug
Difficoltà
5/5
Tempo stimato
Più di una settimana
Stato di attività
Attiva
Chiarezza
Abbastanza chiara
Idoneità per principianti
25/100

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.