Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[regression - 5.4] Generated .d.ts file contains invalid types #57843

Closed
bradzacher opened this issue Mar 19, 2024 · 6 comments Β· Fixed by #57849 Β· May be fixed by #57396
Closed

[regression - 5.4] Generated .d.ts file contains invalid types #57843

bradzacher opened this issue Mar 19, 2024 · 6 comments Β· Fixed by #57849 Β· May be fixed by #57396
Labels
Fix Available A PR has been opened for this issue

Comments

@bradzacher
Copy link
Contributor

bradzacher commented Mar 19, 2024

πŸ”Ž Search Terms

error "generic type" "requires" "type argument(s)" declaration .d.ts emit

πŸ•— Version & Regression Information

  • This changed between versions 5.3 and 5.4

⏯ Playground Link

bug workbench link

πŸ’» Code

// @showEmit
// @declaration: true
// @showEmittedFile: repro.d.ts

// @filename: base.ts
export abstract class One<
  D = unknown,
  S = unknown,
  M = unknown,
  DP = unknown,
  SP = unknown,
> {}
export abstract class Two<
  D = any,
  S = any,
  M = any,
  DP = any,
  SP = any,
> extends One<D, S, M, DP, SP> {
  readonly a!: D;
  readonly b!: S;
  readonly c!: M;
  abstract readonly d: Six<D, S>;
}

export class Three<D> {}
export class Four<D> {}
export type Five<D, S> = Three<D> | Four<S> | undefined;
export abstract class Six<D = unknown, S = unknown> {
  readonly e!: D;
  readonly f!: S;
}

export type Seven<S> = S;
export class Eight<D, S> extends Six<Five<D, S>, Seven<S>> {}
export type Nine<C extends Six> = Eight<Ten<C>, Eleven<C>>;
export type Ten<C extends Six> = C["e"];
export type Eleven<C extends Six> = C["f"];
export type Twelve<T> = Nine<Fourteen<T>>;
export type Thirteen<T> = Ten<Twelve<T>>;
export class Fourteen<T> extends Six<void, T> {}
export interface Fifteen<T> {
  g: T;
}
export class Sixteen<T = any> extends Two<
  Thirteen<T>,
  T,
  Fifteen<T>,
  never,
  never
> {
  override readonly d!: Eight<void, T>;
}

export type Seventeen<T extends TwentyOne> = T["h"];
export type Eighteen = { [k: string]: TwentyOne };
export type Nineteen = { [k: string]: Two };
export type Twenty = Record<string, Sixteen>;

export class TwentyOne<T = any> {
  readonly h!: T;
}
export class TwentyTwo<T extends TwentyOne> extends TwentyOne<
  Seventeen<T> | undefined
> {}
export class TwentyThree<D, S> extends Six<D | undefined, S | undefined> {}
export class TwentyFour<D, S, M, DP, SP> extends Two<
  D | undefined,
  S | undefined,
  M | undefined,
  DP | undefined,
  SP | undefined
> {
  override readonly d!: TwentyThree<D, S>;
}

export type TwentyFive<T extends Two> = T["d"];
type TwentySix<V extends Eighteen, R extends Nineteen, A extends Twenty> = {
  [K in keyof V]: Fourteen<Seventeen<V[K]>>;
} & { [K in keyof R]: TwentyFive<R[K]> } & {
  [K in keyof A]: TwentyFive<A[K]>;
};
export type TwentySeven<
  V extends Eighteen,
  R extends Nineteen,
  A extends Twenty,
> = ThirtyThree<TwentySix<V, R, A>>;
export type TwentyEight<
  V extends Eighteen,
  R extends Nineteen,
  A extends Twenty,
> = Eleven<TwentySeven<V, R, A>>;
export type TwentyNine<
  V extends Eighteen,
  R extends Nineteen,
  A extends Twenty,
> = Ten<TwentySeven<V, R, A>>;
type Thirty<C extends Eighteen> = {
  [k in keyof C]: Fourteen<Seventeen<C[k]>>;
};
type ThirtyOne<C extends Twenty> = {
  [k in keyof C]: TwentyFive<C[k]>;
};
export type ThirtyTwo<C extends Nineteen> = {
  [k in keyof C]: TwentyFive<C[k]>;
};
export class ThirtyThree<Spec extends ThirtyFour> extends Six<
  ThirtySix<Spec>,
  ThirtyFive<Spec>
> {}
export type ThirtyFour = { readonly [k: string]: Six };
export type ThirtyFive<Domains extends ThirtyFour> = {
  readonly [K in keyof Domains]: Eleven<Domains[K]>;
};
export type ThirtySix<Domains extends ThirtyFour> = {
  readonly [K in keyof Domains]?: Ten<Domains[K]>;
};
export type ThirtySeven<T extends Two> = T["c"];
export type ThirtyEight<T extends Two> = T["b"];
export type ThirtyNine<
  V extends Eighteen,
  R extends Nineteen,
  A extends Twenty,
> = { readonly [K in keyof V]: Seventeen<V[K]> } & {
  readonly [K in keyof R]: ThirtySeven<R[K]>;
} & { [K in keyof A]: ThirtyEight<A[K]> };
export class Forty<
  V extends Eighteen,
  R extends Nineteen,
  A extends Twenty,
> extends Two<
  TwentyNine<V, R, A>,
  TwentyEight<V, R, A>,
  ThirtyNine<V, R, A>,
  never,
  never
> {
  override readonly d!: ThirtyThree<Thirty<V> & ThirtyTwo<R> & ThirtyOne<A>>;
}

export declare function doThing<D, S, M, DP, SP>(
  arg: Two<D, S, M, DP, SP>,
): TwentyFour<D, S, M, DP, SP>;


// @filename: repro.ts
import { doThing, type Forty } from "./base";
export const bar = doThing({} as Forty<{}, {}, {}>);

// @filename: repro_output.d.ts
export declare const bar: import("./base").TwentyFour<
  import("./base").TwentyNine<{} & import("./base").ThirtyTwo<{}> & {}>,
  import("./base").TwentyEight<{} & import("./base").ThirtyTwo<{}> & {}>,
  import("./base").ThirtyNine<{}, {}, {}>,
  unknown,
  unknown
>;

πŸ™ Actual behavior

The code emitted for repro.d.ts is

export declare const bar: import("./base").TwentyFour<
  import("./base").TwentyNine<
    import("./base").Thirty<{}> &
      import("./base").ThirtyTwo<{}> &
      import("./base").ThirtyOne<{}>
  >,
  import("./base").TwentyEight<
    import("./base").Thirty<{}> &
      import("./base").ThirtyTwo<{}> &
      import("./base").ThirtyOne<{}>
  >,
  import("./base").ThirtyNine<{}, {}, {}>,
  unknown,
  unknown
>;

This code has two type errors (on lines 2-6 and 7-11 respectively):

Generic type 'TwentyNine' requires 3 type argument(s).
Generic type 'TwentyEight' requires 3 type argument(s).

Note how the generated type uses TwentyNine on L2 and TwentyEight on L7.
These are the incorrect types to use (see below)

πŸ™‚ Expected behavior

The types generate valid code with no semantic errors.
For example intellisense reports this type for bar which is valid

export declare const bar: import("./base").TwentyFour<
  import("./base").ThirtySix<
    import("./base").Thirty<{}> &
      import("./base").ThirtyTwo<{}> &
      import("./base").ThirtyOne<{}>
  >,
  import("./base").ThirtyFive<
    import("./base").Thirty<{}> &
      import("./base").ThirtyTwo<{}> &
      import("./base").ThirtyOne<{}>
  >,
  import("./base").ThirtyNine<{}, {}, {}>,
  unknown,
  unknown
>;

Note how this type instead uses ThirtySix on L2 and ThirtyFive on L7.

Additional information about the issue

Sorry that this example is so goddamn disgusting to look at.
This is the name-mangled version of some real code from our codebase.
I tried to minimise it but it's all such an intermingled spaghetti mess that I wasn't able to figure out what things I could delete without changing the output.

For context - I am working on upgrading our codebase to TS5.4.
When I ran our typecheck CLI I got a number of errors across the codebase.
When I opened the files with errors - those errors didn't show up in the IDE.
I spent a while pulling my hair out trying to figure out why there was a discrepancy.

The error messages led me back to a monster file which makes use of some really ugly patterns of inferred types from typeofs to generate a lot of types. For reference the file itself is >3k LOC and the .d.ts file it generates is >11k LOC. So so so many anonymous, inferred types.

When I opened the .d.ts file it was filled with type errors. From what I can tell what's happening is that when you open the files with errors in the IDE then TS uses the .ts source files for type checking and so it uses the "correct" type for everything. When we run our CLI to do the typecheck it uses project references and so it uses the .d.ts output instead.

Because the .d.ts output doesn't match the types TS infers from the .ts file this causes the discrepancy between the reported errors.

I think a most (if not all) of the errors in the .d.ts file are variations of the error shown by this repro but it's so hard to know as there are hundreds of them.

Note: I didn't write this code and I hate that it exists. I am sorry.

@bradzacher bradzacher changed the title Generated .d.ts file contains invalid type names Generated .d.ts file contains invalid types Mar 19, 2024
@bradzacher
Copy link
Contributor Author

bradzacher commented Mar 19, 2024

Update: This is a regression in TS5.4

In TS5.3 the emitted .d.ts file is correct, but in TS5.4 it uses the wrong type names

@bradzacher bradzacher changed the title Generated .d.ts file contains invalid types [regression - 5.4] Generated .d.ts file contains invalid types Mar 19, 2024
@bradzacher
Copy link
Contributor Author

bradzacher commented Mar 19, 2024

I've got a repro repo for testing too.

  1. clone https://github.com/bradzacher/bug-repros
  2. checkout the typescript-dts-emit-bug branch
  3. npm i
  4. npm test --> observe diff

Bisecting it looks like the first release that exhibited this bug was 5.4.0-dev.20240110 (it does not reproduce on 5.4.0-dev.20240109)

@jakebailey
Copy link
Member

If you hadn't tried it yet, https://www.npmjs.com/package/every-ts can bisect further down from the nightly versions.

@bradzacher
Copy link
Contributor Author

bradzacher commented Mar 19, 2024

Bisecting with every-ts reveals 4bcbc16 to be the bad commit (#56087)

@bradzacher
Copy link
Contributor Author

Yup - 100% confirmed.
4bcbc16 only has 4 lines of changes so it's easy to revert locally. If I comment out the 4 lines then it emits the correct output.

@bradzacher
Copy link
Contributor Author

I patched #57396 locally and it looks like it fixes the regression

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Fix Available A PR has been opened for this issue
Projects
None yet
3 participants