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

Type inference for partially specified generics #40020

Closed
4 of 5 tasks
stof opened this issue Aug 12, 2020 · 4 comments
Closed
4 of 5 tasks

Type inference for partially specified generics #40020

stof opened this issue Aug 12, 2020 · 4 comments
Labels
Duplicate An existing issue was already created

Comments

@stof
Copy link

stof commented Aug 12, 2020

Search Terms

generic inference

Suggestion

When a function has several generic types and only one is specified, it seems like the following ones are assigned their default types rather than applying inference. This means that needing to specify the first type forces to specify them all to avoid loosing the benefits of the more precise inferred type for the second generic type.

It would be great that when specifying only partially the generic types for a call, the remaining ones could still benefit from type inference rather than always using their base type.

Use Cases

The use case comes from fregante/delegate-it#19 where I try to make the library nicer for type inference, but an expected usage for TS users would be to explicitly specify the first generic type (for which there is no smart inference possible) and let type inference do its work for the second one.

Examples

I'm reproducing here the types from the PR above:

export type EventType = keyof GlobalEventHandlersEventMap;
type GlobalEvent = Event;
namespace delegate {
	export type Subscription = {
		destroy: VoidFunction;
	};
	export type EventHandler<TEvent extends GlobalEvent = GlobalEvent, TElement extends Element = Element> = (event: Event<TEvent, TElement>) => void;
	export type Event<TEvent extends GlobalEvent = GlobalEvent, TElement extends Element = Element> = TEvent & {
		delegateTarget: TElement;
	};
}

function delegate<TElement extends Element = Element, TEventType extends EventType = EventType>(
	base: EventTarget | Document | ArrayLike<Element> | string,
	selector: string,
	type: EventType,
	callback: delegate.EventHandler<TEvent, TElement>,
	type: TEventType,
	callback: delegate.EventHandler<GlobalEventHandlersEventMap[TEventType], TElement>,
	options?: boolean | AddEventListenerOptions
): delegate.Subscription {
     // [redacted]
}
export default delegate;

Here is 3 different usages:

import delegate from 'delegate-it';

delegate(document, '.js-comment-field', 'keydown', event => {
  const field = event.delegateTarget;
  const key = event.keyCode;
})

delegate<HTMLTextareaElement>(document, '.js-comment-field', 'keydown', event => {
  const field = event.delegateTarget;
  const key = event.keyCode;
})

delegate<HTMLTextareaElement, 'keydown'>(document, '.js-comment-field', 'keydown', event => {
  const field = event.delegateTarget;
  const key = event.keyCode;
})

In the first case, the event is properly inferred as using KeyboardEvent thanks to the 'keydown' event type. That's where type inference shines. But we get Element as the type for the field variable because type inference cannot infer a better type there.
That's where the maintainer of the library would write code using the second syntax, where the generic type for the delegate target is specified explicitly. But doing that looses the inference of KeyboardEvent based on the 'keydown' event type, going back to KeyboardEvent | UIEvent | Event | AnimationEvent | MouseEvent | ... for the event. This happens because it keeps using keyof GlobalEventHandlersEventMap as the TEventType generic rather than inferring it based on the 'keydown' argument.
This would force writing the third ugly syntax (repeating 'keydown') to get both a precise type for field and for event

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, etc.)
  • This feature would agree with the rest of TypeScript's Design Goals.

I'm not sure whether changing this would break existing Typescript code.

@awerlogus
Copy link

duplicate: #38499

@tadhgmister
Copy link

technically this is a duplicate of #26242 since that is the suggestion and #38499 is just a misunderstanding that ends up linking to #26242

@stof
Copy link
Author

stof commented Aug 13, 2020

indeed. I missed that one in the search.

@RyanCavanaugh RyanCavanaugh added the Duplicate An existing issue was already created label Aug 20, 2020
@typescript-bot
Copy link
Collaborator

This issue has been marked as a 'Duplicate' and has seen no recent activity. It has been automatically closed for house-keeping purposes.

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

5 participants