-
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
Represent the types of function parameters that mutate inside the function. #22865
Comments
Modelling the behavior of I am not sure we can model side effects in any useful way throughout the system, nor do we want to try. |
I can't comment on the feasibility of this idea but I know I have wanted it before. I like creating type-safe builder style interfaces in typescript but currently the only way I know of to avoid losing the types is to make sure you always chain the builder calls and if you need to store the intermediate stages of a builder, always ensure it is stored in a fresh variable. Example: interface Builder<Props = {}> {
addProp<T extends string, V>(prop: T, val: V): Builder<Props & Record<T, V>>
finish(): Props
}
declare function newBuilder(): Builder
const builderA = newBuilder()
builderA.addProp("a", "mystring")
builderA.addProp("b", 123)
const resultA = builderA.finish() // unfortunately resultA has type {}
let builderB = newBuilder()
builderB = builderB.addProp("a", "mystring")
builderB = builderB.addProp("b", 123)
const resultB = builderB.finish() // resultB also has type {}
const builderC1 = newBuilder()
const builderC2 = builderC1.addProp("a", "mystring")
const builderC3 = builderC2.addProp("b", 123)
const resultC = builderC3.finish() // Now has type {a: string, b: number} as desired
const resultD = newBuilder()
.addProp("a", "mystring")
.addProp("b", 123)
.finish() // also has type {a: string, b: number} as desired In a perfect world it'd be nice if there was some way to model stateful changes like |
I updated with a new example and a little bit more detailed explanation. |
@kpdonn I think what you want is support for linear / affine types, you're basically encoding it by hand currently. |
Right now some kind of let x: Array<any> | number;
...
if (x instanceof Array) {
// here x changes from Array|number to Array
} else {
// here x changes from Array|number to number
} Couldn't that feature be used for this new purpose? |
Another way of implementing this could be by adding a keyword that suggests that the function will modify the type of a parameter. We could borrow the function Special(out obj) {
obj['foo'] = () => {};
}
const bar = {};
Special(out bar);
bar.foo(); // ts-ok, js-ok In the function scope an function Fail(out obj) {
obj = {}; // ts-error assigning to const
}
let obj = {};
Fail(out obj); The function Fail(out obj) {}
let obj = {};
Fail(obj); // ts-error overload error, no overload takes a single "none out" argument We could then explicitly type the function Special<T>(out obj: T then T & { foo(): void } ) {
obj['foo'] = () => {};
}
const bar = {};
Special(out bar);
bar.foo(); // ts-ok, js-ok I would just like to mention that you can achieve something close to this by "abusing" the typegurad feature. function Special<T>(obj: T): obj is T & { foo(): void; } {
obj['foo'] = () => {};
return true;
}
const bar = {};
if (!Special(bar)) throw 'Will never happen';
bar.foo(); // ts-ok, js-ok |
I've been sorely missing this 'feature' in a project of mine, which is a refactor of an old JS project where the primary mechanism of data transfer is an object that is passed from function to function and mutated in each. Typing that has been a nightmare to the point where I've found the easiest solution is to declare a type with all possible added properties marked as optional. My code is now filled with non-null assertions, but I've concluded that Typescript just isn't designed for this. Object mutation goes against the spirit of static types. |
Just encountered this issue as well (typing stateful builders), rather disappointed to find out that it's not really possible to do so without chaining the calls together. Does seem unlikely that this will be supported in TypeScript anytime soon as the original issue mutating function parameters is mostly covered by the function merge<T>(x: unknown, y: T): asserts x is (typeof x) & T {
Object.assign(x, y);
}
const x = { a: 1 };
merge(x, { b: 2 });
// No error now!
console.log(x.b); |
Related issues:
About a year ago I encountered the need for this, but wasn't motivated to engage at the time. I recently considered opening a new issue to cover a specific angle of this (return type), but I can't justify doing that yet — at this point, I think this issue is on the same topic. I'd like to see a new kind of return type: one that asserts the mutation of a mutable argument — and also returns that value. Essentially, this is an assertion function which — instead of returning In the example below, both function assertDeletesFooPropAndAddsBarProp<T extends object>(
obj: T,
): asserts obj is Omit<T, "foo"> & { bar: string } { /*
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
A type predicate's type must be assignable to its parameter's type.
Type 'Omit<T, "foo"> & { bar: string; }' is not assignable to type 'T'.
'Omit<T, "foo"> & { bar: string; }' is assignable to the constraint of type 'T',
but 'T' could be instantiated with a different subtype of constraint 'object'.(2677) */
delete (obj as { foo?: unknown }).foo;
(obj as { bar: string }).bar = "bar";
return obj; /*
~~~~~~
Type 'T' is not assignable to type 'void'.
Type 'object' is not assignable to type 'void'.(2322) */
}
const obj = { foo: "foo" };
//^? const obj: { foo: string; }
const theSameObjAfterMutation = assertDeletesFooPropAndAddsBarProp(obj);
//^? const theSameObjAfterMutation: void
obj;
//^? const obj: { foo: string; } Perhaps the return type could be something like this, which uses function assertDeletesFooPropAndAddsBarProp<T extends object>(
obj: T,
): asserts obj as Omit<T, "foo"> & { bar: string }; But don't let that suggestion hinder reception of this message — this is about the concept, not bikeshedding syntax. This could be used to solve #32253, among other mutations. |
In JavaScript is possible to mutate objects inside functions. Right now, the following code in JavaScript:
Can't be written in TypeScript without casting the type. There are a few options whose type definition is wrong in all scenarios I can think of (maybe I'm missing a better option):
Option 1
Option 2
Suggestion
There could be an extension to function parameter definition like the following:
There, we indicate that whatever type was x before, now it is something different. The code above could be written in TypeScript as follows:
Another example
That could be syntax sugar for this:
Syntax
It could be something like:
With the identifiers being different, and with the types being mandatory an extension of Object.
The text was updated successfully, but these errors were encountered: