Там, где требуется функция, машинопись позволяет мне передавать объект с несовместимым свойством `apply` - PullRequest
0 голосов
/ 27 апреля 2019

В настоящее время используется машинопись 3.4.5 с включенным режимом strict ...

Предыстория

Я просто столкнулся с ситуацией, когда машинопись не защитила меня от моих собственных ошибок, к сожалению. И я пытаюсь выяснить, почему машинописный текст не смог поймать эту ошибку.

Я писал объявление типа для такой функции:

function acceptVisitors (visitor) {
    visitor.apply(value);
}

Проницательные наблюдатели могут указать, что тип visitor может быть определен одним из двух способов - как функция или как объект со свойством apply:

type visitorType = (this: IValue) => void;
// or
type visitorType = {
    apply: (value: IValue) => void;
};

Оказывается, в моем случае это был последний. После добавления объявления типа я приступил к написанию этого неправильного кода:

// This is incorrect because it doesn't pass it as an argument.
// Rather, the `this` context is set to the value.
acceptVisitors((value: IValue) => { ... });

Теперь, загадкой является то, что Typescript не показывал ошибку, когда я передавал функцию, тип которой был несовместим с visitorType.

Упрощенный пример

Давайте изменим тип параметра на строку и пройдемся по ней.
Я определяю тип с именем func, который является функцией, требующей строкового аргумента.

type func = (param1: string) => void;

Функции по своей природе являются вызываемыми объектами, которые также имеют метод apply.

declare let f: func;
f.apply(undefined, ['str']);
// all good

Теперь вот другой тип - объект со свойством apply.

type objectWithApplyProp = {
    apply: (param1: string) => void;
};

Мы можем вызвать свойство apply, но не так ...

declare let o: objectWithApplyProp;
o.apply(undefined, ['str']); // Error: Expected 1 arguments, but got 2.

И objectWithApplyProp имеет подпись вызова, которая не работает с func:

o.apply('str'); // ok
f.apply('str'); // Error: The 'this' context of type 'func' is not assignable to 
                // method's 'this' of type '(this: string) => void'

И дальнейшие тесты показывают, что f присваивается o, но не наоборот, что имеет смысл ... все функции являются объектами, но не все объекты могут быть вызваны.

Но почему f считается присваиваемым o? Тип objectWithApplyProp требует значения apply, которое соответствует определенного типа , а func не соответствует

Подпись apply функции должна быть выведена из ее параметров, но машинописный текст, по-видимому, не выводит ее.

Итак, любые отзывы приветствуются. Я не прав или есть ограничение в Typescript? Это известная проблема? Спасибо

1 Ответ

0 голосов
/ 29 апреля 2019

Итак, это техническая причина, по которой это происходит, и обходной путь:

Встроенный в Typescript файл lib / es5.d.ts определяет файл Function.apply с параметрами типа any. Также он определяет Function.prototype как `any.

interface Function {
    apply(this: Function, thisArg: any, argArray?: any): any;
    call(this: Function, thisArg: any, ...argArray: any[]): any;
    bind(this: Function, thisArg: any, ...argArray: any[]): any;
    toString(): string;
    prototype: any;
    readonly length: number;
    // Non-standard extensions
    arguments: any;
    caller: Function;
}

И я полагаю, что все функциональные выражения имеют тип Function по умолчанию.

Таким образом, функции было разрешено назначаться объекту с несовместимым свойством apply, поскольку у функции не было строго типизированного метода apply, основанного на встроенных типах Function. Поэтому машинопись не может определить, что подписи apply были разными.

Typescript 3.2 представляет CallableFunction , которая имеет общие аргументы в своем объявлении apply. Но я так и не понял, как это исправить.

Обходной путь - определить более сильный тип функции и вручную назначить его функции. Обходной путь немного утомителен, но работает.

interface func extends Function {
    (param1: string): void;
    // manually define `apply
    apply<T, R> (thisArg: T, args: [string]): R;
}
interface objectWithApplyProp { // unchanged
    apply: (param1: string) => void;
}
// Now we have proper errors here:
o = f;
// Type 'func' is not assignable to type 'objectWithApplyProp'.
//  Types of property 'apply' are incompatible.
//    Type '<T, R>(thisArg: T, args: [string]) => R' is not assignable to type '(param1: string) => void'. ts(2322)
f = o;
// Type 'objectWithApplyProp' is missing the following properties from type 'func': call, bind, prototype, length, and 2 more. ts(2740)
...