Skip to content

Conversation

@ethanresnick
Copy link
Contributor

@ethanresnick ethanresnick commented Sep 24, 2025

I may be able to better explain later what's going on here, when I have a chance to debug it further, but the TL;DR is that, as of v5, when a variable whose type contains a Tagged type gets exported from a file, TS's declaration emit often produces a lot of errors like:

Exported variable 'xyz' has or is using name 'tag' from external module
"/Users/ethanresnick/my-project/node_modules/.pnpm/[email protected]/node_modules/type-fest/source/tagged"
but cannot be named.ts(4023)

It seems like the fact that the tag symbol now is imported from a separate package somehow influences TS' decisions about how to (try to) emit type declarations for an export. The result is that the emitted declarations look something like this:

export const xyz: import("type-fest").Tag<"UUID", never> & import("type-fest").Tag</* ... */> & string;

I.e., TS has reduced Tagged<....> to an intersection of Tag<..> & ... & Base, but, because Tag isn't itself exported, the declaration emit hits blows up with the above error. Before, what seemed to happen more often is that the declared exported type would not be as reduced, and would just reference the public Tagged export.

The only fix I've found is to manually import and re-export the tag symbol from tagged-tag:

import type tag from "tagged-tag";
export { tag };

With that, the emitter is able to see that type-fest's Tag type is just a small wrapper type around the same exported symbol, so it totally inlines Tag's definition. I.e., the code above instead is emitted as:

import type tag from "tagged-tag";
export type { tag };
export const xyz: {
    readonly [tag]: {
        UUID: never;
    };
} & {
    readonly [tag]: {
        /* ... */
    };
} & string;

This works, but unfortunately, this import and re-export has to be added transitively — i.e., it also has to be added to every file that re-exports something derived from xyz. In my codebase, I had to add this import and re-export of the tag symbol to 50+ files, and I'm sure that most developers would never intuit that this is the workaround when given the compiler's somewhat inscrutable error message.

So, this PR fixes at least most of the symptoms of the issue by exporting Tag so that emitted declarations like import("type-fest").Tag<"UUID", never> work.

Copilot AI review requested due to automatic review settings September 24, 2025 23:45
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot wasn't able to review any files in this pull request.


Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

@som-sm
Copy link
Collaborator

som-sm commented Sep 25, 2025

@ethanresnick I'm unable to reproduce this issue, can you help me reproduce it.

@ethanresnick
Copy link
Contributor Author

Yeah, I'll try to put together a full reproduction later this week

@cooper667
Copy link

cooper667 commented Oct 2, 2025

I also have this issue, so will try to create a reproduction too (have had forever, not just v5)

@ethanresnick
Copy link
Contributor Author

ethanresnick commented Oct 9, 2025

I tried to put together a reproduction, and discovered that the issue may actually be a bug in TS; see microsoft/TypeScript#60913 (comment).

However, that repro in that TS issue also shows how type-fest is effected: it uses a simplified version of type-fest's ReadonlyDeep to demonstrate the issue, and a workaround would be exporting ReadonlyObjectDeep, analogous to what I'm doing in this PR by exporting Tag.

Meanwhile, here's a very simple repro with tagged types in particular, where there's an error goes away with this PR: https://github.com/ethanresnick/type-fest-bug-repro/blob/main/src/index.ts

@ethanresnick
Copy link
Contributor Author

ethanresnick commented Oct 9, 2025

@sindresorhus If you read the linked TS issue (#60913), it seems like what's really going on here is that adding the exports key to package.json was probably a much bigger breaking change than was intended/understood, because of this TS declaration emit behavior.

@ethanresnick
Copy link
Contributor Author

As it stands on the TS side, type-fest may have to either export many more of its internal utility types, or inline their definitions into the public types that use them.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants