TypeScript 函数声明在函数表达式内应继承父函数表达式的控制流缩小,

z5btuh9x  于 6个月前  发布在  TypeScript
关注(0)|答案(7)|浏览(66)

我正在尝试避免为外部接口上曾经是可选的属性编写空值检查,但后来在函数体上下文中被分配了默认值。我使用了类型保护,问题在于受保护的类型在一个函数中丢失,但在箭头函数中没有丢失,尽管它不能在类型保护之前提升。
我不想以类似的方式为每个函数参数引入新变量,也不想像 printMenu(options as DefinedOptions) 那样用类型转换调用其他函数。

TypeScript 版本: 3.7.3, 3.8.0-beta
搜索关键词:

typescript type guard function arrow function

代码

export interface Options {
  header?: string
  border?: boolean
  pageSize?: number
  helpMessage?: string
  showKeypress?: boolean
}

interface DefinedOptions extends Options {
  pageSize: number
  helpMessage: string
}

const isType = <T>(arg: any): arg is T => true

export default async function menu(options: Options) {

  options // Options

  options.pageSize = options.pageSize ?? 0
  options.helpMessage = options.helpMessage ?? 'Default message'
  if (!isType<DefinedOptions>(options)) return null

  options // DefinedOptions

  return new Promise((resolve, reject) => {

    function handleMenuKeypress(key: any) {
      options // Options  -  should be DefinedOptions

      printMenu(options) // error
    }

    const candleMenuKeypress = (key: any) => {
      options // DefinedOptions

      printMenu(options) // no error
    }
  })
}

function printMenu(options: DefinedOptions) {

}

Playground 链接

预期行为:

类型应该在两者中都是 DefinedOptions

实际行为:

函数中的类型是 Options,而箭头函数中的类型是 DefinedOptions

相关:

#10927

9cbw7uwe

9cbw7uwe1#

这是一个简化的示例:

interface A { s: string }
interface B extends A { t: boolean }

declare function isb(a: A): asserts a is B;

function foo(a: A) {
  isb(a);
  a; // B
  const f = () => {
    a; // B, since f is not hoisted and couldn't be called before its definition
  }
  function g() {
    a; // A, since definition of g is hoisted and could be called elsewhere
  }
  const h = () => {
    a; // B
    const hf = () => {
      a; // B
    }
    function hg() {
      a; // A, hg is hoisted but it couldn't be called ouside of h?
    }
  }
}

Playground
我认为在箭头函数内部的普通函数声明中,TypeScript过于悲观,并且 hg 应该具有与 h 相同的缩小范围,即使考虑到 #32300 中讨论的内容。

h4cxqtbf

h4cxqtbf2#

感谢简化版本@nmain100
这里唯一的错误是hg,考虑到它在CFA图中的位置,应该是B。函数表达式和函数声明的行为不同,因为一个会被提升,而另一个不会。

bttbmeg0

bttbmeg03#

这是相同的错误吗?

function somethingWentWrong(): boolean {
  let definitellyIsTrue: boolean = false;
  ['true'].forEach(() => { definitellyIsTrue = true });
  return definitellyIsTrue == true;
}

console.log(somethingWentWrong());

Playground链接
如果你在playground中运行它,你会发现错误:“由于类型'false'和'true'没有重叠,这个条件总是返回'false'。(2367)”
或者我需要为这个问题创建一个新的issue吗?

kdfy810k

kdfy810k4#

@AleksandrGilmanov 这是 #9998 的重复; typescript 不知道传递给 forEach 的函数在那个点被调用,如果所有的缩小操作在每次函数调用后都被悲观地重置,那么缩小操作基本上是无用的。

acruukt9

acruukt95#

另一个标准案例:

function foo(opts?: object) {
  opts = opts || {};
  opts; // object
  return () => opts; // object | undefined <- wrong
}
function bar(a?: string | object, opts?: object) {
  if (typeof a === 'object') [a, opts] = [undefined, a];
  a; // string | undefined
  return () => a; // string | undefined | object <- wrong
}

http://www.typescriptlang.org/play/?ts=3.8.0-dev.20200124&ssl=1&ssc=1&pln=10&pc=2#code/GYVwdgxgLglg9mABMOcAUcAOUDOB+ALkTgCMArAU2gEpEBvAKEWOx0QF4XdEAfH+gL4BuJlxxDEAeknFyVKKIBOFKCEVI0tdgD4xE6bMrReicABMKwGGApnEAHgC0iAO6KEAcwYCGoSLAREEgBDRTRgwkQcKEVrDxNSIygAGjFIxPlaRmYYYEQ0KABPTAo4POCOdk4AcgzoatoAbWDUrFwAXQ5ERvNLa1tU4PaRZmD9GWjYsHj+XqsbMyUVNQ0tXTGpCZi4kzn+u346qAdnN09vIA

l7mqbcuq

l7mqbcuq6#

你好!
有人能看一下 this playgroud 吗?我在文件底部留下了关于错误的适当评论。我的这个问题似乎部分与此相关,尽管它不是关于函数类型(函数声明或函数表达式)。
我应该创建一个单独的问题还是它们是相关的?
提前感谢。

nuypyhwy

nuypyhwy7#

其他一些更新可能已经部分解决了这个问题,因为我在4.5.4版本上得到了以下结果:

function workingExample<T>(
    arg: unknown,
    validator: (arg: unknown) => asserts arg is T,
    callme: (arg: T) => void
): void {
    validator(arg);
    const fn = () => {
        callme(arg); // no error. arg: T
    };
}

这似乎是原始问题。然而,当Assert一个对象的成员时,它仍然失败,但非常令人困惑。类型注解来自VSCode悬停在TS 4.5.4上的提示:

function nonworkingExample<T>(
    arg: {member: unknown},
    validator: (member: unknown) => asserts member is T,
    callme: (member: T) => void
): void {
    validator(arg.member);
    callme(arg.member); // no error. on hover -> arg: {member: unknown}, but arg.member: T ???
    const fn = () => {
        callme(arg.member); // error. arg.member: unknown
    };
}

编辑:目前找到了一个暂时可行的解决方法,我觉得还不错:

function woraround<T>(
    arg: {member: unknown},
    validator: (member: unknown) => asserts member is T,
    callme: (member: T) => void
): void {
    const member = arg.member;
    validator(member);
    callme(member); // no error. member: T
    const fn = () => {
        callme(member); // no error. member: T
    };
}

相关问题