TypeScript泛型包装函数转类方法时泛型推断异常解决办法
我正尝试为TypeScript实现类似Python和Rust的迭代器,采用函数式与面向对象Iter<T>类拆分的方式。目前已实现Python内置函数和itertools的多数功能,正在迭代器类上开发。但直接重定向所有方法调用到函数式实现过于重复机械,因此尝试通过遍历generators对象的键来动态挂载原型方法,却遇到泛型推断异常:所有泛型都被推断为unknown,例如调用new Iter([1,2,3,4]).enumerate()返回的res类型为Iter<[number, unknown]>,而函数式的enumerate签名明明是function* enumerate<T>(iter: Iterator<T>): Generator<[number, T]>;调用map方法时,参数i被推断为unknown,返回类型也是Iter<unknown>。
当前代码如下:
export type Iterator<T> = IterableIterator<T> | T[]; export function* enumerate<T>(iter: Iterator<T>): Generator<[number, T]> { let i = 0; for(const item of iter) { yield [i, item]; i++; } } export function* map<T, U>(iter: Iterator<T>, mapFn: (item: T) => U): Generator<U> { for(const item of iter) { yield mapFn(item); } } const generators = {enumerate, map}; type WrappedGenMethod<T, F extends (...args: any[]) => any> = F extends (iter: Iterator<T>, ...args: infer Args) => Generator<infer U> ? (...args: Args) => Iter<U> : never; /* methods for generators expected to be { enumerate: () => Iter<[number, T]>, map: <U>(mapFn: (item: T) => U) => Iter<U> } */ type Gen<T> = { [key in keyof typeof generators]: WrappedGenMethod<T, typeof generators[key]> } export class Iter<T> implements IterableIterator<T> { private inner: IterableIterator<T> private consumed = false; [Symbol.iterator] = () => this.inner; constructor(from: Iterator<T>) { this.inner = from[Symbol.iterator](); } next() { return this.inner.next(); } consume() { if (this.consumed) throw new Error("Iterator already consumed!"); this.consumed = true; } } export interface Iter<T> extends Gen<T> {} Iter.prototype.enumerate = function() { this.consume(); return new Iter(generators.enumerate(this)) } // Commented out since the compiler cannot determine T // Iter.prototype.map = function<U>(mapFn: (item: T) => U) { // this.consume(); // return new Iter(generators.map(this, mapFn)) // } const res = new Iter([1,2,3,4]).enumerate();
问题根源
TypeScript在处理对象中泛型函数的类型映射时,typeof generators[key]会丢失原函数的泛型参数化信息,导致WrappedGenMethod无法正确关联Iter<T>的泛型T与生成器函数的泛型参数,最终推断出unknown类型。
可行解决办法
1. 改进类型映射逻辑,保留泛型参数关联
修改WrappedGenMethod的定义,通过高阶类型处理泛型函数的包装,确保Iter<T>的泛型T能传递到生成器函数中:
// 通用包装工具:移除第一个iter参数,并将返回的Generator转为Iter type WrapGeneratorMethod<T, F> = F extends (...args: [Iterator<T>, ...infer Rest]) => Generator<infer U> ? (...args: Rest) => Iter<U> : never; // 重新定义Gen<T> type Gen<T> = { [K in keyof typeof generators]: WrapGeneratorMethod<T, typeof generators[K]> };
2. 为原型方法显式指定泛型约束
在挂载原型方法时,通过this参数约束和类型断言,强制TypeScript识别泛型参数:
// 修复enumerate方法 Iter.prototype.enumerate = function<T>(this: Iter<T>) { this.consume(); return new Iter(generators.enumerate(this)) as Iter<[number, T]>; }; // 修复map方法 Iter.prototype.map = function<T, U>(this: Iter<T>, mapFn: (item: T) => U) { this.consume(); return new Iter(generators.map(this, mapFn)) as Iter<U>; };
3. 动态挂载时自动关联泛型
如果需要批量挂载方法,可以借助类型工具批量生成正确的方法类型,再通过Object.assign挂载:
// 批量生成原型方法 const prototypeMethods = Object.fromEntries( Object.entries(generators).map(([key, fn]) => { return [key, function(...args: any[]) { this.consume(); return new Iter(fn(this, ...args)); }]; }) ) as { [K in keyof typeof generators]: Gen<unknown>[K] }; // 挂载到原型 Object.assign(Iter.prototype, prototypeMethods);
验证效果
修改后,new Iter([1,2,3,4]).enumerate()的返回类型会正确推断为Iter<[number, number]>,map方法的参数i会被推断为number,返回类型为Iter<number>。
内容的提问来源于stack exchange,提问作者OliverCWY

