1

I am trying to infer the data type in my doAction from the generic type of my GAction

I don't understand why I get the error message Argument of type 'string | number' is not assignable to parameter of type 'never'. Type 'string' is not assignable to type 'never'. Please help me understand what I am doing wrong.

abstract class Action<GData> {
  abstract isValid(data : GData): boolean

  static dostuff<GData>(action: Action<GData>, data : GData): boolean {
    return action.isValid(data);
  }
}
  abstract class State<GAction extends Action<any>> {
    abstract doAction(action: GAction, data: any): State<GAction> 
  }
  
  class NextAction extends Action<string> {
    isValid(data: string): boolean {
      return (data === "222")
    }
  }
  
  class LastAction extends Action<number> {
    isValid(data : number): boolean {
      return (data === 254);
    }
  
  }
  
  type AllStateActions = LastAction | NextAction 

  type ActionData<S> = S extends Action<infer H> ? H : never;
  class OneState extends State<AllStateActions> {
    doAction<GAction extends AllStateActions, GData extends ActionData<GAction>> (action: GAction , data: GData): OneState {
      if(!action.isValid(data)) {
        return this;
      }
      return this; 
    }
  
  }

1 Answer 1

1

The issue is in this line:

type AllStateActions = LastAction | NextAction

Union of function never behaves in an expected way. In fact, I'm not aware of a case where you need to use union of functions.

Consider this example:


type Foo = (v: { name: string }) => void

type Bar = (v: { age: number }) => void

type Union = Foo | Bar

declare const union:Union

// union(v: { name: string; } & { age: number; })
union()

Please see docs:

Likewise, multiple candidates for the same type variable in contra-variant positions causes an intersection type to be inferred

Let's go back to our original example:

type AllStateActions = LastAction | NextAction

declare const action:AllStateActions

// isValid(data: never): boolean
action.isValid()

isValid expects never, because string & number === never. Intersection of string and number is unrepresentable.

In order to fix it, you should intersection:

type AllStateActions = LastAction & NextAction

Intersection of two function overloads them.

WHole code:

abstract class Action<GData> {
    abstract isValid(data: GData): boolean

    static dostuff<GData>(action: Action<GData>, data: GData): boolean {
        return action.isValid(data);
    }
}
abstract class State<GAction extends Action<any>> {
    abstract doAction(action: GAction, data: any): State<GAction>
}

class NextAction extends Action<string> {
    isValid(data: string): boolean {
        return (data === "222")
    }
}

class LastAction extends Action<number> {
    isValid(data: number): boolean {
        return (data === 254);
    }
}

type AllStateActions = LastAction & NextAction // <-------------------------- IMPORTANT CHANGE

type ActionData<S> = S extends Action<infer H> ? H : never;

class OneState extends State<AllStateActions> {

    doAction<GAction extends AllStateActions, GData extends ActionData<GAction>>(action: GAction, data: GData): OneState {
        if (!action.isValid(data)) {
            return this;
        }
        return this;
    }
}

Playground

Sign up to request clarification or add additional context in comments.

1 Comment

Thank you, your answer sent me in the right direction!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.