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