Skip to content

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

Not planned
@AlessioGr

Description

@AlessioGr

🔎 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

Activity

RyanCavanaugh

RyanCavanaugh commented on Jun 21, 2024

@RyanCavanaugh
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.

lukasbash

lukasbash commented on Jun 22, 2024

@lukasbash

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

fatcerberus commented on Jun 22, 2024

@fatcerberus

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

JstnMcBrd

JstnMcBrd commented on Jun 24, 2024

@JstnMcBrd

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

AlessioGr commented on Jun 24, 2024

@AlessioGr
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

JstnMcBrd

JstnMcBrd commented on Jun 24, 2024

@JstnMcBrd

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

fatcerberus commented on Jun 24, 2024

@fatcerberus

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

RyanCavanaugh commented on Jun 24, 2024

@RyanCavanaugh
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

RyanCavanaugh commented on Jun 24, 2024

@RyanCavanaugh
Member

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

6mint commented on Jul 5, 2024

@6mint

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 {}
}

6 remaining items

Loading
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Working as IntendedThe behavior described is the intended behavior; this is not a bug

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

      Participants

      @GersonDias@fatcerberus@RyanCavanaugh@lukasbash@JstnMcBrd

      Issue actions

        "Duplicate identifier" errors introduced in TS 5.5, if declare module is used · Issue #58961 · microsoft/TypeScript