Typescript: Use return type as an inference location

Created on 26 Sep 2016  路  18Comments  路  Source: microsoft/TypeScript

From https://github.com/Microsoft/TypeScript/issues/11054

interface FolderContentItem{
    type: 'folder' | 'file';
}

let a:FolderContentItem[] = [];
a = [1,2,3,4,5].map(v=>({type:'folder'}))

Today this is an error without casting "folder" to the literal type. We have a contextual type coming from a that is not being used.

Fixed Needs Proposal Suggestion

Most helpful comment

Still have this problem, and also see this:

let a = [[1, 2], [3, 4]];
let b: [number, number][] = a.map(v => v);
let c: [number, number][] = a.map(v => [1,2]);

Both b and c has compile error:

Type 'number[][]' is not assignable to type '[number, number][]'.

[1,2] is treated as number[] rather than [number, number]

All 18 comments

Does this effectively mean curried function will be inferred?

const curry = <K, T>(k: K) => (t: T) => {}

curry(123)('123') // K inferred to number, T inferred to string

That would be amazing to see. I stumbled upon a similar issue tonight, which is roughly the same as https://github.com/Microsoft/TypeScript/issues/1212. What is the progress on this?

Another case where this would be useful:

function id<T>(x: T): T { return x; }
function f(b: boolean): { n: number } {
    if (b) {
        return { n: 1 };
    } else {
        return id({ n: 1 });
    }
}

Find-all-references on the declaration of n: number should find all 3 references to n, but today misses the one inside id().

Still have this problem, and also see this:

let a = [[1, 2], [3, 4]];
let b: [number, number][] = a.map(v => v);
let c: [number, number][] = a.map(v => [1,2]);

Both b and c has compile error:

Type 'number[][]' is not assignable to type '[number, number][]'.

[1,2] is treated as number[] rather than [number, number]

I'm moving the example from #21275 to here as I filed a duplicate

type Path<T, V> = Array<string>

function path<T, A extends keyof T>(key: A): Path<T, T[A]>
function path<T>(path: string|Array<string>): Path<T, any> {
  if (typeof path === 'string') return [path] as Path<T, any>
  else return path as Path<T, any>
}

function field<T, V>(path: Path<T, V>) {
  return {path}
}

type User = {name: string}

// Errors
field<User, string>(path('name'))

// Works
field<User, string>(path<User, 'name'>('name'))

Can I do anything to help get this feature going?

Keywords: map on array of tuple contextual contextually typed return type of lambda arrow function expressions

I think the only reason this doesn't work is because we widen the return type of the function expression?

Actually, I think #25937 maybe fixes (some of) this. Although undoubtedly @RyanCavanaugh is probably right - most of the remarks here are caused by the return type widening.

Another example, simplified from #26621:

type Box<T> = { value: T };
declare function box<T>(value: T): Box<T>;

type WinCondition =
    | { type: 'win', player: string }
    | { type: 'draw' };

let zz: Box<WinCondition> = box({ type: 'draw' });  // Error

type WinType = 'win' | 'draw';

let yy: Box<WinType> = box('draw');  // Error

Would be nice if we could do better here.

Seems like my proposal at #26979 could be a fix for this. While I don't propose the exact mechanism for literal type inference, I suggest an expression for type assertion that would prevent the type from widening. For example @k8w's code could be written this way:

const a = [[1, 2], [3, 4]] as const;
const b: Array<[number, number]> = a.map(v => v);
const c: Array<[number, number]> = a.map(v => [1,2] as const);

While I agree that implementing the interference mechanism is important, this could provide a quick fix.

Would definitely like to see this done. We've hit this over in Pulumi as part of https://github.com/Microsoft/TypeScript/issues/11312. It seems really unfortunate that something as simple as: new Map(arr.map(a => [a.foo, a.bar])) can't work properly.

I had a PR up that made return widening contextual, rather than always - didn't really get a great chance to review and iterate it before it got out of sync though. #20976 for reference.

Fixed in #29478.

Thanks much @ahejlsberg ! This will be very helpful in many of our complex, highly generic code spots!

I must be missing something. I installed Typescript 3.4.1, and still get the wrong inferrence.

interface Ent { id: number, name: string };

const entities: Ent[] = [{ id: 1, name: 'one' }, { id: 2, name: 'two' }];

// Wrong type inferred: (number | string)[][]
const wrongInferredType = entities.map(ent => [ent.id, ent.name]);

// This works: [number, string][] (as it should be)
const idEntities = entities.map(ent => [ent.id, ent.name]) as [number, string][];

@jeremychone nothing changed w.r.t where we infer tuple types (excepting const contexts) - you'd need to have something with a tuple type on the LHS of that assignment for it to be interpreted as a tuple.

@weswigham Thank you. In fact, just realized I did not understand the root of the problem (which was that without typing, returning an array could not be assumed to be a tuple).

Was this page helpful?
0 / 5 - 0 ratings