Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
584 views
in Technique[技术] by (71.8m points)

Why does Typescript say my type is "assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint"?

Consider the following Typescript code:

class OrderFixture {
  orderId: string;

  constructor() {
    this.orderId = "foo";
  }
}

class DecisionFixture {
  decisionId: string;

  constructor() {
    this.decisionId = "bar";
  }
}

class FixtureStore {
  order = () => new OrderFixture();
  decision = () => new DecisionFixture();
}

const fixtureStore = new FixtureStore();

export function getFixture<
  K extends keyof FixtureStore,
  T extends ReturnType<FixtureStore[K]>
>(entityName: K): T {
  return fixtureStore[entityName](); // ERROR: Type 'OrderFixture' is not assignable to type 'T'.
}

It yields the following type error:

Type 'OrderFixture | DecisionFixture' is not assignable to type 'T'.
  'OrderFixture | DecisionFixture' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint 'OrderFixture | DecisionFixture'.
    Type 'OrderFixture' is not assignable to type 'T'.
      'OrderFixture' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint 'OrderFixture | DecisionFixture'.ts(2322)

Here is a playground.

There seems to be a fairly canonical answer to the question raised by this type of error but I am unable to see any relevant similarity between the two causes laid out there and my code.

If I force cast the return value to T as suggested in this answer I get the right types when invoking getFixture. Why doesn't Typescript infer these types for me?


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

Typescript's error message here is perfectly descriptive: your function returns something which is assignable to OrderFixture | DecisionFixture, but not assignable to an arbitrary subtype of it, such as e.g. OrderFixture & HTMLCanvasElement. If you still aren't sure, consider the following code, in which your function promises to return such a thing; it is clearly not type-safe, but it has no error, because it's only using the return type that your function claims to have.

let canvas = getFixture<'order', OrderFixture & HTMLCanvasElement>('order');
let ctx = canvas.getContext('2d');

Normally, for exactly this reason, you should not have a type parameter which only appears in the return position, because it allows the caller to expect a specific type without passing any argument that allows the function to know what type it is expected to return. In your case, there is no need for the type parameter T: just make ReturnType<FixtureStore[K]> the return type of your function directly.


That said, in this case there is something fishy going on that does seem to be Typescript's fault: even splitting up the function's logic and providing type annotations to help the compiler, Typescript still gives an error.

function getFixture<K extends keyof FixtureStore>(entityName: K): ReturnType<FixtureStore[K]> {
  let factory: FixtureStore[K] = fixtureStore[entityName];

  // error here
  let instance: ReturnType<typeof factory> = factory();
  
  return instance;
}}

Playground Link

Logically, Typescript should never complain that the type of factory() might not be assignable to ReturnType<typeof factory>, yet here it complains of exactly that. So I think this should be looked at by someone who knows about Typescript's internals, and perhaps raised as a bug on their issue tracker.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...