Skip to content

Type inference is fuzzy / blackbox-y #16774

Closed
@EmielM

Description

@EmielM

TypeScript Version: 2.5.0-dev.20170627

When a generic type has multiple constraints (through extends in generic declaration), it is very hard to predict which one the compiler uses for type inference. I would expect this to be deterministic/predictable.

Two examples (and excuse the verbosity of the code, found it hard to replicate with less):

Listing 1

class Data<T> {
	constructor(v: T) {}
}

type Bound<O> = {
	[K in keyof O]: string | number | boolean
}

type Binding<O> = {
	[K in keyof O]: O[K] | Data<O[K]> | (() => Data<O[K]>)
}

function bind<O extends Bound<O>>(b: Binding<O>, c: ( (a: O) => {} )): null {
	return null
}

var MyComponent = bind({
	a: () => new Data<number>(123),
	b: new Data<boolean>(true)
},
function({a,b}) {
	var j : number = a;
	var l : boolean = b;
	return null;
});

MyComponent is type inferred as bind<{},{a: number, b: boolean}>... as I would expect. This snippet compiles fine.

Listing 2

class Data<T> {
	constructor(v: T) {}
}

type Bound<O> = {
	[K in keyof O]: string | number | boolean
}

type Binding<O> = {
	[K in keyof O]: O[K] | Data<O[K]> | (() => Data<O[K]>) | (() => O[K])
}

function bind<O extends Bound<O>>(b: Binding<O>, c: ( (a: O) => {} )): null {
	return null
}

var MyComponent = bind({
	a: () => new Data<number>(123),
	b: new Data<boolean>(true)
},
function({a,b}) {
	var j : number = a;
	var l : boolean = b;
	return null;
});

Binding<..> now has an additional map value in the form of | (() => O[K]). For some reason, this triggers MyComponent to be inferred as bind<{}, { a: string | number | boolean, b: string | number | boolean }> (which is the type of the Bound). And it then fails to compile because the assignment in the function is incorrect.

I can certainly see type inference under these kind of complex conditions is never going to be trivial, but it's really hard to have any clue as to what's happening here.

Does it work like this because of the spec, because (of some exploration bounds in) the implementation? And is there something I can do to force the inference towards the first case?

I'm posting here as to maybe have a more generic discussion about how such complex cases might be made more approachable by normal programmers. If this is better suited on SO, please do close the ticket.

Metadata

Metadata

Assignees

No one assigned

    Labels

    DiscussionIssues which may not have code impact

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions