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

"Duplicate identifier" errors introduced in TS 5.5, if declare module is used #58961

Closed
AlessioGr opened this issue Jun 21, 2024 · 11 comments
Closed
Labels
Working as Intended The behavior described is the intended behavior; this is not a bug

Comments

@AlessioGr
Copy link

AlessioGr commented Jun 21, 2024

🔎 Search Terms

declare module
duplicate identifier
5.5

🕗 Version & Regression Information

This issue was introduced in ts 5.5.2. It's still an issue in 5.6.0-dev.20240621. In 5.4.5 it works as expected.

Specifically, this issue was introduced in 5.5.0-dev.20240524. The last version where this was working as expected was 5.5.0-dev.20240523.

⏯ GitHub reproduction

https://github.com/AlessioGr/tsbug/blob/83e990cdb97d016f13aac969258aa36491dd31b1/src/index.ts#L9-L8

💻 Code

(as linked in the reproduction):

import type { PayloadRequest } from 'payload'

declare module 'payload' {
    export interface DatabaseAdapter {
        test: string
    }
}

🙁 Actual behavior

The code throws the following error in TypeScript 5.5.2 and works in 5.4.5:

src/index.ts:5:22 - error TS2300: Duplicate identifier 'DatabaseAdapter'.

5     export interface DatabaseAdapter {
                       ~~~~~~~~~~~~~~~

  node_modules/.pnpm/payload@3.0.0-beta.53_@swc+core@1.6.3_@swc+types@0.1.8_graphql@16.9.0_typescript@5.5.2/node_modules/payload/dist/index.d.ts:199:15
    199 export type { DatabaseAdapter, GeneratedTypes, Payload, RequestContext };
                      ~~~~~~~~~~~~~~~
    'DatabaseAdapter' was also declared here.

Here is how it is exported from the payload module:

type DatabaseAdapter = BaseDatabaseAdapter;
export type { DatabaseAdapter, GeneratedTypes, Payload, RequestContext };

Strangely, if I export it like this instead, the TypeScript error disappears:

export type { BaseDatabaseAdapter as DatabaseAdapter, GeneratedTypes, Payload, RequestContext };

🙂 Expected behavior

The code should not throw an error / the repo should compile when running npx tsc.

Additional information about the issue

@RyanCavanaugh
Copy link
Member

This is a correct error that was incorrectly missed before due to a bug. It's not legal to augment a type alias with an interface.

@RyanCavanaugh RyanCavanaugh added the Working as Intended The behavior described is the intended behavior; this is not a bug label Jun 21, 2024
@lukasbash
Copy link

I am facing the same issue but I do not really get what you mean with regard to a type alias @RyanCavanaugh?

In my example I am working with the mantine component framework and they are providing a type MantineModalsOverride specifically exported for module augmentation so that their component can correct use the preferred type definitions.

I am using the module augmentation like this:

const modalConfigs = { /*...*/ }

declare module "@mantine/modals" {
  export type MantineModalsOverride = {
    modals: typeof modalConfigs;
  };
}

In their package they are simple exporting it as:

// package: @mantine/modals
export type MantineModalsOverride = {};

Why should this be intended? How can I correctly augment the type here?

@fatcerberus
Copy link

You can’t. If they intended the type to be augmented it should have been declared as an interface.

@JstnMcBrd
Copy link

JstnMcBrd commented Jun 24, 2024

I have also encountered this problem, but not when augmenting type aliases. TypeScript throws the same "duplicate identifier" error when trying to augment a class.

// This example is based off a real-life package that TS 5.5 broke
import { EventEmitter } from 'node:events';

declare module 'node:events' {
  class EventEmitter {
    test: string;
  }
}
src/index.ts:6:9 - error TS2300: Duplicate identifier 'EventEmitter'.

   class EventEmitter {
          ~~~~~~~~~~~~

  node_modules/@types/node/events.d.ts:519:30
    519         export { internal as EventEmitter };
                                     ~~~~~~~~~~~~
    'EventEmitter' was also declared here.

This example works in 5.4.5, but breaks in 5.5.2. In contrast, interface augmentation still works fine.

Is this also intentional behavior, related to the same bugfix mentioned previously? If so, is this an undocumented breaking change? I know of several packages that rely on class augmentation in their types.

@AlessioGr
Copy link
Author

@RyanCavanaugh I feel like it might be a good idea to mention this change briefly in the TS changelogs! Not a breaking change since it was a bug, but it would still remove "functionality" people could have depended on before. Would be helpful for them (including me) to know.

Would also be nice to add more clarity in the docs about module augmentation not being allowed for types, as well as mentioning that you cannot override existing properties - maybe here: https://www.typescriptlang.org/docs/handbook/declaration-merging.html#disallowed-merges. It's never explicitly mentioned that types cannot be augmented.

You are right TypeScript was never intended to allow module augmentation on types. And not only that. The previous bug allowed us to do the following things:

  1. Use module augmentation on types
  2. Override EXISTING properties on those types.

To fix this, not only did we have to change them to interfaces, we also had to find a new pattern for what we were trying to do, as it's no longer possible to override properties using module augmentation.
Instead of overriding properties, we had to add new properties using module augmentation, and change whatever accessed those types in our code to conditionally either use the new, augmented types, or the old non-augmented ones.

I'll close this given it's working the way it was intended to work

@AlessioGr AlessioGr closed this as not planned Won't fix, can't repro, duplicate, stale Jun 24, 2024
@JstnMcBrd
Copy link

JstnMcBrd commented Jun 24, 2024

I see this issue has been closed. Let me know if I should open a separate issue for the class augmentation problem I mentioned above.

@fatcerberus
Copy link

fatcerberus commented Jun 24, 2024

The previous bug allowed us to do the following things

I could have sworn I remembered the previous (buggy) behavior being "the augmentation is just silently ignored"...

@JstnMcBrd Documentation explicitly states

classes can not merge with other classes

AFAIK you can augment a class using an interface, though.

@RyanCavanaugh
Copy link
Member

See #53287; this usually manifested as a crash because multiple parts of the code rely on the assumption that type aliases and interfaces can't merge.

@RyanCavanaugh
Copy link
Member

RyanCavanaugh commented Jun 24, 2024

This example works in 5.4.5, but breaks in 5.5.2. In contrast, interface augmentation still works fine.

Correct; to add instance properties to a class, use an interface merge

@6mint
Copy link

6mint commented Jul 5, 2024

When using a .d.ts file the interface doesn't throw the duplicate identifier error even though it is defined in an installed module. Is this working as intended or is this incorrect behaviour that will be fixed in the future?

The code below works as a .d.ts file but throws as a .ts file:

import { Config } from "./payload-types.ts";

declare module "payload" {
  export interface GeneratedTypes extends Config {}
}

@GersonDias
Copy link

when the bug is better than the feature...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Working as Intended The behavior described is the intended behavior; this is not a bug
Projects
None yet
Development

No branches or pull requests

7 participants