Выведите тип возвращаемого значения из параметра и карты

У меня есть следующие типы. Он неправильно выводит returnType. Ожидается, что cityAttractions должен иметь ошибку типа в своем операторе возврата, поскольку он возвращает строку вместо массива строк.

машинописная площадка

interface Tool<RESULT = any> {
  returnType?: RESULT;
}

type ToolCallWithInferredReturnType<ToolsRecord extends Record<string, Tool>> = {
  [ToolName in keyof ToolsRecord]: ToolsRecord[ToolName]['returnType']
}

export type OnToolCall<ToolsRecord extends Record<string, Tool> = Record<string, Tool>> = 
  (args: { toolCall: { toolName: string } }) => ToolsRecord extends Record<string, Tool>
    ? ToolCallWithInferredReturnType<ToolsRecord>[typeof args['toolCall']['toolName']] 
    : void | Promise<unknown> | unknown;

type Tools = {
  weather: {
    returnType: string;
  }
  cityAttractions: {
    returnType: string[];
  }
}

export const onToolCall: OnToolCall<Tools> = ({ toolCall }) => {
  if (toolCall.toolName === 'weather') {
    return 'Weather information was shown to the user.';
  }
  if (toolCall.toolName === 'cityAttractions') {
    return "Attractions was shown to the user."; // type error should be throw here
  }

  throw Error("")
}

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

Brother58697 20.08.2024 09:00

Спасибо за отзыв! Я удалил типы параметров zod иtool.

Sentient 20.08.2024 18:45
unknown — топовый тип в ТС. Любой союз с unknown — это просто unknown. Вы написали что-то вроде T[K]['returnType'] | (void | Promise<unknown> | unknown), но это всего лишь unknown. Зачем вам это там, если вы не хотите принять все ценности?
jcalz 20.08.2024 18:55

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

jcalz 20.08.2024 19:01

Я стремился к чему-то подобному в JavaScript ToolsRecord[ToolName]['returnType'] || (void | Promise<unknown> | unknown). Используйте returnType, если он существует, в противном случае просто предположите, что он есть (void | Promise<unknown> |known). Альтернативно (void | Promise<returnType> | returnType) также должно работать.

Sentient 20.08.2024 19:02

