Нарушает ли этот пример машинописного текста принцип подстановки Лискова?

У меня такой код:

type T = { foo: string }
var t: T = { foo: 'foo' }

interface S { foo: string }
var s: S = t

Итак, мы знаем, что T < S.

Как насчет этого?

t = s

Итак S < T тоже верно.

Мы можем подразумевать, что S == T.

Теперь представим U:

type U = { [key: string]: string }
var u: U = t

Так T < U. Все идет нормально.

Но ждать!

u = s // Error!

Похоже, это нарушает принцип замены Лискова (LSP):

если S является подтипом T, то объекты типа T могут быть заменены объектами типа S

Это нарушение LSP? Имеет значение, есть это или нет?

Если отбросить принципы, это выглядит довольно глупо:

u = s    // Error!
u = <T>s // Ok!

Будет ли это считаться ошибкой? Конечно, компилятор мог сделать это сам, нет?

1 ответ

Решение

Система типов TypeScript местами не работает; вы обнаружили эту проблему, при которой псевдонимы типов, но не интерфейсы, получают неявные подписи индекса. Предоставление типу неявной подписи индекса полезно, но в целом небезопасно. Рассмотреть возможность:

const fooBar = { foo: "foo", bar: 123 };
const tFooBar: T = fooBar; // okay
const uFooBar: U = tFooBar; // okay?
const whoopsie = uFooBar.bar; // string at compile time, number at runtime?!
console.log(whoopsie);

Значение fooBar действительный T, потому что у него foo свойство типа string. Таким образом, вы можете назначить егоtFooBar. А поскольку TypeScript позволяет вам присваивать значение типаT к переменной типа U, вы можете назначить tFooBar к uFooBar. И вот теперь несостоятельность обнаруживается, если вы читаетеbar свойство uFooBar. Это должно бытьstring согласно с U, но это number. Ой.

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

По-видимому, текущее правило для TypeScript:

  • объектным литералам / анонимным типам присваиваются неявные индексные подписи
  • псевдонимам типов даются неявные подписи индекса
  • интерфейсам НЕ даются неявные индексные подписи

По-видимому, последнее намеренно, а не ошибка, согласно этому комментарию @RyanCavanaugh:

Просто чтобы заполнить людей, это поведение в настоящее время задумано. Поскольку интерфейсы могут быть расширены дополнительными объявлениями, а псевдонимы типов - нет, то "безопаснее" (в кавычках) вывести неявную подпись индекса для псевдонимов типов, чем для интерфейсов. Но мы рассмотрим возможность сделать это и для интерфейсов, если это будет иметь смысл.

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

Хорошо, надеюсь, что это поможет; удачи!

Ссылка на код

Другие вопросы по тегам