简体   繁体   中英

Typescript union type consistency

Having a Typescript variable of union type A

type A = {
    b: true
    x: number
  } | {
    b: false
    x: string
  }

declare const v: A

i can properly assign property x to correct type, by checking for property b value type with an if discriminant block to protect type A consistency

if (v.b) {  // v.x is number

  // ok for compiler 
  v.x = 3   

  //  compiler error as v.x should be number 
  v.x = ''

} else { // v.x is string

  //  compiler error as v.x should be string 
  v.x = 3   

  // ok for compiler 
  v.x = ''  
}

outside discriminant block vx correctly appears to be number | string number | string
however, compiler doesn't complain assigning x to number | string number | string despite that would break type A consistency

v.x = 3   // ok for compiler 
v.x = ''  // ok for compiler 

Is there a way to force the compiler to reject this?
check it out on typescriptlang.org/play

在您的情况下,通用类型别名的代码示例: 在typescriptlang.org/play上查看

Okay, so I think I've found the canonical GitHub issue about this: microsoft/TypeScript#14150 , a suggestion that "unsafe type-incompatible assignments should not be allowed". It's still an open issue (as of 2019-09-13) marked as "awaiting more feedback", so if you think you have a compelling use case that's not already mentioned in there you might want to comment in there. I wouldn't hold my breath waiting for this to be implemented, though, since the related issues like enforcing readonly strictness via flag flag and enabling variance annotations are either closed or haven't been acted upon.

The problem here involves type system's lack of soundness . A sound type system would only let you do safe things. But here it lets you make a property assignment to an object that might violate the object's declared type. This unsafe permissiveness means the type system is unsound . That, by itself, is not considered a bug. It is not one of TypeScript's design goals to "apply a sound or 'provably correct' type system". There is a tradeoff between correctness and productivity, and it is quite possible that fixing this issue might be more trouble than it's worth. See microsoft/TypeScript#9825 for more discussion about TypeScript's soundness and/or lack thereof.

The particular unsoundness here: the compiler assumes that it is safe to write the same type to a property that you can read from it. This is not true in general, as shown in your example, and in this related example from the linked issue :

interface A { kind: "A"; foo(): void; }
interface B { kind: "B"; bar(): void; }

function setKindToB(x: A | B): void {
    x.kind = "B"; // clearly unsafe
}

So what can be done? Not sure. TypeScript 3.5 introduced a change to indexed access writes (such as foo[bar] = baz ) so that if the key is of a union type (say bar is Math.random()<0.5 ? "a" : "b") then you must write the *intersection* of the property types to it, not the *union* (so the type of baz must be typeof foo.a & typeof foo.b and will no longer accept typeof foo.a | typeof foo.b`). This is a soundness improvement that prohibits some invalid things which were previously allowed. And it also prohibits lots of valid things which were previously allowed. And lots of people are still upset about it and new issues about it are still filed fairly frequently. I imagine the same problem would happen here if they fixed this issue... you would get the error you expect, and lots of code bases would break. For now I'd say you should probably just avoid doing these assignments, which I understand is not much consolation.

Anyway, hope that information is of some use to you. Good luck!

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM