TBH I am not sure if this is a bug with ts-toolbelt or a limitation of TS itself.
I have a case where I utilise O.Filter against a generic model.
For basic concrete implementations of the generic model everything works okay, however, if I have a kind of factory to produce various forms of the generic model then the typing breaks.
Given the following...
import { O } from 'ts-toolbelt';
// Filter out Actions - i.e. functions
type State<Model extends object> = O.Filter<Model, Function>;
type Action<Model extends object, T> = (
state: State<Model>,
payload: T,
) => void;
// Basic model with a generic value and setter
interface GenericModel<T> {
value?: T;
set: Action<GenericModel<T>, T>;
}
The following concrete implementation works
const concrete: GenericModel<number> = {
value: 1,
set: (state, payload) => {
// The state.value is correctly typed
// 馃帀
state.value = payload;
},
};
However, the following generic factory implementation fails
const genericFactory = <T>(): GenericModel<T> => {
return {
value: undefined,
set: (state, payload) => {
// The state.value does not exist
// 馃敟
state.value = payload;
},
};
};
FYI, if I don't use O.Filter and instead do something like hardcode Omit<Model, 'set'> then all works fine.
@ctrlplusb, I'm sorry about this :crying_cat_face:. I'm looking into it right now!
@ctrlplusb It's just so strange... If you use Select instead of Filter it allows set, normal (because it selects the functions). Now you must know that Filter and Select are strict opposites. So logically, if you swapped them (put Filter back), you should have value and not set. BUT IT'S NOT THE CASE. This just drives me mad because I thought it was because of the type inference for maybe 3H... I am starting to think that something is not working well internally (in TS) - because I tested with other types, and it's all good. And mostly, Filter works outside of your example - blow my mind.
But I'm sure I'll find something, but not today :laughing: so yhea, please just use Omit for now 馃く馃く馃く
No worries, thanks for looking into it irrespective - I know how complex and time consuming this stuff can be. 馃榾
I'll have a play around with an alternative solution in the interim.
@ctrlplusb, OK I've figured it out. This happens because the lib is stricter than it should. If you try:
import {A} from 'ts-toolbelt'
type Filter<O extends object, M> = {
[K in keyof O]: O[K] extends M ? never : O[K]
}
// Filter out Actions - i.e. functions
type State<Model extends object> =
Filter<Model, Function>
type Action<Model extends object, T> = (
state: A.Compute<State<Model>>,
payload: T,
) => void
// Basic model with a generic value and setter
type GenericModel<T> = {
value?: T;
set: Action<GenericModel<T>, T>;
}
const genericFactory = <T>(): GenericModel<T> => {
return {
value: undefined,
set: (state, payload) => {
state.value = payload
}
}
}
TypeScript will tell you that payload can't be assigned to state.value. Why? Because value might actually been filtered out because it could be a Function. Quite clever!
Now, the lib is a bit stricter and tells you that it does not exist. It actually swallowed the above error.
But in either case, you won't be able to achieve what you wanted. In fact, the field was not picked up because Filter already knew that it would not be possible. So the solution here is to re-include the value regardless of it's type:
import {A, O} from 'ts-toolbelt'
// Filter out Actions - i.e. functions
type State<Model extends object> =
O.Filter<Model, Function> & O.Pick<Model, 'value'>
type Action<Model extends object, T> = (
state: A.Compute<State<Model>>,
payload: T,
) => void
// Basic model with a generic value and setter
type GenericModel<T> = {
value?: T;
set: Action<GenericModel<T>, T>;
}
const genericFactory = <T>(): GenericModel<T> => {
return {
value: undefined,
set: (state, payload) => {
state.value = payload
}
}
}
Wow man, thanks for this detailed insight into how TS works. Super helpful in further developing my understanding of TS.
I really appreciate your time on this.
I think I have enough information and understanding to utilise an alternative strategy.
Thanks so much again.
Most helpful comment
Wow man, thanks for this detailed insight into how TS works. Super helpful in further developing my understanding of TS.
I really appreciate your time on this.
I think I have enough information and understanding to utilise an alternative strategy.
Thanks so much again.