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

String literal type unexpectedly widened to string #54767

Closed
mspiess opened this issue Jun 25, 2023 · 5 comments
Closed

String literal type unexpectedly widened to string #54767

mspiess opened this issue Jun 25, 2023 · 5 comments
Labels
Duplicate An existing issue was already created

Comments

@mspiess
Copy link

mspiess commented Jun 25, 2023

Bug Report

πŸ”Ž Search Terms

  • string literal type
  • widen
  • not assignable to

πŸ•— Version & Regression Information

  • This is the behavior in every version I tried, and I reviewed the FAQ for entries about mapped types

⏯ Playground Link

Playground link with relevant code

πŸ’» Code

type StringKeyOf<T> = Exclude<keyof T, symbol>;
type JsonPointer<T> = T extends object
  ? { [K in StringKeyOf<T>]: `/${K}${JsonPointer<T[K]>}` | `/${K}` }[StringKeyOf<T>]
  : '';

function createJsonPointer<T>(key: StringKeyOf<T>): JsonPointer<T> {
    return `/${key}`;
}

πŸ™ Actual behavior

Following error is emitted because StringKeyOf<T> is falsely widened to string:

Type '`/${string}`' is not assignable to type 'JsonPointer<T>'.

πŸ™‚ Expected behavior

I expected the code to type check as it does if the function's type parameter is replaced with a concrete type.

@MartinJohns
Copy link
Contributor

MartinJohns commented Jun 25, 2023

Duplicate of #33912.

Your issue is not a widening to string, that's just part of the error message. The problem is your generic conditional type. In order to know if /${key} is a valid return type, the compiler needs to know what type JsonPointer<T> is. In order to know what type it is, it needs to know what type T is, because JsonPointer<T> is a conditional type that changes depending on T. Unfortunately the compiler doesn't know what type T is because it's supplied by the callsite.

@mspiess
Copy link
Author

mspiess commented Jun 26, 2023

Duplicate of #33912.

It's not specific to return types. If instead of returning, we try to pass the value as function argument, the same behavior can be observed.

Your issue is not a widening to string, that's just part of the error message.

Thank you for the explanation @MartinJohns ! The error message could be more precise.

The problem is your generic conditional type. In order to know if /${key} is a valid return type, the compiler needs to know what type JsonPointer is. In order to know what type it is, it needs to know what type T is, because JsonPointer is a conditional type that changes depending on T. Unfortunately the compiler doesn't know what type T is because it's supplied by the callsite.

Could the compiler do "partial evaluation" of conditional types? E.g. if we constrain the function's type parameter with extends object that should provide enough information to know that /${StringKeyOf<T>} is a valid JsonPointer<T>.

@MartinJohns
Copy link
Contributor

It's not specific to return types. If instead of returning, we try to pass the value as function argument, the same behavior can be observed.

I can not reproduce this. Playground link

Could the compiler do "partial evaluation" of conditional types?

There's an issue about this, but I can't find it right now. I wouldn't expect this supported any time soon. It also wouldn't solve your issue, because of the recursiveness of your type - it's still a conditional type for the deeper levels.

@RyanCavanaugh
Copy link
Member

This is possible with a few workarounds. Critically, your conditional type can't be distributive.

type StringKeyOf<T> = Exclude<keyof T, symbol>;
type JsonPointer<T> = [T] extends [object]
  ? { [K in StringKeyOf<T>]: `/${K}${JsonPointer<T[K]>}` | `/${K}` }[StringKeyOf<T>]
  : '';

function createJsonPointer<T extends object>(key: StringKeyOf<T>): JsonPointer<T & object> {
    return `/${key}`;
}

There's an existing issue tracking the fact that you have to write the meaningless "T & object" in the type argument there for this to work

@RyanCavanaugh RyanCavanaugh added the Duplicate An existing issue was already created label Jun 26, 2023
@typescript-bot
Copy link
Collaborator

This issue has been marked as "Duplicate" and has seen no recent activity. It has been automatically closed for house-keeping purposes.

@typescript-bot typescript-bot closed this as not planned Won't fix, can't repro, duplicate, stale Jun 29, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Duplicate An existing issue was already created
Projects
None yet
Development

No branches or pull requests

4 participants