Как определить predicate на методе класса?

Речь о typescript predicates и неверном поведении типов в некоторых встроенных объектах JS.

Если самостоятельно определить predicate, TypeScript "помнит" о каком типе идет речь и соответственно не вызывает ошибок ...

const isString = (s: unknown): s is string => typeof s === 'string'
const isBoolString = (s: unknown): boolean => typeof s === 'string'

function foo(something: unknown) {
  if (isString(something)) something.replace('a', '')
  if (isBoolString(something)) something.replace('a', '')
  // "Правильная" ошибка ----- ^^^^^^^^^
}

... но вариант с методами экземпляров, например с Map<K, V>, определенный по умолчанию в "lib.es2015.collection.d.ts", не "помнит" что строкой выше мы уже удостоверились в наличии свойства и вызов Map.get() не должен возвращать undefined:

function bar<K, V>(key: K, map: Map<K, V>): V | 123 {
  if (map.has(key)) {
    return map.get(key) // V | undefined
    // ^^^^^^^^^^^^^^^
  }
  return 123
}

Хотелось бы переопределить некоторые типы, т.е. должно получится что-то вроде такого:

interface Map<K, V> {
  has(key: K): false | is V
}

Playground

И ваши варианты с методами кастомного класса...


Ответы (1 шт):

Автор решения: Alexander Lonberg

Такой метод определения predicate не желает работать с "переменной типа"

interface Map<K, V> {
  // Извлекаем тип
  has<T extends K>(key: T): this is (
    // Извлекаем метод
    Omit<Map<K, V>, 'get'> &
    { // Переопределяем на момент вызова "predicate | has"
      get<R extends K>(k: R):
        // При вызове get(), проверяем ранее извлеченный тип T
        R extends T ? V : (V | undefined)
    }
  )
  get(key: K): V | undefined
}

Вариант ниже работает

const map = new Map<string, number>()

const v1 = map.get('key') // number | undefined

if (map.has('key')) {
  // Оба варианта работают как и ожидается
  const v2 = map.get('key')   // number
  const v3 = map.get('error') // number | undefined
}

... но если тип заранее неизвестен, опять ошибка

function foo<K, V>(key: K, m: Map<K, V>): V | 123 {
  if (m.has(key)) {
    return m.get(key)
    // error ^^^^^^^^
  }
  return 123
}

Playground

→ Ссылка