You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Though it seems recursive mapped types didn't get as much love.
Is there any way to write the signature of update so that the following can be true?
I run into either too permissive behaviors (I can add keys that are not in the original object, or set a non nullable key to undefined) or inferFromTypes: Maximum call stack size exceeded.
Because it has to be deep. At every level, it is allowed to either pass a primitive (it will be set) or an object with a partial set of keys, and so on.
i.e, this should compile :
constupdated=update({a: {b: {c: 33,d: 33,e: 'aa'}}},{a: {b: {c: 99}})// updated is now equals to { a: { b: { c: 99, d: 33, e: 'aa' }}}// The spec didn't change d and e, so their values are still the same.
that is cause the recursion makes it go to any. I will need to discuss this with Anders Hejlsberg (@ahejlsberg) and see if there is anything we can do to make this work in a better way.
With the current nightly we are actually very close:
typeUpdateSpec<T>={[PinkeyofT]?: UpdateSpec<T[P]>};declarefunctionupdate<T>(obj: T,spec: UpdateSpec<T>): T;declareletfoo: {a: number,b: {x: string}};// These all work as expectedupdate(foo,{a: 1});update(foo,{b: {x: "test"}});update(foo,{a: 10,b: {x: "howdy"}});// These are all errors as expectedupdate(foo,{a: "hello"});update(foo,{b: {x: 42}});update(foo,{b: {y: 10}});update(foo,{b: {y: 10}});update(foo,{c: true});// These should be errors but aren'tupdate(foo,"ouch");update(foo,{b: "ouch"});
The thing that works for us here is that when a homomorphic mapped type { [P in keyof T]: X } is instantiated for a primitive type we simply yield the primitive type. The thing that doesn't work so well is actually quite unrelated to mapped types: When the target of an assignment is a type with properties that are all optional, the source can effectively be any type that doesn't have those properties. For example:
letx: {a?: number,b?: string}="ouch";
We're perfectly happy with that because type String has no a or b property and therefore satisfies the contract. Since an UpdateSpec<T> has only optional properties we end up in the same situation.
I see! Would it help with the primitive case if you could say "give me an object with the same shape , with all optional keys BUT with a cardinality of at least one"? After all, if you're willing to update something, you might as well not pass a null object.
So it gets closer to full type safety, but I have the feeling you're not willing to go to full typesafety considering the "primitive to fully optional {} is structurally allowed" (which makes sense in most use cases but is a bit unfortunate in this one); or is it that you know about an upcoming fix specially for Mapped types that could circumvent that?
Anyway, thanks; that's already much better than what can be done currently!
Argument of type '{ a: number; b: { y: number; }; }' is not assignable to parameter of type 'UpdateSpec<IFoo>'.
Types of property 'b' are incompatible.
Type '{ y: number; }' is not assignable to type '({ x: string; y: number; } & UpdateSpec<{ x: string; y: number; }>) | undefined'.
Type '{ y: number; }' is not assignable to type '{ x: string; y: number; } & UpdateSpec<{ x: string; y: number; }>'.
Type '{ y: number; }' is not assignable to type '{ x: string; y: number; }'.
Activity
mhegazy commentedon Dec 8, 2016
these two examples seem conflicting to me..
and
since the first is more or less the same as saying
c: undefined.on any rate, i would say using two type parameters and a constraint to check their relationship would be a good way to go:
assuming that the first argument is a "spec" and the second can omit members, then it can be as:
AlexGalays commentedon Dec 8, 2016
Not sure how that helps :(
Btw, did you mean T extends U ? I doesn't makes sense for U to have more properties than T in my usage.
That's what I have so far btw :
mhegazy commentedon Dec 8, 2016
i am confused now.. so if all what you want is for the second be a partial of the first, why now just:
AlexGalays commentedon Dec 8, 2016
Because it has to be deep. At every level, it is allowed to either pass a primitive (it will be set) or an object with a partial set of keys, and so on.
i.e, this should compile :
AlexGalays commentedon Dec 8, 2016
Made some progress with
But I still get stack overflows for type inference on some usages and I wish the ? operator didn't allow explicitly setting undefined values.
This will be troublesome for React too, as setState() will allow one to set undefined on a non nullable property.
mhegazy commentedon Dec 8, 2016
I do not think the intersection is correct.. this should work..
that it does not is seems like a bug, and we should investigate this further.
AlexGalays commentedon Dec 9, 2016
Yeah it's strange. Without intersecting with T[P], I can update most properties with another property with a completely different type.
mhegazy commentedon Dec 9, 2016
that is cause the recursion makes it go to
any. I will need to discuss this with Anders Hejlsberg (@ahejlsberg) and see if there is anything we can do to make this work in a better way.ahejlsberg commentedon Dec 14, 2016
With the current nightly we are actually very close:
The thing that works for us here is that when a homomorphic mapped type
{ [P in keyof T]: X }is instantiated for a primitive type we simply yield the primitive type. The thing that doesn't work so well is actually quite unrelated to mapped types: When the target of an assignment is a type with properties that are all optional, the source can effectively be any type that doesn't have those properties. For example:We're perfectly happy with that because type
Stringhas noaorbproperty and therefore satisfies the contract. Since anUpdateSpec<T>has only optional properties we end up in the same situation.AlexGalays commentedon Dec 14, 2016
I see! Would it help with the primitive case if you could say "give me an object with the same shape , with all optional keys BUT with a cardinality of at least one"? After all, if you're willing to update something, you might as well not pass a null object.
So it gets closer to full type safety, but I have the feeling you're not willing to go to full typesafety considering the "primitive to fully optional {} is structurally allowed" (which makes sense in most use cases but is a bit unfortunate in this one); or is it that you know about an upcoming fix specially for Mapped types that could circumvent that?
Anyway, thanks; that's already much better than what can be done currently!
14 remaining items
sandersn commentedon Jun 29, 2017
With 2.4's weak type checking, the last two examples are now errors, so I think the last part of this bug has been addressed.
esamattis commentedon Sep 18, 2017
What about when there is a property missing from a nested object?
but yields
mhegazy commentedon Sep 18, 2017
should not it be
Partial<T[P]>instead ofT[P].esamattis commentedon Sep 18, 2017
Ah, you're right. Works with Partial, thanks!