У меня есть следующие типы. Он неправильно выводит 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("")
}
Спасибо за отзыв! Я удалил типы параметров zod иtool.
unknown
— топовый тип в ТС. Любой союз с unknown
— это просто unknown
. Вы написали что-то вроде T[K]['returnType'] | (void | Promise<unknown> | unknown)
, но это всего лишь unknown
. Зачем вам это там, если вы не хотите принять все ценности?
см. предыдущий комментарий. Если бы мне пришлось переписать это, чтобы оно работало, я бы сделал это похоже на эту версию , но там нет unknown
. Пожалуйста, отредактируйте, чтобы мы не использовали void
и unknown
, и тогда мы посмотрим, можно ли это ответить.
Я стремился к чему-то подобному в JavaScript ToolsRecord[ToolName]['returnType'] || (void | Promise<unknown> | unknown)
. Используйте returnType, если он существует, в противном случае просто предположите, что он есть (void | Promise<unknown> |known). Альтернативно (void | Promise<returnType> | returnType) также должно работать.
Итак, соответствует ли эта версия вашим потребностям? Вам нужен общий термин, поскольку в вашей версии typeof args['toolCall']['toolName']
— это многословный способ сказать string
. И вам нужно переписать реализацию для TS, чтобы проверить ее тип; вы не можете использовать серию операторов if
/else
для проверки дженериков (без реализации чего-то вроде ms/TS#33014). Если это полностью отвечает на вопрос, я могу написать ответ; если нет, то чего не хватает?
Кажется близко, но, похоже, не работает с такой функцией, как const onToolCall: OnToolCall<Tools> = ({ toolCall }) => {...}
Я не понимаю... моя ссылка показывает, что она работает. В чем конкретно проблема? ...
не помогает мне понять.
Попробуйте эту ссылку. Он выдает ошибку, когда он неверен и когда он также должен работать.
Хм, я обратился к этому в своем комментарии: «И вам нужно переписать реализацию TS для проверки типа; вы не можете использовать серию операторов if
/else
для проверки дженериков (без чего-то вроде ms/TS#33014) реализуется)". Это недостающая функция в TypeScript. Я что-то упускаю?
Неа. Если это так, то я думаю, это зависит от функции, реализованной в TypeScript, и это должно быть ответом.
Чтобы это работало, вам нужно, чтобы 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!
Если вы хотите большего вовлечения в вопрос, вам следует значительно упростить его до минимального примера, в котором есть ошибка.