A function initially doesn't allow to change read-only properties (e.g. name in ES6):
let foo = function (n: number) {
return n;
}
foo.name = 'not foo'; // Cannot assign to 'name' because it is a read-only property
In order to work this around, Writable utility type from the reference was used:
type Writable<T> = {
-readonly [K in keyof T]: T[K];
};
1. Read-only name is not affected by an intersection:
let writableFoo: typeof foo & { name: string } = foo;
writableFoo.name = 'not foo'; // Cannot assign to 'name' because it is a read-only property
2. Writable doesn't get name from function type and is not callable:
let writableFoo: Writable<typeof foo> = foo;
writableFoo.name = 'not foo'; // Property 'name' does not exist on type 'Writable<(n: number) => number>'
writableFoo(1); // This expression is not callable
3. Writable gets name from Function but is still not callable:
let writableFoo: Writable<Function> = foo;
writableFoo.name = 'not foo';
writableFoo(1); // This expression is not callable
4. Omit uses index signature and is not callable either:
let writableFoo: Omit<typeof foo, 'name'> & { name: string } = foo;
writableFoo.name = 'not foo';
writableFoo(1); // This expression is not callable
The objective here is to type writableFoo to keep writableFoo callable and allow name to be changed, preferably without modifying other properties with Writable. It doesn't try to solve a specific coding problem but investigate specified type issues.
Why does 1 not affect readonly modifier by intersection type?
Why does 2 not get name despite it's recognized for typeof foo as foo.name?
How can 2-4 get call signature while removing readonly modifier from name?
With this you can both call and write name
interface Foo extends Function{
name: string
}
const d: Foo = function(){}
d.name ='not foo'
d()
Thanks. It technically solves the problem but Function ignores the signature of the function, d('should cause TS error', 'for unexpected args') (this wasn't mentioned in the question but seems to be a reasonable requirement). Also const itself is able to remove the error because it's treated differently than let
type Incrementer = (x: number)=>string
interface Foo extends Incrementer{
name: string
}
let d: Foo = (x)=>'a'+x
d.name ='not foo'
d(1)
d('s') // string not expceted must be number
If you want a general answer, here is your Writable
interface Writable<T extends (...args: any) => any> {
(...arg: Parameters<T>): ReturnType<T>;
name: string
}
type Incrementer = (x: number, y: boolean) => string
let d: Writable<Incrementer> = (x, y) => 'a' + x + y
d.name = 'not foo' // no error
d(1, true) // no error
d('d', true) // 'd' is not assignable to number
d(2, 1) // 1 is not assignable to boolean
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With