Merge type guard declarations

After studying the method outlined in this post, I successfully created an Or function that accepts a series of type guards and outputs a type guard for the union type. For example: x is A + x is B => x is A | B. However, I encountered difficulties using the resulting function as an argument in Array.filter.

Definitions:

type TypeGuard<A, B extends A> = (a: A) => a is B;
type GuardType<T> = T extends (o: any) => o is infer U ? U : never

class A { q: any }
class B { p: any }
declare function isA(x: any): x is A
declare function isB(x: any): x is B

function Or<T extends TypeGuard<any, any>>(guards: T[]): T extends TypeGuard<infer A, any> ? (a: A) => a is GuardType<T> : never;
function Or<T extends TypeGuard<any, any>>(guards: T[]) {
    return function (arg: T) {
        return guards.some(function (predicate) {
            predicate(arg);
        });
    }
}

Code example:

let isAOrB = Or([isA, isB]); // inferred as ((a: any) => a is A) | ((a: any) => a is B)
let isOr_value = isAOrB({ q: 'a' }); // here isAOrB is inferred as (a: any) => a is A | B which is what I want
[{}].filter(isAOrB).forEach(x => { }); // here I expected x's type to be inferred as A | B because of the type guard, however filter's overload is the regular one, returning the same {}[] type as the source array

To avoid writing a lambda expression as the argument for filter in order to force type inference, I seek an alternative solution.

[{}].filter((x): x is A | B => isAOrB(x)).forEach(x => { });

Unfortunately, this workaround is not ideal for my requirements.

The same problem with an And function combining type guards

Utilizing the UnionToIntersection construct highlighted in this answer, I attempted to correctly type the And function but encountered the following error:

function And<T extends TypeGuard<any, any>>(guards: T[]): T extends TypeGuard<infer A, any> ? (a: A) => a is UnionToIntersection<GuardType<T>> : never;
// the above gives error A type predicate's type must be assignable to its parameter's type.
  Type 'UnionToIntersection<GuardType<T>>' is not assignable to type 'A'.
    Type 'unknown' is not assignable to type 'A'.

Answer №1

There seems to be an issue in this scenario where your conditional type is inadvertently distributing itself. When a bare type parameter, T, is used, the conditional type T extends Foo ? Bar : Baz will break down T into its union members, evaluate the conditional for each member, and then combine the results together. This leads to an unwanted outcome of

((a: any) => a is A) | ((a: any) => a is B)
.

To prevent this distribution, you can wrap the bare type parameter in a single-element tuple like so: [T] extends [Foo] ? Bar : Baz:

function Or<T extends TypeGuard<any, any>>(guards: T[]): 
  [T] extends [TypeGuard<infer A, any>] ? (a: A) => a is GuardType<T> : never;

This modification should provide the intended behavior:

[{}].filter(isAOrB).forEach(x => { }); // x is A | B

The same adjustment applies to your And function, with the additional note that the compiler might not recognize UnionToIntersection<...> as a valid narrowing for the type guard function. Therefore, it's advisable to enclose it in Extract<> like shown below:

declare function And<T extends TypeGuard<any, any>>(guards: T[]):
    [T] extends [TypeGuard<infer A, any>] ? 
    (a: A) => a is Extract<UnionToIntersection<GuardType<T>>, A> : never;

let isAAndB = And([isA, isB]); 
let isAnd_value = isAAndB({ q: 'a' }); 
[{}].filter(isAAndB).forEach(x => { }); // A & B

These adjustments appear to resolve the issue. Best of luck with your code!

Link to Playground with Code

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Issue reported: "Usage of variable 'someVar' before assignment" ; however, it is being properly assigned before usage

This piece of code showcases the issue: let someVar: number; const someFunc = async () => { someVar = 1; } await someFunc(); if (someVar == 1) { console.log('It is 1'); } As a result, you will encounter ...

What is the best way to interweave my objects within this tree using recursion?

I am working on creating a new function called customAdd() that will build a nested tree structure like the one shown below: let obj = [] let obj1 = { key: "detail1Tests", id: "94d3d1a2c3d8c4e1d77011a7162a23576e7d8a30d6beeabfadcee5df0876bb0e" } ...

Error: The function concat() is not valid for messagesRef.current

I'm currently developing an app that enables users to interact with AI by asking questions related to the uploaded PDF file. However, I've encountered a problem while interacting with AI on the client side - I keep receiving the error 'TypeE ...

When a selection is made in React MUI Virtualized Autocomplete, the autocomplete menu scrolls back to the top

