GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — Proposal: Add an "exclusive or" (^) operator · Issue #14094 · microsoft/TypeScript

Proposal: Add an "exclusive or" (^) operator #14094

New issue
Copy link
New issue
Copy link
Closed
Closed
Proposal: Add an "exclusive or" (^) operator#14094
Copy link
Labels
DeclinedThe issue was declined as something which matches the TypeScript visionThe issue was declined as something which matches the TypeScript visionSuggestionAn idea for TypeScriptAn idea for TypeScript

Description

@mohsen1
Mohsen Azimi (mohsen1)
opened on Feb 15, 2017
Issue body actions

Based on this comment TypeScript does not allow exclusive union types.

I'm proposing a logical or operator similar to union (|) or intersection (&) operators that allows defining types that are one or another.

Code

type Person = { name: string; } ^ { firstname: string; lastname: string; };

const p1: Person = { name: "Foo" };
const p2: Person = { firstname: "Foo", lastname: "Bar" } ;

const bad1: Person = { name: "Foo", lastname: "Bar" }
                                    ~~~~~~~~~~~~~~~   Type Person can not have name and firstname together 
const bad2: Person = { lastname: "Bar", name: "Foo" }                                            
                                        ~~~~~~~~~~~   Type Person can not have lastname and name together

For literal and primitive types it should behave like union type:

// These are the same 
type stringOrNumber = string | number;
type stringORNumber = string ^ number;
Reacted by Matt Banz, Raphael Ferreira, Aliaksei Tuzik, Christian, Luke Scott, Igor Ovsiannikov, EliSnow, Sylvain Estevez, Riley Avron, Scandiravian and 124 more

Activity

Next
  1. mhegazy commented on Feb 15, 2017

    @mhegazy
    Mohamed Hegazy (mhegazy)
    on Feb 15, 2017
    Contributor
    More actions

    The issue is there are no exact/final types in TS. all types are open ended. so this is allowed from an assignable perspective:

    var p1: {name: string };
    var p2 =  {name:"n", firstName: "f", lastName: "l"};
    p1 = p2;  // OK

    A type is assignable to a union type, iff it is assignable to one of the constituents. so even with the exclusive union, open types would allow that check to pass.

    Another feature that TS has is flagging "unknown" properties. This only applies to object literals, that happen to have a contextual type. e.g.:

    var p: {name: string} = { name: "n", another: "f" }; // Error `another` is not a known property

    This check is simplified for union types, just to say it has to be a "known" property on the union in general, not for this constituent. The main issue here is how unions are compared, when we are comparing constituent types, we do not know if we should check for "unknown" properties, because it might be one of the other types.

    If i am not mistaken, you are after this unknown property check in this case. I would say we are better off trying to change how this is handled for unions, rather than creating a new concept in the language that users need to understand.

    Related discussion in #12997. Issue tracked by #12745

    Reacted by Kris Kaczor
  2. mohsen1 commented on Feb 16, 2017

    @mohsen1
    Mohsen Azimi (mohsen1)
    on Feb 16, 2017
    ContributorAuthor
    More actions

    #12745 will solve parts of this problem. But it won't have exclusive or logic because tagged unions are merging assignable types. For example:

    interface A { foo: string; }
    interface B { bar: string; }
    
    type C = A | B;
    
    const a: C = { foo: '' } // Wrongfully ✔ // Hopefully #12745 fixes this
    const b: C = { bar: '' } // Wrongfully ✔ // Hopefully #12745 fixes this
    const c: C = { foo: '', bar: '' } // ✔

    What I'm suggesting is an exclusive or operator between two interfaces:

    interface A { foo: string; }
    interface B { bar: string; }
    
    type C = A ^ B;
    
    const a: C = { foo: '' } // ✔
    const b: C = { bar: '' } // ✔
    const c: C = { foo: '', bar: '' } // ❌
    Reacted by Aadit Olkar, Anders Engan and Denis Pipchenko
  3. mhegazy commented on Feb 16, 2017

    @mhegazy
    Mohamed Hegazy (mhegazy)
    on Feb 16, 2017
    Contributor
    More actions

    without exact types the new operator does not solve the issue, since { foo: '', bar: '' } is still a { foo: string; }

    Reacted by Kris Kaczor
  4. mukhuve commented on Mar 10, 2017

    @mukhuve
    Manuel Rivera (mukhuve)
    on Mar 10, 2017
    More actions

    This would be really helpful, Allows better management in development time.

  5. Ryan Cavanaugh (RyanCavanaugh)
    added
    In DiscussionNot yet reached consensusNot yet reached consensus
    SuggestionAn idea for TypeScriptAn idea for TypeScript
    on Mar 10, 2017
  6. RyanCavanaugh commented on Mar 10, 2017

    @RyanCavanaugh
    Ryan Cavanaugh (RyanCavanaugh)
    on Mar 10, 2017
    Member
    More actions

    Presumably the behavior here would be that

    type A = { m: T } ^ { n : U };

    is equivalent to

    type A = { m: T, n: undefined } | { m: undefined, n : U };
    Reacted by Ian MacLeod, John Reilly, Raphael Ferreira, Kevin Ryan, Kris Kaczor, Mariusz Pawelski, Harold Nyikal, Kamil Baran, Igor «InoY» Zviagintsev, SlurpTheo and 17 more
    Reacted by Ricardo Fernández Serrata
  7. mohsen1 commented on Mar 10, 2017

    @mohsen1
    Mohsen Azimi (mohsen1)
    on Mar 10, 2017
    ContributorAuthor
    More actions

    Ryan Cavanaugh (@RyanCavanaugh) it makes it even easier to implement as de-sugar algorithm is so easy!

    This operator is still a good idea since there are lots of cases where an API expect two or more completely different interfaces.

    Adding undefined can pile up quickly:

    type A = { m: T } ^ { n:  U } ^ { o: Q }

    vs.

    type A = 
        { m: T; n: undefined; o: undefined; } |
        { m: undefined; n: U; o: undefined; } |
        { m: undefined; n: undefined; o: Q; }
    Reacted by hirokihokari, Bolutife Lawrence, Anders Engan, Igor Sukharev, A and artfox3
  8. RyanCavanaugh commented on May 11, 2017

    @RyanCavanaugh
    Ryan Cavanaugh (RyanCavanaugh)
    on May 11, 2017
    Member
    More actions

    Search terms so I can find this later: "mutually exclusive" "disjoint unions"

    Reacted by Jan Steinke and gearcoded
    Reacted by camelaissani, Jan Steinke and gearcoded
  9. battmanz commented on Jun 2, 2017

    @battmanz
    Matt Banz (battmanz)
    on Jun 2, 2017
    More actions

    I'd like to add another use case for this feature. I'm currently using json-schema-to-typescript to generate TypeScript interfaces from JSON Schema. Currently the "oneOf" keyword is listed as not expressible in TypeScript. Having exclusive unions would then make it expressible.

    Reacted by Maxime Quandalle, Mohsen Azimi, Christian, Adam Duren, Sylvain Estevez, Patrick Lienau, Kunal Shah, Bradly Schlenker, Kyle Mitofsky, Chloe Rice and 1 more
    Reacted by Ricardo Fernández Serrata
  10. lukescott commented on Nov 15, 2017

    @lukescott
    Luke Scott (lukescott)
    on Nov 15, 2017
    More actions

    With this example:

    type A = { m: T } ^ { n:  U } ^ { o: Q }
    // ->
    type A = 
        { m: T; n: undefined; o: undefined; } |
        { m: undefined; n: U; o: undefined; } |
        { m: undefined; n: undefined; o: Q; }

    This actually doesn't work:

    // Assume T, U, and Q are string:
    let a = { // <-- error
      m: ""
    }

    Because it expects:

    // Assume T, U, and Q are string:
    let a = {
      m: "",
      n: undefined, // <-- expects undefined value
      o: undefined,  // <-- expects undefined value
    }

    You have to define it like this:

    type A = 
        { m: T; n?: undefined; o?: undefined; } |
        { m?: undefined; n: U; o?: undefined; } |
        { m?: undefined; n?: undefined; o: Q; }

    But that allows you to put n: undefined on the object, which makes the key enumerable. So far I've found this works out better:

    type A = 
        { m: T; n?: never; o?: never; } |
        { m?: never; n: U; o?: never; } |
        { m?: never; n?: never; o: Q; }

    I wish there was an ^ operator. I've been bitten by thinking | was supposed to be an exclusive OR. Thinking about bitwise it makes sense. But I almost always want ^ instead of |.

    Reacted by Calvin, Kevin Ryan, Igor «InoY» Zviagintsev, SlurpTheo, Sean G. Wright, Roman Bielte, monkeytao, Tarık İNCE, Hudo Assenco, Slava and 13 more
  11. krryan commented on Mar 15, 2018

    @krryan
    Kevin Ryan (krryan)
    on Mar 15, 2018
    More actions

    I'm all for the ^ sugar, that's great.

    But I also think TS needs to recognize and leverage mutually-exclusive union patterns, regardless of the sugar. For example, { kind: 'foo'; } | { kind: 'bar'; } is mutually exclusive because kind cannot possibly be 'foo' and 'bar' simultaneously. Likewise with literal number types. And naturally, { m: T; n?: never; } | { m?: never; n: U; } under discussion as a de-sugared { m: T; } ^ { n: U; } is mutually exclusive as well. None of these types is mutually exclusive because of any specific syntax, they just are exclusive by their very nature.

    But TS doesn't recognize or leverage that fact as strongly as it could. For examples, #20375 and #21879 could benefit from recognizing mutually exclusive unions to allow for narrowing (that would not be safe with mutually inclusive unions). So I think this request should be more than just sugar. I would expect something like declare const test: { kind: 'foo'; } & { kind: 'bar'; } to result in test: never, but it doesn't, and TS will happily allow you to pass test to anything that's expecting a { kind: 'foo'; } or anything that's expecting a { kind: 'bar'; }.

  12. RyanCavanaugh commented on Mar 15, 2018

    @RyanCavanaugh
    Ryan Cavanaugh (RyanCavanaugh)
    on Mar 15, 2018
    Member
    More actions

    We already turn unit type contradictions ("foo" & "bar") into never during union/intersection type distribution.

    Going further than that is somewhat dangerous because it means we'd produce never in a way that was fundamentally un-debuggable - imagine intersection two types and deep inside two uninteresting properties conflict and the whole thing collapses to never and you'd have no way to figure out why.

    Also, sometimes you want to intersect two types in a way where you're using the part of it that isn't contradictory.

    Reacted by SlurpTheo and craigphicks-public
  13. krryan commented on Mar 15, 2018

    @krryan
    Kevin Ryan (krryan)
    on Mar 15, 2018
    More actions

    I agree with the debugging issue; having a way to investigate type inference would be really useful in general but I imagine that would be very difficult to offer.

    But I still think never is the correct type for this case. Omit could be used, perhaps, to specify that you weren't interested in that bit that conflicts; certainly on our project, that would be an expectation that we'd have of our coders, to be explicit about something like that.

    Finally, even if you really want to maintain { kind: 'foo'; } & { kind: 'bar'; }, test.kind should still have the type never. It currently has the type 'foo' & 'bar', which is, again, impossible.

  14. Griffork commented on Mar 16, 2018

    @Griffork
    Griffork
    on Mar 16, 2018
    More actions

    It seems like this operator should result in closed types rather than open types like the union operator.

  15. 35 remaining items

  16. yangliguo7 commented on Aug 24, 2023

    @yangliguo7
    YangLGggggggggg (yangliguo7)
    on Aug 24, 2023
    More actions

    so Is there a best practice for implementing type now ?

    the discussion is too long.

    we can use ^ now ? Ryan Cavanaugh (@RyanCavanaugh) or use XOR like Mohsen Azimi (@mohsen1) said ?

    Reacted by Martin Johns, Kostis Maninakis and Jonathan MASSUCHETTI
  17. maninak commented on Aug 28, 2023

    @maninak
    Kostis Maninakis (maninak)
    on Aug 28, 2023
    More actions

    If you ended up here from Google, I've packaged up a working solution complete with tests and documentation and published it on npm as ts-xor.

    (shameless plug blah blah but I've ended up needing this way too often and thought I'd make a neat shareable solution I wouldn't feel bad introducing as a dependency at work)

    EDIT: ts-xor now supports XORing together up to 200 types, which will hopefully meet the needs that some community members couldn't satisfy with the solutions shared in this issue until now.

    Reacted by Menezes, Andrew Ferrazzutti, Igor «InoY» Zviagintsev, Lorenz Leutgeb, Pierre Cavin, Bryan Hoang, Igor Sukharev, Oleksandr Danylchenko and Kostis Maninakis
    Reacted by Menezes, Keegan Witt, Pierre Cavin and Alexey Berezin
    Reacted by Amit Beckenstein
  18. shane-js commented on Jun 23, 2024

    @shane-js
    Shane (shane-js)
    on Jun 23, 2024
    More actions

    I was recently led here indirectly when asking on Stackoverflow about this functionality. It's roughly a year or so since the last comment so I figured I'd voice one.

    It feels that from an end user perspective the ability to say something is "one of these types" in a native built-in way would be extremely helpful. This stackoverflow post of mine lays out a straight forward use case example: https://stackoverflow.com/q/78656695/5117487. It could possibly have better intellisense than options you will see suggested such as ts-essentials XOR or ExclusifyUnion custom type def from https://stackoverflow.com/a/46370791/5117487.

    I've read through the comments and as someone on the newer side of learning typescript I was wondering if it can be shared if there is technical limitations that block this from being native to TS? Or even design philosophical reasons why it should not be included? Not so as to put pressure on why it does not exist today but I think it is what I see missing from this thread thus far in what mostly became driven by external library solution talks.

  19. MartinJohns commented on Jun 23, 2024

    @MartinJohns
    Martin Johns (MartinJohns)
    on Jun 23, 2024
    Contributor
    More actions

    I've read through the comments and as someone on the newer side of learning typescript I was wondering if it can be shared if there is technical limitations that block this from being native to TS?

    This would not prevent from the additional properties to exist, because additional properties are allowed to exist. It would only work for excess property checks and object literals, which is not really what the people want. An operator like this would only really make sense when we have Exact Types (#12936).

    Reacted by Claudia Meadows and Igor «InoY» Zviagintsev
  20. shane-js commented on Jun 24, 2024

    @shane-js
    Shane (shane-js)
    on Jun 24, 2024
    More actions

    Thanks for the reply Martin Johns (@MartinJohns)

    This would not prevent from the additional properties to exist

    I don't follow this part - the linked "ExclusifyUnion" suggestion (very popularly pointed to online) and ts-essentials XOR does stop the properties from existing. You can see this in this playground.

    Maybe you are referring to one of the few suggested approaches in this issue specifically? I personally am not suggesting one specific one (I am at this point too ignorant of under the hood of TS to be making specific implementation suggestions yet).

    Though I could be misunderstanding depending on how you are distinguishing between blocking "additional properties ... exist[ing]" and "excess property checks". My current level of understanding interprets that as the same thing? From all the stackoverflow posts / blog posts I've read on this it seems to be exactly what many people want (blocking extra properties) so again not sure what you are referencing when you say it is not what the people want.

    I read through the Exact Types suggestion you linked and yes I do think (from what I can tell) that it is one way to solve this. Though you say "when we have" but I don't get any stronger sense from reading all the comments there that it is committed to versus something like proposed in any of the comments here (except for of course that this one was closed).

    Reacted by Klaas Van Parys
  21. nitzcard commented on Jul 17, 2024

    @nitzcard
    Nitzan Bar (nitzcard)
    on Jul 17, 2024
    More actions

    here in 2024 and I would be happy for it.

    Reacted by Paweł Stolarski, Michael Barney, Jr, Robin Andersson, Tomoe, Bortnikov Pavel, Alex Crișan, Elliot Huffman and artfox3
    Reacted by Claudia Meadows, Martin Johns and Kostis Maninakis
  22. Qsppl commented on Jan 21, 2025

    @Qsppl
    Tomoe (Qsppl)
    on Jan 21, 2025
    More actions

    here in 2025 and I would be happy for it)

    Reacted by Igor «InoY» Zviagintsev, Bortnikov Pavel, Domi, Alex Crișan, Elliot Huffman and artfox3
    Reacted by Kostis Maninakis, Martin Johns and Claudia Meadows
  23. maninak commented on Jan 21, 2025

    @maninak
    Kostis Maninakis (maninak)
    on Jan 21, 2025
    More actions

    Instead of posting incremented comments check out ts-xor.

    Reacted by Bortnikov Pavel, Andrew Baxter, Elliot Huffman and artfox3
  24. inoyakaigor commented on Jan 21, 2025

    @inoyakaigor
    Igor «InoY» Zviagintsev (inoyakaigor)
    on Jan 21, 2025
    More actions

    Kostis Maninakis (@maninak) I don't agree. If this issue will be active it's going to show maintainers it's still relevant

  25. darkbasic commented on Jan 21, 2025

    @darkbasic
    Niccolò Belli (darkbasic)
    on Jan 21, 2025
    More actions

    To show it's active we need more partecipants/thumbs up, not more +1 comments that will only lead to the issue being eventually locked.

    Reacted by Kostis Maninakis
  26. inoyakaigor commented on Jan 21, 2025

    @inoyakaigor
    Igor «InoY» Zviagintsev (inoyakaigor)
    on Jan 21, 2025
    More actions

    In general you're right but even comments «+1» it better than nothing

    Reacted by Ryan Cavanaugh, Claudia Meadows, Martin Johns and Kostis Maninakis
  27. RyanCavanaugh commented on Jan 21, 2025

    @RyanCavanaugh
    Ryan Cavanaugh (RyanCavanaugh)
    on Jan 21, 2025
    Member
    More actions

    https://github.com/microsoft/TypeScript/wiki/FAQ#what-kind-of-feedback-are-you-looking-for

    Reacted by Martin Johns
  28. tonyxiao commented on Jan 27, 2025

    @tonyxiao
    Tony Xiao (tonyxiao)
    on Jan 27, 2025
    More actions

    What is inconsistent about typescript's behavior is the following

    type Person = { name: string; } | { firstname: string; lastname: string; };
    const ok: Person = { name: "Foo", lastname: "Bar" } // no type error
    const notOk: Person = { name: "Foo", lastname: 123 } // Type 'number' is not assignable to type 'string'
    

    Why is that? Couldn't { name: "Foo", lastname: 123 } be considered a type of { name: string } where lastname is an excess property? Why does it also check against type of { firstname: string; lastname: string; }?

  29. dead-claudia commented on Jan 27, 2025

    @dead-claudia
    Claudia Meadows (dead-claudia)
    on Jan 27, 2025
    More actions

    Tony Xiao (@tonyxiao) Are you running suppressExcessPropertyErrors: true? If so, that's expected behavior right now, as it satisfies the first type of the union (and it only needs to satisfy one part of the union to satisfy the union). The chances of that changing are zero, as a lot of code depends on it in other ways (like just assigning to subtypes in general).

    If not, I suggest searching for an existing issue (this isn't it) and, if none exist, filing a new bug. In that case, the TS compiler is incorrectly allowing properties it shouldn't be. (Could be it checking the union of all property names instead of just the names that apply to the types matched, could be it just not checking them at all.)

  30. RyanCavanaugh commented on Jan 27, 2025

    @RyanCavanaugh
    Ryan Cavanaugh (RyanCavanaugh)
    on Jan 27, 2025
    Member
    More actions

    Excess properties are allowed when they appear elsewhere in a union, because when we implemented excess property checking, we found that a huge number of type definitions were extremely sloppy about when it was actually a disjoint union vs just an optional property.

    Reacted by Claudia Meadows
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    DeclinedThe issue was declined as something which matches the TypeScript visionThe issue was declined as something which matches the TypeScript visionSuggestionAn idea for TypeScriptAn idea for TypeScript

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions

      You are visitor 01402503 — GeezerVison does not store the pages you look up.
      Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.