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

Generic parameter type bounds inference #18611

Closed
brandonbloom opened this issue Sep 20, 2017 · 12 comments
Closed

Generic parameter type bounds inference #18611

brandonbloom opened this issue Sep 20, 2017 · 12 comments
Labels
Duplicate An existing issue was already created

Comments

@brandonbloom
Copy link
Contributor

TypeScript Version: 2.5.1

Overview

There are some similar issues, but I couldn't find one that explicitly addresses this. Apologies if I missed it.

Frequently, I discover that adding type bounds (namely: extends) produces cascading boilerplate to all downstream generic code. It's one thing to be forced to propagate type parameters, but it's another entirely to have to propagate type bounds as well.

Code

interface Foo {
  foo: number;
}

let f = <T extends Foo>(x: T) => { }

let g = <T>(x: T) => f(x);

Expected behavior:

The inferred type of g should be <T extends Foo>(x: T) => void.

Actual behavior:

An error occurs at the the x argument being passed to f:

Argument of type 'T' is not assignable to parameter of type 'Foo'.

Related

A similar/related problem occurs with defaults. If f had type parameter T extends Foo = Foo, then hof should infer the same default.

@brandonbloom
Copy link
Contributor Author

I received a comment via email that doesn't appear to have shown up here. Maybe it was deleted?

Anyway, the comment essentially said that I should write let g = <T extends Foo>(. I just wanted to clarify: That's the boilerplate I'm trying to avoid. If I have a dozen or so functions that operator on a Foo, or worse yet a Bar, then each of them has to reiterate the extends clause.

@aluanhaddad
Copy link
Contributor

aluanhaddad commented Sep 22, 2017

I agree this is a pain but usually type-inference makes it a non-issue since you don't need to restate any of the types.

In this particular example, where a type would need to be stated, you can write

let f = <T extends Foo>(x: T) => { }

let g: typeof f = x => f(x);

@brandonbloom
Copy link
Contributor Author

This particular example is minimal to illustrate the problem. The typeof operator doesn't help in any more complex case, for example if you add an argument to g.

@mhegazy
Copy link
Contributor

mhegazy commented Sep 22, 2017

This is in the vein as #15114. Use-site inference is something we have considered in the past and decided not to pursue. I think in general we can provide better tooling experience both in terms of errors and completions by avoiding non-local/use-site inference.

@mhegazy mhegazy added the Duplicate An existing issue was already created label Sep 22, 2017
@brandonbloom
Copy link
Contributor Author

I see the parallels, but I don't think this is a duplicate. Type parameters are different than function parameters and deserve separate consideration. It may very well be the case that the same tradeoffs apply, but it's not immediately clear to me that they do.

@mhegazy
Copy link
Contributor

mhegazy commented Sep 23, 2017

I see the parallels, but I don't think this is a duplicate. Type parameters are different than function parameters and deserve separate consideration

It is the same sort of analysis. you would expect it to flow inferences from use sites. so if your g is:

let g = <T>(x: T) => f(x) || h(x);

then the bounds should be that of f and that of h.

if function let baz = <T>(x :T ) => f(x); let g = <T>(x: T) => f(x), i would expect that you want to flow the bounds inference back to g from the call to f.

In general such non-local inferences are possible, and other type systems have implemented them, but there are always trade-offs. we have chosen to build the TS type system on a different design vector.

@brandonbloom
Copy link
Contributor Author

Sorry, my example is bad. I'm not actually interested in inference from the body of the function, just within the signature itself. I made my example too minimal and, you're right, I turned it in to a case of the other issue. Please allow me to attempt to re-articulate.

Here's another example:

interface Value {
  tag: string;
}

interface List<T extends Value> extends Value {
  tag: 'list',
  elements: T[];
}

let count = <T extends Value>(xs: List<T>) => xs.elements.length;

I wish I could write:

let count = <T>(xs: List<T>) => xs.elements.length;

That would be the same kind of use-site analysis, but it's entirely contained by the type signature. I may be mistaken, but I don't think the concerns about complexity and non-locality of error messages apply in quite the same way.

@weswigham
Copy link
Member

So you wish an unconstrained generic had an inferred constraint of the intersection of the types of all the constraints the generic must fulfill based on its usages? That is use-site inference; just on type parameters rather than value-level parameters.

@brandonbloom
Copy link
Contributor Author

I'd like use-site inference everywhere, but it has been ruled out-of-scope due to complexity at the value level. I get that. What I'm suggesting is that the tradeoffs may be different at the type level.

@mhegazy
Copy link
Contributor

mhegazy commented Sep 26, 2017

but it has been ruled out-of-scope due to complexity at the value level.

That is not accurate. as I noted earlier it is a design choice that we have made early on when we started building TS. We believe explicit types allows us to have better tools, and in turn help us increase JS developer productivity. This applies to both parameters and type parameter upper bounds.

@mhegazy
Copy link
Contributor

mhegazy commented Oct 11, 2017

Automatically closing this issue for housekeeping purposes. The issue labels indicate that it is unactionable at the moment or has already been addressed.

@mhegazy mhegazy closed this as completed Oct 11, 2017
@gabomgp
Copy link

gabomgp commented Apr 11, 2018

Maybe, someone is interested in a RFC in Rust that is accepted and is being implemented. The RFC 2089 Implied bounds is similar to this issue. I think this it is a good idea.

@microsoft microsoft locked and limited conversation to collaborators Jul 25, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Duplicate An existing issue was already created
Projects
None yet
Development

No branches or pull requests

5 participants