TypeScript 对于解构的区分联合体,控制流分析不起作用

wecizke3  于 6个月前  发布在  TypeScript
关注(0)|答案(4)|浏览(46)

Bug报告

🔎 搜索词

控制流分析,解构赋值

🕗 版本与回归信息

这个已经存在了,但我认为在4.6中引入这个功能时可能已经修复了。

⏯ Playground链接

带有相关代码的Playground链接

💻 代码

export const reduce = <S, T extends S>(self: Iterable<T>, operation: (acc: S, t: T) => S): S => {
  const iter = self[Symbol.iterator]();
  let { value, done }: IteratorResult<T, unknown> = iter.next();
  if (done) throw new Error("Empty iterable can't be reduced.");
  let acc: S = value; // Type 'unknown' is not assignable to type 'S'.
  while (!done) {
    acc = operation(acc, value);
    ({ value, done } = iter.next());
  }
  return acc;
};

请注意,如果你不向上转型 iter.next() ,你将不会得到错误,但这并不是出于好的原因:

let {value, done} = iter.next(); // type is IteratorResult<T, any>
if (done) throw new Error("Empty iterable can't be reduced.");
// value is any and can be assigned to anything

一个更小的仓库是:

export function foo(iter: Iterator<number, string>) {
  let { value, done } = iter.next();
  if (done) return;
  let acc: number = value; // Type 'string | number' is not assignable to type 'number'. Type 'string' is not assignable to type 'number'.
  return acc;
}

🙁 实际行为

上面最后一个例子中的 value 被推断为 number | string ,它应该是 number

🙂 预期行为

我希望有与没有解构相同的推断:

export const reduce2 = <S, T extends S>(self: Iterable<T>, operation: (acc: S, t: T) => S): S => {
  const iter = self[Symbol.iterator]();
  let result: IteratorResult<T, unknown> = iter.next();
  if (result.done) throw new Error("Empty iterable can't be reduced.");
  let acc: S = result.value; // result.value is T (extends S)
  while (!result.done) {
    acc = operation(acc, result.value);
    result = iter.next();
  }
  return acc;
};
wdebmtf2

wdebmtf21#

  1. 你忘记填写用于提交bug报告的问题模板。
  2. 对于解构类型的CFA需要推断类型,但你明确地设置了类型。从你的变量声明中删除 : IteratorResult<T, unknown> 部分,它就可以正常工作了。
iswrvxsc

iswrvxsc2#

好的,对不起,我会填写它。但是,留下IteratorResult<T, unknown>的bug并没有被修复,请参阅我的最后一条评论,它只是不会报错,因为它被推断为任意类型,而不是未知类型(默认情况下第二个泛型参数是任意类型)。
你也可以尝试:

function foo(iter: Iterator<number, unknown>) {
  let { value, done } = iter.next();
  if (done) throw new Error("Empty iterable can't be reduced.");
  let acc = value; // unknown, not number
}
hmtdttj4

hmtdttj43#

我还有一个例子:

export const last = <T>(self: Iterable<T>): T => {
  let { found, last }: { found: false; last?: undefined } | { found: true; last: T } = {
    found: false,
  };
  for (const it of self) {
    ({ found, last } = { found: false, last: it });
  }
  if (!found) throw new NoSuchElementException("Iterable contains no element matching the predicate.");
  // can not infer last to be T
  return last;
};

@RyanCavanaugh@MartinJohns

q5iwbnjs

q5iwbnjs4#

A较短的constlet。示例取自#46266

type Action =
    | { kind: 'A', payload: number }
    | { kind: 'B', payload: string };

function f11(action: Action) {
    const { kind, payload } = action; // works
    if (kind === 'A') {
        payload.toFixed();
    }
    if (kind === 'B') {
        payload.toUpperCase();
    }
}

function _f11(action: Action) {
    let { kind, payload } = action; // doesn't work
    if (kind === 'A') {
        payload.toFixed();
    }
    if (kind === 'B') {
        payload.toUpperCase();
    }
}

相关问题