I’m having trouble understanding some typescript behavior in regards to circular references for types.
type NonEmptyObject<T> = keyof T extends never ? never : T
type Base = { [key: PropertyKey]: Nested }; // I'm using this instead of Record to avoid a circular reference.
type NonEmptyBase = NonEmptyObject<Base>;
type Nested = string | NonEmptyBase;
Type alias 'NonEmptyBase' circularly references itself.ts(2456)
Type alias 'Nested' circularly references itself.ts(2456)
If I switch to generics, everything works fine. But this looks wrong and feels even more circularly mixed up.
type Base<T extends Base<T>> = { [key: PropertyKey]: Nested<T> };
type NonEmptyBase<T extends Base<T>> = NonEmptyObject<T>;
type Nested<T extends Base<T>> = string | NonEmptyBase<T>;
Why is this and how can I achieve the desired outcome without generics?
The error you’re encountering is due to circular references in your type definitions. TypeScript is unable to resolve these circular references when defining type aliases directly.
In your initial code, the NonEmptyBase type refers to NonEmptyObject, and Base refers to Nested, while Nested also refers to NonEmptyBase. This circular reference creates an error.
To achieve the desired outcome without using generics, you can use interfaces instead of type aliases.
As per this link – [1]: https://github.com/microsoft/TypeScript/issues/28748
would
type Nested = string | Base
be okay for you? Or do you necessarily need the nested ones nonempty? I’m really unsure about yourNonEmptyBase
. Your generic doesn’t make any sense in here, since you are not passing any generic types there, but rather aBase
, which’s key arePropertyKey
, so basicallyNonEmptyBase
is the same asBase
I do need the
Base
to be nonempty. The reasoning is that I don’t want to be able to pass an empty object into the function using this type. I have tried extending the generic toNonEmptyObject<T extends object>
, but unfortunately that didn’t change anything.