Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Remove read-only modifier for callable signature

Tags:

typescript

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?

like image 994
Estus Flask Avatar asked Aug 29 '20 10:08

Estus Flask


1 Answers

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
like image 144
TSR Avatar answered Nov 17 '22 05:11

TSR