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

Ouverte
#63,960 1 commentaire 0 réactions 0 personnes assignées Voir sur GitHub

Personne n'a encore pris cette issue.

Needs More Info
Langage dominant
Go
Étoiles
111k
Forks
14.3k
Merge moyen
2 j 4 h
PR mergées (30 j)
132

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:
    // @tanstack/router-core/dist/esm/router.d.ts
    export interface Register { /* ... */ }
    
  • @tanstack/react-router only re-exports it:
    // @tanstack/react-router/dist/esm/index.d.ts
    export type { Register /* ... */ } from '@tanstack/router-core';
    
  • App code augments the re-exporting module name:
    // 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:
    // @tanstack/react-start .../server-entry.d.ts
    import { Register } from '@tanstack/react-router';
    export type ServerEntry = { fetch: RequestHandler<Register> };
    // start-server-core request-handler.d.ts
    export type RequestOptions<TRegister> = 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<Register> 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:

const fetchHandler = handler.fetch as unknown as (
  request: Request,
  opts: { context: QuesonaWebRequestContext }
) => Promise<Response>

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.

Guide de contribution

Ouvrir le guide de contribution

Par où commencer

  1. Lisez l'issue en entier, puis le guide de contribution du projet.
  2. Signalez en commentaire que vous la prenez — cela évite que deux personnes fassent le même travail.
  3. Forkez le dépôt et travaillez sur une branche.
  4. Ouvrez une pull request qui référence le numéro de l'issue.

Piste de recherche

Commencez par comparer les deux configurations d’include et la sortie de --explainFiles, puis obtenez le snapshot complet du programme en échec mentionné dans l’issue. Concentrez-vous sur les déclarations dans router.d.ts, index.d.ts, server-entry.d.ts et request-handler.d.ts, ainsi que sur src/server.ts ; le travail sera terminé lorsque les mêmes fichiers racine résoudront systématiquement la branche augmentée avec TypeScript 7.0.2, avec un test de régression couvrant le cas de glob de répertoire.

Rédigé par le modèle d'indexation à partir du texte de l'issue.

Évaluation

Stack technique
typescript
Domaine
compilers
Type d'issue
Bug
Difficulté
5/5
Temps estimé
Plus d'une semaine
Activité
Active
Clarté
Plutôt claire
Accessibilité débutants
25/100

Recevez les nouvelles issues par e-mail

Un résumé court des issues GitHub adaptées aux débutants.