typescript-go icon indicating copy to clipboard operation
typescript-go copied to clipboard

Change in inference from tuple to array

Open TimMoore opened this issue 6 months ago • 3 comments

The following code is accepted by TypeScript tsc 5.8.3 but fails with tsgo as of version 7.0.0-dev.20250603.1:

type IMapEntries<K, V> = [K, V][];

type IKeyValueMap<V> = {
  [key: string]: V;
};

function testFunction<K, V>(initialValues?: IMapEntries<K, V> | IKeyValueMap<V>): void {}

const obj = {
  key1: 'value1',
  key2: 'value2',
  key3: 'value3',
};

testFunction(Object.entries(obj).map(([key, value]) => [key, value]));

https://tsplay.dev/mbb5dm

The error from tsgo is:

test.ts(15,14): error TS2345: Argument of type 'string[][]' is not assignable to parameter of type 'IKeyValueMap<string> | IMapEntries<string, string> | undefined'.
  Type 'string[][]' is not assignable to type 'IKeyValueMap<string> | IMapEntries<string, string>'.
    Type 'string[][]' is not assignable to type 'IMapEntries<string, string>'.
      Type 'string[]' is not assignable to type '[string, string]'.
        Target requires 2 element(s) but source may have fewer.

Seemingly incidental changes such as inlining either of those type definitions into the function signature resolves the error.

This is a simplification of production code using the MobX observable.map function.

TimMoore avatar Jun 04 '25 00:06 TimMoore

This is union order dependent. Swap the order of the two things in your union, and you get the same error in TS 5.8: Playground Link

The Go port has to have a different union ordering than the original code in order to prevent check-order dependent inconsistencies (allowing us to do concurrent checking), and this is unfortunately a case where that matters.

(This reminds me of https://github.com/microsoft/TypeScript/issues/52313)

jakebailey avatar Jun 04 '25 01:06 jakebailey

@ahejlsberg I know you marked this as wontfix, but do you think there's likely to be a generic fix to this class of problem? AFAIK this is union subtype reduction, right?

jakebailey avatar Jun 04 '25 17:06 jakebailey

It does appear there's something we can do in this particular situation. The reason for the failure (with either compiler) is that as we're trying to determine the contextual type for the [key, value] return expression, we infer from the contextual type of the map(...) call to U[], the return type of map. That yields two inference candidates string and [string, string], and since neither is a supertype of the other, we pick the "first" candidate--and when that candidate is string we loose the indication that we should produce a tuple type for [key, value]. However, we're inferring to produce a contextual type and we gain nothing by discarding candidates. We should really just produce a union type and we can easily do that by using InferencePriorityReturnType in the logic that creates the return type mapper. That does indeed fix this issue.

I will create a PR for this in the old compiler repo to see the full effects of this change.

ahejlsberg avatar Jun 22 '25 13:06 ahejlsberg