-
Notifications
You must be signed in to change notification settings - Fork 12.5k
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
Design Meeting Notes, 3/8/2022 #53169
Comments
Reads like a good novel. It has everything:
|
The problem with the "homomorphic" version of type Omit2<T, DroppedKeys extends PropertyKey> = {
[K in keyof T as Exclude<K, DroppedKeys>]: T[K];
};
function omit2<O, Mask extends { [K in keyof O]?: true }>(
_object: O,
_mask: Mask
): Omit2<O, keyof Mask> {
throw new Error("Not implemented");
}
const object2 = omit2(object,
{
name: true,
}
);
object2.id // ❌ goto/docblock |
Hm, I expected that to work in 5.0 since #51650 . It doesn't though... I'll investigate what's happening there |
It seems that my fix (#51650) had a bug. Accidentally, my other PR already fixes this but since its fate is still not determined, I will prepare a separate PR fixing this tomorrow. |
I was often confused about this in the past too. In reality, mapped types are not split into two groups though (homomorphic and non-homomorphic). They are split into 3: homomorphic, non-homomorphic with non-unknown modifiers type and non-homomorphic with an unknown moifiers type. Homomorphic mapped types preserve potential arrayness of the input, they distribute over unions and they preserve original modifiers. So in a sense, homomorphic mapped types are a subset of the mapped typed with non-unknown modifiers type. I might be using this lingo slightly wrong (so don't quote me on that) but that's roughly how things are called under the hood (there is no specific name for a mapped type with a (non-)unknown modifiers type though, it's just something that is checked when "transferring" the modifiers on the output type etc). |
Okay, so "non-homomorphic with known modifiers" is specifically what you get when you write |
Yes, that's one way to end up with this "type".
function getModifiersTypeFromMappedType(type: MappedType) {
if (!type.modifiersType) {
if (isMappedTypeWithKeyofConstraintDeclaration(type)) {
// If the constraint declaration is a 'keyof T' node, the modifiers type is T. We check
// AST nodes here because, when T is a non-generic type, the logic below eagerly resolves
// 'keyof T' to a literal union type and we can't recover T from that type.
type.modifiersType = instantiateType(getTypeFromTypeNode((getConstraintDeclarationForMappedType(type) as TypeOperatorNode).type), type.mapper);
}
else {
// Otherwise, get the declared constraint type, and if the constraint type is a type parameter,
// get the constraint of that type parameter. If the resulting type is an indexed type 'keyof T',
// the modifiers type is T. Otherwise, the modifiers type is unknown.
const declaredType = getTypeFromMappedTypeNode(type.declaration) as MappedType;
const constraint = getConstraintTypeFromMappedType(declaredType);
const extendedConstraint = constraint && constraint.flags & TypeFlags.TypeParameter ? getConstraintOfTypeParameter(constraint as TypeParameter) : constraint;
type.modifiersType = extendedConstraint && extendedConstraint.flags & TypeFlags.Index ? instantiateType((extendedConstraint as IndexType).type, type.mapper) : unknownType;
}
}
return type.modifiersType;
} whereas function isMappedTypeHomomorphic(type: MappedType) {
return !!getHomomorphicTypeVariable(type);
} and function getHomomorphicTypeVariable(type: MappedType) {
const constraintType = getConstraintTypeFromMappedType(type);
if (constraintType.flags & TypeFlags.Index) {
const typeVariable = getActualTypeVariable((constraintType as IndexType).type);
if (typeVariable.flags & TypeFlags.TypeParameter) {
return typeVariable as TypeParameter;
}
}
return undefined;
} Sorry for quoting a bunch of source code but I feel like it's just the only way to answer this precisely ;p Note that an |
Using a Homomorphic Mapped Type For Omit
#53134
Today,
Omit
is non-homomorphic. Will not preserve shape.keyof
on its own does not retain its source type.Today, we can write
Omit
with anas
clause withExclude
- still considered "homomorphic" (even though at this point "homomorphic" is a misnomer).Switching
Omit
at this point would likely be breaky.interface
s extends fromOmit
Using a "homomorphic"
Omit
makes code flow work better because it can produce union types.Could choose to have
Omit
(original) andMappedOmit
- orOmit
(improved) andLegacyOmit
.Omit
to the "better" version is breaky - at least one package breaks.We really don't want to have 2
Omit
s, and want to push on an improvedOmit
.Pick<T, Omit<keyof T, DroppedKeys>>
Wait, should
Pick
be fixed to use a homomorphic mapped type too?Omit
.If you fix
Pick
, thenOmit
defined asPick<T, Exclude<keyof T, DroppedKeys>>
is also homomorphic, right?Omit
is not going to distribute, when we writeExclude<keyof T, DroppedKeys>
So each of them need to be written as their own mapped types written in terms of
keyof T
.Note: the
as
clause forPick
should use an intersection, notExtract
never
.The text was updated successfully, but these errors were encountered: