Skip to content

allow narrowing to any sub-type-expression in type guards #6544

Closed

Description

Currently X & Y | Z cannot be asserted as being X or Y separately in custom type guards (only X & Y together), however there seem to be no reason why they can't be. #6538

Activity

  1. RyanCavanaugh commented on Jan 19, 2016

    @RyanCavanaugh
    Member

    Just as a starting point for things we need in the proposal, there's no such thing as "partial assignability".

  2. zpdDG4gta8XKpMCd commented on Jan 19, 2016

    @zpdDG4gta8XKpMCd
    Author

    I am lacking proper terminology sorry. Out of all possible combinations of types A, ..., Z using operations of union and intersection a type guard asserting a value being of a separate constituent type should be allowed:

    function isZ(value: (A | B) & D & (C | (E & F & G) | H) | (I & J)....): value is Z {

    In other words: anything that, according to the type being assert, is possible should be able to be the target type of a custom type guard.

  3. RyanCavanaugh commented on Jan 19, 2016

    @RyanCavanaugh
    Member

    I think what I'm hearing is that if you have a parameter whose type is an intersection type, the type predicate's type must be assignable to any one constituent of the intersection type?

    For example:

    // OK because A is assignable to (A | B)
    declare function f1(x: (A | B) & C): x is A
    
    // Error
    declare function f2(x: (A | B) & C): x is number;
  4. zpdDG4gta8XKpMCd commented on Jan 19, 2016

    @zpdDG4gta8XKpMCd
    Author

    to sure I got it... not only intersection, but union too:

    declare function f1(x: (A | B) & C): x is A; // ok
    declare function f2(x: (A | B) & C): x is B; // ok
    declare function f3(x: (A | B) & C): x is C; // ok
    

    here! I got it: any sub-type-expression (as long as we talk unions and intersections) of the type-expression being asserted against should be a valid target of a type guard:

    declare function f1(x: (A | B) & C): x is A | B; // ok
    declare function f1(x: (A | B) & C): x is (A | B) & C; // ok
    
  5. RyanCavanaugh commented on Jan 19, 2016

    @RyanCavanaugh
    Member

    You don't need a special rule for union types because it's already the case that A is assignable to A | B

  6. zpdDG4gta8XKpMCd commented on Jan 19, 2016

    @zpdDG4gta8XKpMCd
    Author

    Because narrowing is what type guards do! 😜

  7. yortus commented on Jan 20, 2016

    @yortus
    Contributor

    Currently X & Y | Z cannot be asserted as being X or Y separately in custom type guards (only X & Y together), however there seem to be no reason why they can't be.

    Any value that satisfies (X & Y) | Z must satisfy at least one of the following:

    1. it has all the members of Z
    2. it has all the members of X AND all the members of Y

    A value of type Z satisfies (1), so narrowing to Z makes sense.

    However a value of type X satisfies neither (1) because it doesn't have all the members of Z, nor (2) because it doesn't have all the members of Y. Ditto for Y.

    Isn't that the reason why X & Y | Z can't be asserted to X or to Y (but can to Z)?

  8. zpdDG4gta8XKpMCd commented on Jan 20, 2016

    @zpdDG4gta8XKpMCd
    Author

    Troy Gerwien (@yortus) I understand the mechanics of how it works now and you are right in what you saying. I am questioning the very premise that narrowing can't go beyond a member of a union type. I see no reason why it can't. What I am suggesting is that narrowing should be allowed to any sub-expression including lone terms.

  9. yortus commented on Jan 20, 2016

    @yortus
    Contributor

    Aleksey-Bykov maybe I get what you are saying, but I'm not sure. Do you mean that narrowing/type assertion should follow assignability rules sometimes, like:

    declare function f1(x: (A & B)): x is A; // error by narrowing rules
    
    var ab: A & B;
    var a: A = ab; // ok by assignability rules
  10. zpdDG4gta8XKpMCd commented on Jan 20, 2016

    @zpdDG4gta8XKpMCd
    Author

    yes function f1(x: (A & B)): x is A; should be allowed as well as function f1(x: (A & B)): x is A & B;

  11. yortus commented on Jan 20, 2016

    @yortus
    Contributor

    So if you want to narrow a variable of type X & Y | Z using a guard, so that you can treat it like an X in a guarded clause, then isn't it sufficient to just narrow it to X & Y? You can already treat a variable of type X & Y as if it were an X, no narrowing needed. Are there cases where this doesn't work?

  12. yortus commented on Jan 20, 2016

    @yortus
    Contributor

    The guard functions would be interesting:

    function isX(val: X&Y): val is X {
        // ALWAYS TRUE: every value that satisfies X&Y also satisfies X
        return true;
    }
  13. zpdDG4gta8XKpMCd commented on Jan 20, 2016

    @zpdDG4gta8XKpMCd
    Author

    then isn't it sufficient to just narrow it to

    in your example it is

    Are there cases where this doesn't work?

    this is what brought it up in the first place (#6538)

    type V = (X | Y) & Z | W
    function isZ(v: V): v is Z {
    }

    The guard functions would be interesting

    Exactly, it's just a degenerate case.

  14. yortus commented on Jan 20, 2016

    @yortus
    Contributor

    So why not do this instead:

    type V = (X | Y) & Z | W;
    function isZ(v: V): v is (X | Y) & Z {...}
    
    if (isZ(val)) {
        // here we can treat val like a Z
    }
  15. zpdDG4gta8XKpMCd commented on Jan 20, 2016

    @zpdDG4gta8XKpMCd
    Author

    yes, you can do this way, the major points are:

    • the clarity is hurt, I will be asked by my peers why a function called isZ asserts to (X | Y) & Z
    • more isn't always good, when it comes to refactoring for example, I don't want my peers to use and rely on the extra information that wasn't meant to be there
    • having to type more than necessary
    • there is no reason for not doing it right (besides the budget)
  16. yortus commented on Jan 20, 2016

    @yortus
    Contributor

    Fair point. So while allowing narrowing X&Y to X seems pointless in the simple case (like 'narrowing' Dog to Animal), with more complex type expressions it makes type guards much clearer without diminishing type safety.

  17. added and removed
    Needs ProposalThis issue needs a plan that clarifies the finer details of how it could be implemented.
    on Jan 21, 2016
  18. mhegazy commented on Feb 24, 2016

    @mhegazy
    Contributor

    Type guard returns must be a subtype because they introduce new properties in their guarded expressions, not remove them

  19. locked and limited conversation to collaborators on Jun 19, 2018
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 visionIn DiscussionNot yet reached consensusSuggestionAn idea for TypeScript

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions