Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

checkJs can't correctly determine type of generic function parameter #31243

Open
ghost opened this issue May 3, 2019 · 5 comments
Open

checkJs can't correctly determine type of generic function parameter #31243

ghost opened this issue May 3, 2019 · 5 comments
Assignees
Labels
Suggestion An idea for TypeScript
Milestone

Comments

@ghost
Copy link

ghost commented May 3, 2019

TypeScript Version: 3.4.5

jsdoc
checkJs
template
function
parameter

/**
 * @template T
 * @param {(left: T, right: T) => boolean} equals
 * @returns {(xs: T[]) => T[]}
 */
const uniq = equals => xs => {
	/** @type {T[]} */
	const seen = []
	const out = []
	const len = xs.length
	let j = 0
	for(let i = 0; i < len; i++) {
		const item = xs[i]		
		if(!seen.some(s => equals(s, item))) {			
			seen.push(item)
			out[j++] = item
	   }
	}

	return out;
}

uniq((x, y) => x == y)([1, 3, 3, 7])

Expected behavior:
Typechecks fine

Actual behavior:
Type 'number' is not assignable to type 'T

Playground Link:
https://www.typescriptlang.org/play/#src=%2F**%0D%0A%20*%20%40template%20T%0D%0A%20*%20%40param%20%7B(left%3A%20T%2C%20right%3A%20T)%20%3D%3E%20boolean%7D%20equals%0D%0A%20*%20%40returns%20%7B(xs%3A%20T%5B%5D)%20%3D%3E%20T%5B%5D%7D%0D%0A%20*%2F%0D%0Aconst%20uniq%20%3D%20equals%20%3D%3E%20xs%20%3D%3E%20%7B%0D%0A%09%2F**%20%40type%20%7BT%5B%5D%7D%20*%2F%0D%0A%09const%20seen%20%3D%20%5B%5D%0D%0A%09const%20out%20%3D%20%5B%5D%0D%0A%09const%20len%20%3D%20xs.length%0D%0A%09let%20j%20%3D%200%0D%0A%09for(let%20i%20%3D%200%3B%20i%20%3C%20len%3B%20i%2B%2B)%20%7B%0D%0A%09%09const%20item%20%3D%20xs%5Bi%5D%09%09%0D%0A%09%09if(!seen.some(s%20%3D%3E%20equals(s%2C%20item)))%20%7B%09%09%09%0D%0A%09%09%09seen.push(item)%0D%0A%09%09%09out%5Bj%2B%2B%5D%20%3D%20item%0D%0A%09%20%20%20%7D%0D%0A%09%7D%0D%0A%0D%0A%09return%20out%3B%0D%0A%7D%0D%0A%0D%0Auniq((x%2C%20y)%20%3D%3E%20x%20%3D%3D%20y)(%5B1%2C%203%2C%203%2C%207%5D)

Related Issues:
This seems related, but I don't think it's it

#26883

@weswigham weswigham added the Bug A bug in TypeScript label May 3, 2019
@weswigham
Copy link
Member

This isn't really specific to JS, even in TS in master,

declare function uniq<T>(p: (left: T, right: T) => boolean): (xs: T[]) => T[];

uniq((x, y) => x == y)([1, 3, 3, 7]);

shows uniq's signature as function uniq<unknown>(p: (left: unknown, right: unknown) => boolean): (xs: unknown[]) => unknown[].

@ahejlsberg was an invocation like this supposed to be covered in the recent higher order function assignability stuff? It certainly looks similar to many of those.

@ghost
Copy link
Author

ghost commented May 3, 2019

@weswigham am I right then in thinking there's actually no way to type this function in actual typescript either?

My attempt was something like this, but T wasn't available across all the parameters.

const uniq = (equals: <T>(left: T, right: T) => boolean) => (xs: T[]): T[] => {	
	const seen: T[] = []
	const out = []
	const len = xs.length
	let j = 0
	for(let i = 0; i < len; i++) {
		const item = xs[i]		
		if(!seen.some(s => equals(s, item))) {			
			seen.push(item)
			out[j++] = item
	   }
	}

	return out;
}

@weswigham
Copy link
Member

const uniq = <T>(equals: (left: T, right: T) => boolean) => (xs: T[]): T[] => {	
	const seen: T[] = []
	const out = []
	const len = xs.length
	let j = 0
	for(let i = 0; i < len; i++) {
		const item = xs[i]		
		if(!seen.some(s => equals(s, item))) {			
			seen.push(item)
			out[j++] = item
	   }
	}

	return out;
}

you should just be able to attach T to the outermost function. But as I said, we still don't infer the right types on usage.

@ghost
Copy link
Author

ghost commented May 4, 2019

Thanks for the clarification, I wrongly assumed it was a checkJs issue. I'll just use @ts-ignore for now.

@ahejlsberg
Copy link
Member

A simpler example that illustrates the issue:

declare function foo<T>(f: (x: T) => void): (x: T) => void;

let func: (x: { foo: 'hello' }) => void = foo(x => x.foo);  // Ok

foo(x => x.foo)({ foo: 'hello' });  // Error

In the assignment to func we infer from the type of func to the return type of foo which in turn gives us a contextual type for x. However, when the function is immediately invoked, we make no inferences from the arguments of the rightmost call, but it is potentially feasible. I'll mark this issue as a suggestion to do so.

@ahejlsberg ahejlsberg added Suggestion An idea for TypeScript and removed Bug A bug in TypeScript labels Jun 12, 2019
@ahejlsberg ahejlsberg modified the milestones: TypeScript 3.6.0, Backlog Jun 12, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Suggestion An idea for TypeScript
Projects
None yet
Development

No branches or pull requests

3 participants