-
Notifications
You must be signed in to change notification settings - Fork 12.6k
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
Unclear type compatibility of primitives with generic mapped types #47633
Comments
It's a design limitation - conditional types predicated on a generic type parameter are "deferred" (i.e. not actually evaluated) while in a generic context. Essentially, TS doesn't know whether or not |
@fatcerberus Ah, interesting. However, that doesn't explain why |
That I'm not sure of. You would think so though, right? Someone else more knowledgeable about the internal workings of the compiler will need to chime in on that one. 😅. I presume it has something to do with the mapped type being known by the compiler to be homomorphic ( I do seem to recall there was a special rule where if the compiler can figure out that a type is assignable to both sides of a conditional type, assignment is allowed even while deferred, but I don't know what triggers it. |
Ah, I think that's it - Consistent with this analysis, making |
Oh, thanks! I didn't know about this neat little trick. Unfortunately, I think I would want my Also I don't understand why the compiler assumes that |
It's not that |
For what it's worth, soundness is explicitly not a goal of the type system - it's designed to prevent common errors in idiomatic JS and so you'll find there are a lot of these kinds of guessing games w.r.t. what the rules are. It's been my observation that beyond the basic structural type system which is fairly well-defined for concrete types, things are largely guided by pragmatism (and design constraints) in the more abstract corners of the system. |
Yeah, that's what I meant - I know that TypeScript doesn't aim for a sound type system. Unfortunately that imo makes it very unpredictable when attempting to define more advanced precise types for generic functions, a game of try-and-error :-( But I'll stop my rant here and continue trying to learn about all the undocumented intricacies of the compiler implementation… Thanks for your help with that! |
This issue has been marked as 'Question' and has seen no recent activity. It has been automatically closed for house-keeping purposes. If you're still waiting on a response, questions are usually better suited to stackoverflow or the TypeScript Discord community. |
Bug Report
🔎 Search Terms
primitive type partial object mapped type
💻 Code
I was trying to define my own
DeepPartial
generic type mapper. Here are three attempts (and, for comparison, the builtinPartial
):⏯ Playground Link
Passing a primitive value to the
example
function should correctly infer the same primitive type to be returned.DeepPartial<number>
should be equivalent tonumber
, not to a mapped object type.This is why I chose implementation 1 with the type condition directly on
T
- and callingexample1
withstring
orstring | number
for the type parameter does indeed infer that very same type as the result type of the call, unlikeexample2<string | number>(…)
returningDeepPartial2<string | number>
.🙁 Actual behavior
Using
DeepPartial1
, which I thought is the correct implementation, doesn't work. On thereturn
statement of the genericexample1
function, tsc complains:(also this message is not very helpful - there's no indication which properties are missing or incompatible)
🙂 Expected behavior
Using
DeepPartial1
in the generic function should typecheck just fine.Questions
I don't understand three things:
T
not being compatible withDeepPartial1<T>
- it seems to be a proper subtype to me regardless whetherT extends object
or not. Is it a bug or am I missing something?DeepPartial3
does work, instead of causing the same problem on the first nesting levelPartial<number>
is considered compatible withnumber
- it seems that mapped helper types likePartial
should only be used on objects. Why and how does this work, where is this behaviour documented?The text was updated successfully, but these errors were encountered: