-
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
Implementing DeepMerge #12769
Comments
these two examples seem conflicting to me.. update(
{ a: { b: [1] }, c: 33, d: '44' },
{ a: { b: [1, 2] }, d: '66' }
) and update({ a: 33 }, { a: undefined }) since the first is more or less the same as saying on any rate, i would say using two type parameters and a constraint to check their relationship would be a good way to go: declare function update<T, U extends T>(a: T, U): void; assuming that the first argument is a "spec" and the second can omit members, then it can be as: declare function update<T, U extends T>(a: T, b:Partial<U>): void; |
Not sure how that helps :( That's what I have so far btw : type UpdateSpec<T> = {
[P in keyof T]?: UpdateSpec<T[P]>
}
export default function update<T>(obj: T, spec: UpdateSpec<T>): T |
i am confused now.. so if all what you want is for the second be a partial of the first, why now just: declare function update<T>(a: T, b: Partial<T>): void; |
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 : const updated = 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. |
Made some progress with type UpdateSpec<T> = {
[P in keyof T]?: T[P] & UpdateSpec<T[P]>
}
export default function update<T>(obj: T, spec: UpdateSpec<T>): T 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. |
I do not think the intersection is correct.. this should work.. type UpdateSpec<T> = {
[P in keyof T]?: UpdateSpec<T[P]>
} that it does not is seems like a bug, and we should investigate this further. |
Yeah it's strange. Without intersecting with T[P], I can update most properties with another property with a completely different type. |
that is cause the recursion makes it go to |
With the current nightly we are actually very close: type UpdateSpec<T> = { [P in keyof T]?: UpdateSpec<T[P]> };
declare function update<T>(obj: T, spec: UpdateSpec<T>): T;
declare let foo: { a: number, b: { x: string } };
// These all work as expected
update(foo, { a: 1 });
update(foo, { b: { x: "test" } });
update(foo, { a: 10, b: { x: "howdy" } });
// These are all errors as expected
update(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't
update(foo, "ouch");
update(foo, { b: "ouch" }); The thing that works for us here is that when a homomorphic mapped type let x: { a?: number, b?: string } = "ouch"; We're perfectly happy with that because type |
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! |
We have a similar case and stumbled above array properties.
does not work well with this typecase:
as the infered result would be:
instead of:
Is there a way to express this recursion with array properties? |
I guess what I am asking for is a way to sometime opt out of classical structural typing. Sometimes, some APIs makes no sense if they are invoked with an Array instead of an Object even though it's also correct to pass en empty object. For instance if an object or an Array are semantically different (e.g a config/option object versus a children Array) and can either be in position 2 or 3 of an API. In that case, the implementation have to discriminate on whether we got an Object or an Array. {
reduceRight?: 'weReallyNeedThisToBeAnObjectLiteralAndNotAnArray'
//...other stuff
} But I don't think there are hacks like this one for every data types. Also, the issue of "non optional keys can be updated with null/undefined" remain I think. |
Tried the nightly. Making progress indeed. type UpdateSpec<T> = { [P in keyof T]?: T[P] & UpdateSpec<T[P]> };
declare function update<T>(obj: T, spec: UpdateSpec<T>): T;
declare let foo: { a: number, b: { x: string }, c?: string };
// These all work as expected
update(foo, { a: 1 });
update(foo, { b: { x: "test" } });
update(foo, { a: 10, b: { x: "howdy" } });
update(foo, { c: undefined });
// These are all errors as expected
update(foo, { a: "hello" });
update(foo, { a: null });
update(foo, { b: { x: 42 } });
update(foo, { b: { y: 10 } });
update(foo, { b: { y: 10 } });
update(foo, { b: "ouch" });
update(foo, { d: true });
// These should be errors but aren't
update(foo, "ouch");
update(foo, { a: undefined }); @ahejlsberg Do you think these last two errors can be fixed without too much trouble? |
@AlexGalays Does the object type help? Maybe: declare function update<T>(obj: T, spec: object & UpdateSpec<T>): T; at least prevents update(foo, "ouch"); |
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. |
What about when there is a property missing from a nested object? type UpdateSpec<T> = {[P in keyof T]?: T[P] & UpdateSpec<T[P]>};
declare function update<T>(obj: T, spec: UpdateSpec<T>): T;
interface IFoo {
a: number;
b: {x: string; y: number};
c?: string;
}
var foo: IFoo = {
a: 1,
b: {x: "s", y: 1},
};
// Should not error
update(foo, { a: 99, b: { y: 2 } }); but yields
|
should not it be |
Ah, you're right. Works with Partial, thanks! |
Hello and good job on the mapped types!
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.
The text was updated successfully, but these errors were encountered: