Нарушает ли этот пример машинописного текста принцип подстановки Лискова?
У меня такой код:
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 и упомянуть о ней.
Хорошо, надеюсь, что это поможет; удачи!