Итак, соответствует ли эта версия вашим потребностям? Вам нужен общий термин, поскольку в вашей версии typeof args['toolCall']['toolName'] — это многословный способ сказать string. И вам нужно переписать реализацию для TS, чтобы проверить ее тип; вы не можете использовать серию операторов if/else для проверки дженериков (без реализации чего-то вроде ms/TS#33014). Если это полностью отвечает на вопрос, я могу написать ответ; если нет, то чего не хватает?

jcalz 20.08.2024 20:01

Кажется близко, но, похоже, не работает с такой функцией, как const onToolCall: OnToolCall<Tools> = ({ toolCall }) => {...}

Sentient 21.08.2024 00:04

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

jcalz 21.08.2024 00:38

Попробуйте эту ссылку. Он выдает ошибку, когда он неверен и когда он также должен работать.

Sentient 21.08.2024 06:05

Хм, я обратился к этому в своем комментарии: «И вам нужно переписать реализацию TS для проверки типа; вы не можете использовать серию операторов if/else для проверки дженериков (без чего-то вроде ms/TS#33014) реализуется)". Это недостающая функция в TypeScript. Я что-то упускаю?

jcalz 21.08.2024 14:46

Неа. Если это так, то я думаю, это зависит от функции, реализованной в TypeScript, и это должно быть ответом.

Sentient 22.08.2024 18:18
Зод: сила проверки и преобразования данных
Зод: сила проверки и преобразования данных
Сегодня я хочу познакомить вас с библиотекой Zod и раскрыть некоторые ее особенности, например, возможности валидации и трансформации данных, а также...
Как заставить Remix работать с Mantine и Cloudflare Pages/Workers
Как заставить Remix работать с Mantine и Cloudflare Pages/Workers
Мне нравится библиотека Mantine Component , но заставить ее работать без проблем с Remix бывает непросто.
Угловой продивер
Угловой продивер
Оригинал этой статьи на турецком языке. ChatGPT используется только для перевода на английский язык.
TypeScript против JavaScript
TypeScript против JavaScript
TypeScript vs JavaScript - в чем различия и какой из них выбрать?
Синхронизация localStorage в масштабах всего приложения с помощью пользовательского реактивного хука useLocalStorage
Синхронизация localStorage в масштабах всего приложения с помощью пользовательского реактивного хука useLocalStorage
Не все нужно хранить на стороне сервера. Иногда все, что вам нужно, это постоянное хранилище на стороне клиента для хранения уникальных для клиента...
Что такое ленивая загрузка в Angular и как ее применять
Что такое ленивая загрузка в Angular и как ее применять
Ленивая загрузка - это техника, используемая в Angular для повышения производительности приложения путем загрузки модулей только тогда, когда они...
0
11
58
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Чтобы это работало, вам нужно, чтобы OnToolCall<T> была универсальной функцией, поэтому тип возвращаемого значения OnToolCall<T> может зависеть от типа подсвойства toolName в аргументе. Этого не происходит, просто ссылаясь на тип типа typeof args['toolCall']['toolName']; если args относится только к типу { toolCall: { toolName: string} }, то этот тип — многословный способ сказать string. От таких дженериков не уйти.

Допустим, тип этого свойства toolName является параметром универсального типа K , ограниченным до keyof T. Тогда OnToolCall<T> следует определить следующим образом:

type OnToolCall<T extends Record<string, Tool>> =
  <K extends keyof T>(args: { toolCall: { toolName: K } }) =>
    ToolCallWithInferredReturnType<T>[K];

Теперь вы можете убедиться, что вызывающие абоненты видят желаемое поведение:

type Tools = {
  weather: {
    returnType: string;
  }
  cityAttractions: {
    returnType: string[];
  }
}

let onToolCall: OnToolCall<Tools> = ⋯;

const strArr = onToolCall({ toolCall: { toolName: "cityAttractions" } });
//    ^? const strArr: string[]
const str = onToolCall({ toolCall: { toolName: "weather" } });
//    ^? const str: string

Так что это выглядит хорошо.


Но учтите, что вы не можете реализовать onToolCall так, как делали раньше, по крайней мере, без утверждения типа :

onToolCall = ({ toolCall }) => { // error!
//~~~~~~~~ <-- Type 'string[] | string' is not assignable to 
//  type 'ToolCallWithInferredReturnType<Tools>[K]'
  if (toolCall.toolName === 'weather') {
    return 'Weather information was shown to the user.';
  }
  if (toolCall.toolName === 'cityAttractions') {
    return ["Attractions was shown to the user."]; 
  }
  throw Error("")
}

TypeScript не может использовать сужение потока управления (например, блоки if/else) для воздействия на параметры универсального типа. Вы можете проверить toolCall.toolName === 'weather', и это сузит toolCall.toolName, но это вообще не повлияет на параметр универсального типа K. Таким образом, TypeScript не может проверить, что string можно назначить ToolCallWithInferredReturnType<Tools>[K].

В microsoft/TypeScript#33014 уже давно существует запрос на открытую функцию, позволяющую этому работать, и есть некоторый прогресс в его реализации, но в настоящее время (начиная с TypeScript 5.5) это не является частью языка.

Возможно, в какой-нибудь будущей версии TypeScript приведенный выше код начнет работать как есть. Прямо сейчас, если вы хотите написать функцию, которая возвращает общий индексированный тип доступа, вам нужно будет реализовать ее путем индексации в объект. Так:

onToolCall = ({ toolCall }) => ({
  weather: 'Weather information was shown to the user.',
  cityAttractions: ["Attractions was shown to the user."]
}[toolCall.toolName]); // okay

По сути, это то же самое, что и предыдущая реализация, но здесь выполняется поиск вместо if/else. И если вы реализуете его неправильно, вы получите ожидаемую ошибку:

onToolCall = ({ toolCall }) => ({
  weather: 'Weather information was shown to the user.',
  cityAttractions: "Attractions was shown to the user."
}[toolCall.toolName]) // error!
//   Type 'string' is not assignable to type 'string[]'.    

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

onToolCall = ({ toolCall }) => {
  const t: ToolCallWithInferredReturnType<Tools> = {
    weather: 'Weather information was shown to the user.',
    cityAttractions: "Attractions was shown to the user." // error!
    //~~~~~~~~~~~~~ <--
    // Type 'string' is not assignable to type 'string[]'.
  }
  return t[toolCall.toolName]
}

Детская площадка, ссылка на код

Спасибо, что помогли мне отладить эту проблему с TypeScript!

Sentient 23.08.2024 19:39

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