maninak / ts-xor

Compose object types containing mutually exclusive keys, using this generic Typescript utility type.
https://app.radicle.at/nodes/seed.radicle.at/rad:z3nP4yT1PE3m1PxLEzr173sZtJVnT
MIT License
106 stars 5 forks source link

Typescript cannot infer properties are not undefined upon reference #21

Closed dminkovsky closed 4 years ago

dminkovsky commented 4 years ago

Not sure if this is a bug. This type works as intended for assignment. However:

interface Code {
    code: string;
}

interface Token {
    token: string;
}

type CodeOrToken = XOR<Code, Token>;

const codeOrToken : CodeOrToken = {
    code: 'code'
}

declare function doSomethingWithCode(code: Code) {}

declare function doSomethingWithToken(token: Token) {}

function doSomething(codeOrToken: CodeOrToken) {
    codeOrToken.token
    ? doSomethingWithToken(codeOrToken)
    : doSomethingWithCode(codeOrToken); <-- TS error
}

The last line has the TS error:

Argument of type '(Without<Code, Token> & Token) | (Without<Token, Code> & Code)' is not assignable to parameter of type 'Code'.
  Type 'Without<Code, Token> & Token' is not assignable to type 'Code'.
    Types of property 'code' are incompatible.
      Type 'undefined' is not assignable to type 'string'.

Likewise:

declare function doSomethingWithCode(code: string) {}

declare function doSomethingWithToken(token: string) {}

function doSomething(codeOrToken: CodeOrToken) {
    codeOrToken.token
    ? doSomethingWithToken(codeOrToken.token)
    : doSomethingWithCode(codeOrToken.code); <-- Error
}

Produces the error:

Argument of type 'string | undefined' is not assignable to parameter of type 'string'.
  Type 'undefined' is not assignable to type 'string'.

Can anything be done about this?

dminkovsky commented 4 years ago

Actually, looks like it works if I write a user-defined type guard:

declare function doSomethingWithCode(code: Code) {}

declare function doSomethingWithToken(token: Token) {}

function isCode(codeOrToken: CodeOrToken) : codeOrToken is Code {
    return !!codeOrToken.code;
}

function doSomething(codeOrToken: CodeOrToken) {
    isCode(codeOrToken)
    ? doSomethingWithCode(codeOrToken)
    : doSomethingWithToken(codeOrToken);
}

No errors.

dminkovsky commented 4 years ago

Thanks a lot for this packaging this up! :)