Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Closed
zpdDG4gta8XKpMCd opened this issue Jan 19, 2016 · 17 comments
Closed

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

zpdDG4gta8XKpMCd opened this issue Jan 19, 2016 · 17 comments
Labels
Declined The issue was declined as something which matches the TypeScript vision In Discussion Not yet reached consensus Suggestion An idea for TypeScript

Comments

@zpdDG4gta8XKpMCd
Copy link

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

@RyanCavanaugh RyanCavanaugh added Suggestion An idea for TypeScript Needs Proposal This issue needs a plan that clarifies the finer details of how it could be implemented. labels Jan 19, 2016
@RyanCavanaugh
Copy link
Member

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

@zpdDG4gta8XKpMCd
Copy link
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.

@RyanCavanaugh
Copy link
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;

@zpdDG4gta8XKpMCd
Copy link
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

@RyanCavanaugh
Copy link
Member

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

@zpdDG4gta8XKpMCd
Copy link
Author

Because narrowing is what type guards do! 😜

@yortus
Copy link
Contributor

yortus commented Jan 20, 2016

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)?

@zpdDG4gta8XKpMCd
Copy link
Author

@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.

@zpdDG4gta8XKpMCd zpdDG4gta8XKpMCd changed the title allow partial assignability in type guards allow narrowing to any sub-type-expression in type guards Jan 20, 2016
@yortus
Copy link
Contributor

yortus commented Jan 20, 2016

@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

@zpdDG4gta8XKpMCd
Copy link
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;

@yortus
Copy link
Contributor

yortus commented Jan 20, 2016

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?

@yortus
Copy link
Contributor

yortus commented Jan 20, 2016

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;
}

@zpdDG4gta8XKpMCd
Copy link
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.

@yortus
Copy link
Contributor

yortus commented Jan 20, 2016

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
}

@zpdDG4gta8XKpMCd
Copy link
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)

@yortus
Copy link
Contributor

yortus commented Jan 20, 2016

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.

@RyanCavanaugh RyanCavanaugh added In Discussion Not yet reached consensus and removed Needs Proposal This issue needs a plan that clarifies the finer details of how it could be implemented. labels Jan 21, 2016
@mhegazy
Copy link
Contributor

mhegazy commented Feb 24, 2016

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

@mhegazy mhegazy closed this as completed Feb 24, 2016
@mhegazy mhegazy added the Declined The issue was declined as something which matches the TypeScript vision label Feb 24, 2016
@microsoft microsoft locked and limited conversation to collaborators Jun 19, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Declined The issue was declined as something which matches the TypeScript vision In Discussion Not yet reached consensus Suggestion An idea for TypeScript
Projects
None yet
Development

No branches or pull requests

4 participants