最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

typescript - Inferred type is too general when passing object vs. passing parameters - Stack Overflow

programmeradmin11浏览0评论

I'm experiencing some unexpected type inference behavior in TypeScript when trying to convert a three-argument function to a single-argument function which takes an object.

I am trying to define a solid-js (or React) component that behaves similarly to the applyWhen function below, which returns a JSX.Element. This problem originates from an attempt to generalize this workaround.

First, we define a Result discriminated union type and a type predicate for each branch:

type ResultOk<T>  = { status: "ok", data: T };
type ResultErr<E> = { status: "error", error: E };
type Result<T,E> = ResultOk<T> | ResultErr<E>;

const isOk = <T,E>(result: Result<T, E>): result is ResultOk<T> => {
  return result.status === "ok";
}
const isErr = <T,E>(result: Result<T, E>): result is ResultErr<E> => {
  return result.status === "ok";
}

We also define the type Predicate of type predicates:

type Predicate<S extends T, T> = (e:T) => e is S;

Now, we want to write a function applyWhen that applies a function to a value, provided that the value satisfies a given type predicate. The simplest definition is:

// Version 1:  Pass Three Parameters
const applyWhen_params = <S extends T, T, R>(
  t: T,
  pred: Predicate<S, T>,
  fn: (s: S) => R
): R|null => {
  if(pred(t)) {
    return fn(t);
  } else {
    return null;
  }
}

// Example 1:  Success!  No Type Errors!
function example_params(x: Result<number, string>) {
  applyWhen_params(
    x,
    isOk,
    (a) => { return a.data + 1; }
  )
}

Hovering over the call to applyWhen_params, we see that the generic arguments S and T are correctly inferred:

// inferred type
const applyWhen_params: <ResultOk<number>, Result<number, string>, number>(t: Result<number, string>, pred: Predicate<ResultOk<number>, Result<number, string>>, fn: (s: ResultOk<...>) => number) => number | null

The above example no longer type-checks if we pass the three values as an object, rather than one-by-one as arguments:

// Version 2:  Pass an Object
const applyWhen_props = <S extends T, T, R>(props: {
  t: T,
  pred: Predicate<S, T>,
  fn: (s: S) => R
}): R|null => {
  if(props.pred(props.t)) {
    return props.fn(props.t);
  } else {
    return null;
  }
}

// Example 2:  Type Error!
function example_props(x: Result<number, string>) {
  applyWhen_props({
    t: x,
    pred: isOk,
    fn: (a) => { return a.data + 1; }
    //                    ^^^^
    // TypeError:  Property 'data' does not exist on type 'Result<number, string>'.
  })
}

Hovering over the call to applyWhen_props shows that the inferred type is too general:

// inferred type of applyWhen_props
const applyWhen_props: <Result<number, string>, Result<number, string>, any>(props: {
    t: Result<number, string>;
    pred: Predicate<Result<number, string>, Result<number, string>>;
    fn: (s: Result<...>) => any;
}) => any

// inferred type of pred
pred: Predicate<Result<number, string>, Result<number, string>>

// inferred type of isOk
const isOk: <T, E>(result: Result<T, E>) => result is ResultOk<T>

Question

Is there a way to modify example_props so that it type-checks, without having to explicitly annotate the type parameters?

发布评论

评论列表(0)

  1. 暂无评论