-
Notifications
You must be signed in to change notification settings - Fork 12.7k
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 inline type predicates #6474
Comments
Here's a workaround that gets removed by bundlers const Narrow = <T>(v): v is T => true;
if (foo.nodeType === 1 && Narrow<Element>(foo)) {
} (edit 2022) Here's another workaround that works as a statement (= works in switch blocks) function Narrow<T extends R, R = unknown>(value: R): asserts value is T {}
switch (foo.nodeType) {
case 1: {
foo; // any
Narrow<Element>(foo);
foo; // Element
}
} |
Combined: function narrow <T>(value: unknown, expr: unknown): value is T {
return Boolean(expr);
}
declare const foo: Node;
if (narrow<Element>(foo, foo.nodeType === 1)) {
foo; // Element
} |
@saschanaz What would it look like using if (foo.nodeType === 1 as foo is Element) {
foo; // Element
} |
It should probably still be with parens - |
Here is another workaround:
|
A better syntax might be: if (foo.nodeType === 1): foo is Element {
// Assume foo is Element here
} This is similar to the declaration of type guards. However, maybe you have a pair of values that depend on eachother to know the type. An example of the original syntax, checking two "dependant" values: if (<foo is Element>(foo.nodeType === 1) && <bar is Text>(foo.nodeType === 3)) { With my first suggestion, this would look like: if (foo.nodeType === 1): foo is Element {
// Assume foo is Element here
if (bar.nodeType === 3): bar is Text {
// Assume bar is Element here
}
} The original syntax beats a nested if statement but looks unclear. Nested if statements may be inefficient for a one off type guard. Functions have only one output but have multiple inputs and type guards only address one input. We can fix this by allowing type guards to address multiple inputs: function isPair(foo: Node, bar: Node): foo is Element, bar is Text;
if (isPair(foo, bar)) {
// Assume foo is Element here
// Assume bar is Text here
} An example of my suggestion now: if (foo.nodeType === 1 && bar.nodeType === 3): foo is Element, bar is Text {
// Assume foo is Element here
// Assume bar is Element here
} Although the current approach would to be create two type guards, there are scenarios where Example: type HelloWorldFirst = Element;
type HelloWorldLast = Element;
// foo and bar derived from a parent element
// they are a pair
// const [foo, bar] = element.childNodes;
if (foo.textContent === "Hello, " && bar.textContent === "world!"): foo is HelloWorldFirst, bar is HelloWorldLast {
// Assume foo is HelloWorldFirst here
// Assume bar is HelloWorldLast here
// We can now pass foo to a function that expects the element to only contain "Hello, " or be of type HelloWorldFirst
} |
+1 for proper compound predicates Aside: You can hack this now by packing the candidates into a tuple (and then unpacking): declare function isPair (tuple: readonly [Node, Node]): tuple is [Element, Text];
declare const foo: Node;
declare const bar: Node;
const fooBarPack = [foo, bar] as const;
if (isPair(fooBarPack)) {
const [
foo,
//^? const foo: Element
bar,
//^? const bar: Text
] = fooBarPack;
} Of course (because it's a hack) it requires (wastefully) creating that tuple. |
You could also imagine doing this as the annotation for a variable holding the predicate's result, i.e.: declare const foo: Node;
const isElement: foo is Element = foo.nodeType === 1;
if (isElement) {
// Assume foo is Element here
} |
The ideal API for this in my opinion would be something similar to the way that type guard functions assert types, but allowed in more places, like on if-statements. For example: Before — requires separate function for type guardingfunction isIterable<T>(obj: Iterable<T> | ArrayLike<T>): obj is Iterable<T> {
return typeof (obj as Iterable<T>)[Symbol.iterator] === 'function';
}
function iterateOver<T>(obj: Iterable<T> | ArrayLike<T>, expose: Function) {
if (isIterable(obj)) {
// some logic here
} else {
// some logic here
}
} After — can handle type guarding inline via if statementfunction iterateOver<T>(obj: Iterable<T> | ArrayLike<T>, expose: Function) {
const isIterable = typeof (obj as Iterable<T>)[Symbol.iterator] === 'function';
if (isIterable): obj is Iterable<T> {
// some logic here
} else {
// some logic here
}
} ** this could just as easily be rewritten to get rid of the function iterateOver<T>(obj: Iterable<T> | ArrayLike<T>, expose: Function) {
if (typeof (obj as Iterable<T>)[Symbol.iterator] === 'function'): obj is Iterable<T> {
// some logic here
} else {
// some logic here
}
} |
From #5731
A proposal from @sandersn: #5731 (comment)
The text was updated successfully, but these errors were encountered: