-
Notifications
You must be signed in to change notification settings - Fork 12.8k
Document mapped object types in spec #20971
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
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems like a good overview. Does trying to reflect the implementation help here? Or do you drown in a sea of special cases gathered from throughout the compiler?
|
||
* The first *Type* (immediately following the `in` keyword) is the operand *K* of a mapped type. | ||
* The second *Type* forms the *property type template* of a mapped type, *T*. | ||
* The *Identifier* forms a type variable *P* that is scoped only in *T*, and is bound and constrained to *K*. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A syntactic form doesn’t form a semantic object. It, uh, creates it? Not sure actually. Results in?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not following on the question here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The verb used is wrong. Syntax doesn't form semantic things like type variable, so an identifier doesn't form a type variable. You might be able to say "The identifier specifies a type variable P" or "The identifier defines a type variable P" or "The identifier references a type variable P". The last two sound best to me, but I think defines is correct here.
  *MappedObjectType:* | ||
   `{` `readonly`*<sub>opt</sub>* `[`*Identifier* `in` *Type*`]` `?`*<sub>opt</sub>* *TypeAnnotation<sub>opt</sub>* `}` | ||
|
||
A ***mapped object type*** is a type operator that operates on types assignable to the string type, but primarily on unions of string literal types. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would change "but primarily" to "usually"
# Mapped Object Types { #mapped-object-types } | ||
|
||
  *MappedObjectType:* | ||
   `{` `readonly`*<sub>opt</sub>* `[`*Identifier* `in` *Type*`]` `?`*<sub>opt</sub>* *TypeAnnotation<sub>opt</sub>* `}` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why is TypeAnnotation optional?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
because it's optional :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
WOW. Why do we support this? It looks like if TypeAnnotation is missing, then it's treated as if it were : any
. Is that documented in this PR?
In the above syntax, | ||
|
||
* The first *Type* (immediately following the `in` keyword) is the operand *K* of a mapped type. | ||
* The second *Type* forms the *property type template* of a mapped type, *T*. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is marked as a type annotation in the grammar. Instead of referencing the grammar, I would give an example of the syntax first: { [P in K]: T }
and then reference P, T and K for this explanation.
* The second *Type* forms the *property type template* of a mapped type, *T*. | ||
* The *Identifier* forms a type variable *P* that is scoped only in *T*, and is bound and constrained to *K*. | ||
|
||
Mapped object types are primarily meant to iterate over a union of string literal types, and to generate a new object type containing properties whose names are based on each string literal within that union. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You already mentioned that mapped types usually operate on string literals. I would reword to "When the operand K is a union of string literal types, mapped object types iterate over ..."
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"are based on" is vague. Maybe "properties which take their names from the value of the members of the string literal union", or a more efficient variant.
|
||
## Homomorphic Mapped Object Types { #homomorphic-mapped-object types } | ||
|
||
A ***homomorphic mapped object type*** is a mapped type of a particular form, where the operand *K* is a type query `keyof` *O*. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
d of a particular form,
|
||
type A = { | ||
foo?: number; | ||
bar: number; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
bar should be optional
A ***homomorphic mapped object type*** is a mapped type of a particular form, where the operand *K* is a type query `keyof` *O*. | ||
|
||
In such instances, TypeScript will consult the type `O` when generating each property and index signature for respective modifiers. | ||
If a modifier is not specified in the mapped type itself, but is specified for a given property in *O*, then the resulting property inherits that same modifier. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In such instances, TypeScript uses the properties and index signatures in O as the source of modifiers for the properties and index signature of the mapped type.
That is, a property p' in a mapped type has all the modifiers from the originating property p in O unless the mapped type's template specifies modifiers.
}; | ||
``` | ||
|
||
This can be powerfully combined with key query types, and indexed access types: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
combined powerfully reads better to me, but the sentence is a little weird either way.
``` | ||
|
||
As mentioned, mapped object types introduce a type variable *P*. | ||
When *K* is not a generic type, as seen thus far, then when generating each property of a mapped object type, the type of that property is *T* with instances of *P* substituted with a string literal type whose contents are equivalent to the property name itself. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's a lot better to phrase this something like "in the scope of the template type, P is each string literal type in the union K for properties and P is K for index signatures.
@DanielRosenwasser can we get these in? the spec is now stale, and the changes to update it are stale as well :) |
@DanielRosenwasser I'd join @mhegazy question: Any progress in updating the spec? The spec is heavily outdated and all related MR seem stale. |
@DanielRosenwasser Is there any progress on this six weeks later? |
@DanielRosenwasser Is there any progress on this ten weeks later? :-) |
@DanielRosenwasser Is there any progress on this PR? :-) It's been opened 44 weeks ago. |
Fixes #20970.