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

Infer Types for Generic Type Parameters That Are Not Specified #43119

Closed
6 tasks done
ITenthusiasm opened this issue Mar 6, 2021 · 6 comments
Closed
6 tasks done

Infer Types for Generic Type Parameters That Are Not Specified #43119

ITenthusiasm opened this issue Mar 6, 2021 · 6 comments
Labels
Duplicate An existing issue was already created

Comments

@ITenthusiasm
Copy link

Suggestion

Infer types for generic type parameters that are not specified.

🔍 Search Terms

Search Inputs:

  • infer, type, for, record
  • I have read the FAQs for feature requests.

Search Time: 21:01.23 (21 minutes)

Potentially related issues (in order of interest):

Though related, I couldn't tell if these issues were exactly the same since the scope here is more narrow.

✅ Viability Checklist

My suggestion meets these guidelines:

  • This wouldn't be a breaking change in existing TypeScript/JavaScript code
  • This wouldn't change the runtime behavior of existing JavaScript code
  • This could be implemented without emitting different JS based on the types of the expressions
  • This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, new syntax sugar for JS, etc.)
  • This feature would agree with the rest of TypeScript's Design Goals.

The third one threw me off a little. But since the suggestion bases itself on existing TS features, I think this should be safe.

⭐ Suggestion

This is a suggestion to infer types for generic type parameters that are not specified. As an example for clarity, you can play with the code below on the typescript playground:

function genericFunction<V = unknown, K extends string = string>(arg: Record<K,V>) {
    return arg;
}

const input = { a: 1, b: 2 };

// Property names ARE inferred
genericFunction(input);

// Property names NOT inferred
genericFunction<number>(input);

In the above example, you'll noticed that when no type parameters are provided, TypeScript is able to correctly infer the types on its own. This thankfully includes the property names of the passed object.

However, when any type parameters are provided, TS assumes the default values for all of the remaining type parameters. This actually causes information to be lost, even though the situation hasn't changed. That is, in both situations, the user never explicitly specified the type of the keys, K.

Instead of assuming the default values for the remaining parameters whenever any types are specified, TS should continue to infer types for the remaining type parameters. The current functionality makes me assume there's some algorithm that tries to infer the types when no type parameters are specified, and it's able to infer object keys. I'd imagine that same algorithm can simply be taken and used to "move down the list of type parameters", so to speak. So it would look for any remaining unspecified types and infer them instead of applying the default values.

📃 Motivating Example

Improved Type Inference for Generics

Previously, when working with optional generic type parameters, TypeScript would assume the default values for any types that the user failed to specify. The issue only occurred if the user specified some of the type parameters. This caused information to be lost (particularly for objects) if the user wanted to specify only a subset of the available types.

Now, TypeScript correctly infers the types for type parameters that are not explicitly provided by the user. The default value is only used if one cannot be determined.

const object = { a: 1, b: 2 };

function foo<Value = unknown, Key extends string = string>(input: Record<Key,Value>): Record<Key, Value> {
  // ...
}

// Object correctly typed as Record<"a" | "b", number> instead of Record<string, number>
const output = foo<number>(object);

💻 Use Cases

Currently, packages like React JSS rely on TS to improve the user experience. In the case of React JSS, a user supplies an object of key-value pairs, where the values represent CSS styles for components. Users have to keep track of the keys because they're used to identify the different styles. See the React JSS playground for a clear example.

As you'll see in the playground, the class names can be inferred for an improved experience. However, after receiving a request for improved prop-type recognition (cssinjs/jss#1273), the library needs to include generic types. Once generics are introduced, users will get type inference for React prop types but will then lose automated inference for object keys, recreating the issue of poor intellisense.

The only workaround for this issue is to supply a list of strings ("A" | "B" | "C") as a type parameter for the class names. But this can get long with a larger number of style declarations, and it's redundant.

@MartinJohns
Copy link
Contributor

Duplicate of #26349. Partial type inference is on the roadmap under the "Future" section.

@ITenthusiasm
Copy link
Author

Man. All that searching and still couldn't find that thing. Thanks @MartinJohns!

@ITenthusiasm
Copy link
Author

Wait @MartinJohns are you sure about that? This situation is a bit different. The ReactJSS library exports functions that the user can use. The (proposed) type parameters are <Props, Theme, and ClassNames>, and they all have defaults. The PR you mentioned involves something the user would still have to do.

Ideally, the user could do something like createUseStyles<PropTypes>(styles) and leave it at that. But, if the user only wanted to supply props, then they'd need to do createUseStyles<PropTypes, _, _>(styles). They'd have to "reach over" Theme, which is a bit strange to me.

Writing this out now, I guess an alternative is for the library to rearrange props to something like <ClassNames, Props, Theme>. However, this means that every time the user calls createUseStyles, they will be forced to add an additional _ whenever they want to simply use props. The PR you mentioned doesn't necessarily resolve this issue.

Thoughts? I'd also like to hear the thoughts of some others briefly. It could be that this is just the trade-off libraries need to take: First, default parameters would need to be defined last. Next, any potential "implicit default params" would need to be put before all the other default params.

@ITenthusiasm ITenthusiasm reopened this Mar 6, 2021
@MartinJohns
Copy link
Contributor

How would you differentiate between inferred type arguments and default type arguments? The idea the TypeScript team plays with is the _ sigil.

@ITenthusiasm
Copy link
Author

Ah. So you're saying other devs may want all the defaults to appear when at least one type is supplied? And that in that case differentiating between defaults and implieds would be impossible?

@RyanCavanaugh RyanCavanaugh added the Duplicate An existing issue was already created label Mar 8, 2021
@ITenthusiasm
Copy link
Author

Seems this has been officially marked as duplicate with no one interested in commenting further. Probably makes the most sense to close. I hope #26349 gets merged soon.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Duplicate An existing issue was already created
Projects
None yet
Development

No branches or pull requests

3 participants