I am currently using reactMUI autocomplete with virtualization because the listbox is expected to contain thousands of items. The virtualization feature works fine, but when I add an onchange function, the listbox automatically scrolls back to the top wh ...

What are the best ways to incorporate a theme into your ReactJS project?

Looking to create a context for applying dark or light themes in repositories without the need for any manual theme change buttons. The goal is to simply set the theme and leave it as is. Currently, I have a context setup like this: import { createContext ...

This error occurs when trying to assign a value to a property of a variable that is currently undefined

Having some issues with assigning the latitude and longitude values to a variable in my code. I am able to retrieve them correctly, but when trying to use them in another method (onUpload()), I am facing some errors. export class latlonComponent implement ...

Utilizing getter and setter functions within a setter with a type guard

I need to implement a getter and setter in my class. The setter should accept a querySelector, while the getter is expected to return a new type called pageSections. The challenge I'm facing is that both the getter and setter must have the same argum ...

The error message TS2304 is indicating that the name 'Set' cannot be found in electron-builder

I am trying to utilize the AppUpdater feature in electron-builder for my Electron Application. Upon importing the updater in my main.ts file: import { autoUpdater } from "electron-updater" An error is triggered when running the application: node_module ...

Is there a way to identify the items that have been removed and added in an array by comparing an old array to a new array

Consider the following scenario: oldArray = [ { id: 1, position: 'DEV OM'}, { id: 2, position: 'Senior Developer'}, ] newArray = [ { id: 2, position: 'Senior Developer'}, { id: 3, position: 'Junior Devel ...

A guide on transforming a Firebase Snapshot child into a personalized object in a React Native environment

I'm looking for a way to retrieve data from Firebase Realtime Database and display it in FlatList format. What is the most efficient method for extracting the child value and converting it into a custom object? For example: class CustomObject { ...

Contrasting covariant and contravariant positions within Typescript

I'm currently diving into the examples provided in the Typescript advanced types handbook to broaden my understanding. According to the explanation: The next example showcases how having multiple potential values for the same type variable in co-var ...

Is there a method to uncover the code that controls the display of a <div> element?

As a fresh face at the company, I've been given the responsibility of developing a chart that is similar to one already present on their website. While I have the knowledge and skills to create it, I am struggling to locate the specific code where the ...

There appears to be an issue with the compilation of the TypeScript "import { myVar }" syntax in a Node/CommonJS/ES5 application

In my Node application, I have a configuration file that exports some settings as an object like this: // config.js export var config = { serverPort: 8080 } Within another module, I import these settings and try to access the serverPort property: // ...

Utilizing Express JS: Invoking a function within my class during routing operations

Currently, I am in the process of developing a MERN Application using Typescript. I seem to be encountering an issue with this within my class. When utilizing this code snippet: router.post("/create", user.createNewUser); It becomes impossible ...

Guide on making a Mapped Type in TypeScript using dynamic generic parameters

I am working with an object that contains one or more PreparedStatement. I am looking to create a general type definition for this object. Is there a way to achieve this in code? type PreparedRequest<Input, Result> = { input: Input; result: Resul ...

Exploring ways to list interface keys in order to create a new interface where the value is determined by the corresponding key

How can we iterate through interface keys to create a new interface where the value is dependent on the key? type IParse<T> = { [K in keyof T as K extends string ? K : never]: string // How can we specify that if K === 'a', the type sho ...

Unleashing the power of Angular 7+: Extracting data from a JSON array

As a newcomer to Angular and API integration, I am facing a challenge in fetching currency exchange rates data from the NBP Web API. The JSON file structure that I'm working with looks like: https://i.stack.imgur.com/kO0Cr.png After successfully ret ...

transferring attributes from a higher component to a lower one (modal)

https://i.sstatic.net/tSXb5.png https://i.sstatic.net/H4xmj.png I am relatively new to React and I want to share a detailed problem description: I have a Todo project that consists of multiple interfaces. The main interface displays all the lists, each ...

The <a> tag does not lead to a different webpage and cannot be clicked on

I have developed a web component that includes a method to generate a copyright string: '<p>Copyright © 2020 John Doe<a href="https://www.example.com">. Terms of Use</a></p>' After creating the string, I conver ...

Error message: "Unidentified variable in the code snippet from MUIv5 sample."

Achieving the Objective To implement a drawer sidebar in MUI5 that can be toggled open and closed by the user, I am exploring the documentation for the Drawer component as well as referencing an example. Encountering an Issue Upon copying the code from ...