TypeScript ts2365允许不安全的比较:string | number < string | number

3df52oht  于 4个月前  发布在  TypeScript
关注(0)|答案(4)|浏览(52)

🔎 搜索词

ts2365
关系比较
二元运算符 <
二元运算符 >
二元运算符 <=
二元运算符 >=
TypeScript 5

🕗 版本与回归信息

⏯️ Playground链接

链接 — 运行并查看日志

💻 代码

const show = (a: any, ltResult: boolean, b: any) =>
    `${JSON.stringify(a)}${ltResult ? ' < ' : ' >='}${JSON.stringify(b)}\t`

// These two show TS2365 error on '<' operators (TS 5.2.2),
// but the allowed arg combinations actually always compared numerically:
const lt1 = (a: string | number, b: number         ) => show(a, a < b, b)
const lt2 = (a: number         , b: string | number) => show(a, a < b, b)

// These three TS 5.2.2 does NOT complain, but are unsafe!
// They allow string<string which compares lexicographically AND 
// one string one number which which compare numerically
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Less_than#description
const lt3 = (a: string | number, b: string         ) => show(a, a < b, b)
const lt4 = (a: string         , b: string | number) => show(a, a < b, b)
const lt5 = (a: string | number, b: number | string) => show(a, a < b, b)

// Only including calls allowed by argument types:

console.log(`lt1: ${lt1(12, 9)} ${lt1('12', 9)}`)
console.log(`lt2: ${lt2(12, 9)}                 ${lt2(12, '9')}`)
// lt3-5 are 100% TS-clean but mix true/false results depending on run-time types!
console.log(`lt3:                             ${lt3(12, '9')} ${lt3('12', '9')}`)
console.log(`lt4:             ${lt4('12', 9)}                 ${lt4('12', '9')}`)
console.log(`lt5: ${lt5(12, 9)} ${lt5('12', 9)} ${lt5(12, '9')} ${lt5('12', '9')}`)

🙁 实际行为

lt1–2 给出错误 Operator '<' cannot be applied to types 'string | number' and 'number'.,反之亦然。
字符串与数字的比较实际上会将双方都转换为数字,因此这些比较始终是数值性的 — 运行Playground并查看日志,您会发现这里始终说 12 >= 9。
lt3-5 的例子反而没有TS错误,但允许一个字符串 < 字符串的情况!
只有在 *双方都是字符串(或将它们转换为原始字符串作为字符串,尽管有数字提示)的情况下,JS才会进行字典序字符串比较。
您会在Playground日志中看到这些返回混合的 < 和 >= 结果,具体取决于它们在运行时是否都是字符串或至少有一个数字。💥
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Less_than#description, https://262.ecma-international.org/#sec-islessthan

🙂 预期行为

lt3: Operator '<' cannot be applied to types 'string | number' and 'string'.
lt4: Operator '<' cannot be applied to types 'string' and 'string | number'.
lt5: Operator '<' cannot be applied to types 'string | number' and 'string | number'.

关于此问题的其他信息

我认为 lt5 情况是最“有趣”的,因为两边的类型相同,从类型系统的Angular 来看,它们都是 < 可以处理的有效子集。
特别是,这就是您编写代码以允许 string < stringnumber < number 在运行时执行的方式,这两种组合都是完全合法的。
但字典序与数值比较是语义上不同的操作(恰好共享相同的运算符),实际上您在编写代码时通常只希望其中一个含义。(参见 #49661,其中询问的是 val + val。那里的答案是它与类型有关而不是身份,但我认为更根本的答案是您的意思是要么加法要么连接。)
我不知道关于更广泛的类型(如 any < any)应该怎么说 — 同样的论点适用于您只想表达其中一个含义的情况,但强制执行这一点将破坏TS逐步类型化的初衷。cc @Andarist@RyanCavanaugh

iswrvxsc

iswrvxsc1#

  • 引发此问题的实时应用故事:

我们有一些表单字段,用户在其中输入数字,有时我们会将字符串/数字表示混合使用(JS代码库逐渐转换为TS...)
因此,现在我们有很多验证需要执行string | number。大多数验证是e.g. value >= 1,而TS 5开始警告这种类型,但实际上是安全的——另一方面,我们有一个min_field <= max_field验证,可能非常危险,并可以从错误中受益!
(嗯,其实min_field和max_field现在被指定为any,因为验证器需要allValues: object,而使用redux-form和Formik进行每个字段的类型标注更加繁琐;我们还没有尝试过这个“高悬果实”;在处理完类型为allValues之前,我们可能会收紧我们的NodesInput组件以存储单个已知类型;但是想象一下一个较小的应用程序,验证接近具体字段的情况。)

thtygnil

thtygnil2#

我认为lt5案例是最"有趣"的,因为两边的类型相同,从类型系统的Angular 来看,它们是可处理的有效子集。
左侧参数可以是string,右侧参数可以是number

nafvub8i

nafvub8i3#

是的,我关于lt5被认为是“有趣”的文章实际上是我在扮演反面角色。你是对的,“两边的类型相同”是一种有意义的手势。
我的观点是,值得阻止的风险行为不仅仅是混合string < number——虽然这些类型定义得很好,但风格很差,因为人们必须查找它们将如何执行——而是可以解析为运行时进行字典或数字比较的类型。

  • lt5合法使用案例:

一个通用的排序函数,可以接受any[]string[] | number[]等参数,可以合理地定义为使用“自然JS < 排序顺序”:一个要按数字排序的数字数组,一个按字典序排序的字符串数组,运行时选择将是已知的。
这样的一个函数将在某个地方进行类似lt5的比较!
在我看来,这种情况足够罕见,以至于需要用户停下来思考并添加ts-ignore吗?

lp0sw83n

lp0sw83n4#

在之前的讨论中,尝试修复了#52807

相关问题