microsoft / TypeScript

TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
https://www.typescriptlang.org
Apache License 2.0
100.76k stars 12.46k forks source link

Optional Generic Type Inference #14400

Open kube opened 7 years ago

kube commented 7 years ago

13487 added default generic types, but it's still not possible to infer a generic type:

type Return<T extends () => S, S = any> = S

Here S takes its default type any, but T could permit inference of a subset type of any for S:

const Hello = () => 'World'

type HelloReturn = Return<typeof Hello> // any

Here HelloReturn still has any type, and TypeScript could infer S as the literal type 'World', which is a subset of any.

Use Case

Here's an example of a fully declarative workaround for #6606, using generic type inference:

type Return<T extends () => S, S = any> = S
const Hello = () => 'World'

type HelloReturn = Return<typeof Hello>  // 'World'

Default Type

If return of T is not a subset of S, it should throw an error:

type ReturnString<T extends () => S, S = string> = S
const Hello = () => 'World'
type HelloReturn = ReturnString<typeof Hello>  // 'World'
const calculateMeaningOfLife = () => 42
type MeaningOfLife = ReturnString<typeof calculateMeaningOfLife> // ERROR: T should return a subset of String

Multiple Possible Type Inferences

The current implementation always returns the superset (which is just the default generic type), solving this issue would require to return the subset (the most precise type of all the inferred possibilities).

If a type has multiple possible type inferences, TypeScript should check that all these types are not disjoint, and that they all are subsets of the Default Generic Type.

The inferred type is the most precise subset.

KiaraGrouwstra commented 7 years ago

Thanks for creating this; I'll follow this thread as well.

A use-case I'm seeing here is clarifying types in function composition in the presence of generics on the first parameter-function -- that is, the one place that'd cause ambiguity to TS. (Related thread on this issue here.)

For example, for Ramda.js pipe<V0, T1, T2>(fn0: (x0: V0) => T1, fn1: (x: T1) => T2): (x0: V0) => T2;, there are three generics in total, but manually clarifying just the parameters of the entering function (in this case that's just V0) should suffice to hint TS as to what the types throughout the rest of the pipeline (here T1 as well as end return type T2) should be.

I'd see this proposal such as to enable that. That said, I'd wonder if manually specifying = any should be required for this to work. (any seems like a default value so = any seems an oxymoron. Looks like this is discussed in #13609.)

KiaraGrouwstra commented 7 years ago

I just realized, might generics 'arities' not be used to disambiguate calls for functions with over-loaded type signatures? i.e. if foo has both <A>(): ... and <A, B>(): ..., if we wanted to invoke foo<myA>() so as to call the binary version, it might get misinterpreted as the unary version. I wonder how we could disambiguate that -- foo<myA, >?

Dessix commented 7 years ago

This would bring forward some nice possibilities in using inference to validate function parameters, and I'd very much like to see it happen 👍

niieani commented 7 years ago

Such a simple idea, but adds so much power to the language! Would love to see it happen!

Related:

EvanMachusak commented 7 years ago

Yeah, yeah, but John, your language designers were so preoccupied with whether or not they could that they stopped to wonder whether or not they should.

Typescript is beginning to delve into the territory of too complicated. This is a hard shove off a cliff, in my opinion. Generic syntax is relatively well understood. Generic constraints are relatively well understood. Generic constraints in the form of a pair of lambda expressions would confuse almost everyone who's seen a generic in another language before.

Dessix commented 7 years ago

@EvanMachusak Seeing the other languages in existence, is allowing inference of generics that complicated to the user in trade for the power it adds to the language?

P.S. Who's John?

EvanMachusak commented 7 years ago

@Dessix which other languages have generic inference patterns that resemble this proposal?

niieani commented 7 years ago

JavaScript's a dynamically typed language, and therefore it requires levels of expression in typing that statically typed languages don't. Nobody is forcing you to use these advanced ways of typing, better yet, you could even create a linter rule that forbids their usage.

On the other hand, this feature would enable proper and stricter typings for so many real-world use-cases that it would be very welcome, even if used only for typing external libraries @DefinitelyTyped. Moreover, I've mentioned two related, complex cases, in which this proposal trumps the necessity for additional complexity, i.e. it proposes to make the language simpler, rather than more complex.

EvanMachusak commented 7 years ago

@niieani Maybe I'm wrong here, but the appeal of TypeScript versus plain old JavaScript is that we can apply static language concepts and rules to a language (JavaScript) that lacks them. Our team became interested in it because it helped us discover bugs statically. I'm not following your argument that because JavaScript (not TypeScript) is dynamically (aka un-)typed, we must introduce exotic syntax to the language to serve use cases that I would argue are detracting from the purpose of TypeScript.

For example, there is a reason that anonymous types are typically only inferred at the scope level in most languages, and it's not just because of lazy compiler designers. #6606 is something that shouldn't be solved even if can be. If you need a reference to an anonymous type outside of a function scope, you should declare it as a type. It's extra effort to do so but making it too easy to produce and consume anonymous types means that a creative coder who happens to be intimately acquainted with this particular language power can easily get carried away and produce something utterly impossible to maintain, because trying to find the ultimate source of the type declaration you're working with several levels downstream requires that you scope into the function where the type was first created.

This leads me to your suggestion about linting. Yes, since I am speaking out against this proposal you can be sure I would look for a linter rule to prevent this from being used, but because it's a language feature, it's another syntax I have to learn and understand to be fluent in TypeScript which circles back to my original objection: that simply because we can doesn't mean we should. I love TypeScript's rapid evolution but we need to be careful to balance innovation and evolution with the needs of the ecosystem. Too much complexity, too much syntax can kill a language.

As for your final point that this would kill two birds with one stone: true, but only because you've created two monstrous birds in trying to do things that other languages disallow.

Compilers are smart. They're smarter than humans. That's why we need them.

But as we read code, we're compiling it in our heads. The complex use cases you're talking about solving require a level of type inference that while a compiler can do, a human being reading the code can't in most cases. You may be overestimating the average intelligence (or, as we get older, the average amount of caring) we will apply to a problem when reading code. In my view, this syntax proposal requires more horsepower than its added value merits, and that's why I think while it's a cool idea, it's just too much.

KiaraGrouwstra commented 7 years ago

Does this really add learning curve if it just mirrors param defaults in the expression language? In function composition I'm not sure there's really a nicer way to clarify types.

RyanCavanaugh commented 7 years ago

Is there a difference between this and the recently-implemented default generic type parameters?

Dessix commented 7 years ago

@RyanCavanaugh This feature allows inference to determine types that fit the existing partial spec to allow creation of types and constraints that were otherwise infeasible or unnecessarily verbose. It is also more specific and strict than the current defaulting method, approaching the goals of type literals.

kube commented 7 years ago

@RyanCavanaugh This feature would extend Type Inference to Generics.

Runtime Function

Today it's possible to infer a type of runtime function, without specifying default generic type:

const return = <T>(fn: () => T) => null as T

Then you can call the function without specifying the type, and T will be inferred:

const hello = () => 'World'
const helloReturn = return(hello) // null

type Hello = typeof helloReturn // 'World'

This is a trick used currently to get the return type of a function, but implies a function call at runtime. (Which will return null here, as the goal is just to do the type inference statically)

Type Declaration

This feature would simply extend this possibility to type declaration, and by this way remove unnecessary runtime function call and variable declaration.

Default Generic Type currently provides no inference:

type Return<T extends () => S, S = any> = S

const Hello = () => 'World'
type HelloReturn = Return<typeof Hello> // any
Igorbek commented 7 years ago

Interestingly, originally type parameters defaults were implemented to have described behavior:

  • When specifying type arguments, you are only required to specify type arguments for the required type parameters. Unspecified type parameters can be inferred.

Then, although it wasn't mentioned in the notes, after a design meeting https://github.com/Microsoft/TypeScript/issues/13607, @rbuckton changed the spec:

Following the discussion during the design meeting, I am making the following changes:

  • When specifying type arguments, you are only required to specify type arguments for the required type parameters. ~Unspecified type parameters can be inferred.~ Unspecified type parameters will resolve to their default types. For example:
declare function f<T, U = T>(a: T, b?: U): void;

f(1); // ok. Using inference: T is number, U is number.
f(1, "a"); // ok. Using inference: T is number, U is string.
f<number>(1, "a"); // error. Not using inference: T is number, U is number. 

While this is generally stricter, we can loosen this restriction at a later date if necessary.

@rbuckton @RyanCavanaugh @DanielRosenwasser can this restriction be considered for removing?

I, personally, faced with that when tried to type real-world widely used library: styled-components v2, see my comment in the PR.

KiaraGrouwstra commented 7 years ago

On use-cases, this could allow declaring reusable variables within types; currently there is no good way to deal with factoring out duplicate computations within types that already need their generics for explicit input.

Edit: whoops, you actually can already do that with defaults.

masaeedu commented 7 years ago

Is there a reason this needs to be restricted to optional type parameters?

KiaraGrouwstra commented 7 years ago

@masaeedu: I think it was worded like that because under normal circumstances type parameters already have inference, though not for what the OP tried here. The generic in the example was made optional with the intent to separately capture it, while still wanting to let it get inferred rather than provide a default value in its declaration (as instead, he intends for it to be inferred from the previous generic).

I've flipped on this proposal though; it's a poor man's #6606.

KiaraGrouwstra commented 7 years ago

Well, I take that back. Return types aside, this proposal might also serve to enable extracting constituents of unions, or function parameters. I'm not aware of other ways to do those so far. An extended #5453 might go so far as to allow extracting parameters once supplied to a function, but this would be about extracting parameter types asked for by an (unapplied) function.

KiaraGrouwstra commented 7 years ago

After further consideration, I'm under the impression other potential uses of this proposal, such as extracting parameter types of function types, could be tackled with #6606 as well. So the challenge posed in this thread is how to explicitly pass some type, then capture its constituents. Through functions that could be achieved; the parts to be passed explicitly would be passed as parameters, the constituents then captured through regular (non-defaulted!) generics. For type Return<T extends () => S, S = any> = S, that might become something like type Return = <T extends () => S, S>(f: T) => S;.

My union idea might have been doomed either way though. For your entertainment:

declare function ArrayifyUnion<Union2 extends A | B, A, B>(v: Union2): [A, B];
let u: string | number = 123;
let x = ArrayifyUnion(u);
// ^ want [string, number], got [{}, {}]

I think there were actually still some generic erasure issues, but yeah I dunno, not very confident about this approach anyway.

danvk commented 7 years ago

There's a relevant discussion over on Stack Overflow: Can I specify a value type in TypeScript but still let TS deduce the key type?

There's a workable hack, but a solution to this issue would make it cleaner.

genXdev commented 7 years ago

In a related matter, does anyone know/have a simple solution for this:

https://tinyurl.com/y7yx7frm

KiaraGrouwstra commented 7 years ago

@renevaessen: specify it in the constraint like doSomething<KeyType extends ThisModuleKeys>(key: KeyType)?

KiaraGrouwstra commented 7 years ago

After further consideration, I'm under the impression other potential uses of this proposal, such as extracting parameter types of function types, could be tackled with #6606 as well.

Update on that, my PR for #6606 at #17961 seems able to get return types, yet I haven't had much luck using it to to get parameter types of function types. (Use-case: a compose with return types accurately reflecting inputs in generics and overloads.)

sebinsua commented 6 years ago

Is this related to the issue in which it doesn't seem possible to write a partial version of a get property function without having to manually specify the type of the key we want a value for?

I use this style of coding quite frequently in libraries that I write, and I want to find out whether this feature request is being tracked somewhere so I can follow it. However, if there is already some elegant syntax to do this, I want to know what it is?

See:


In situations in which I wish to use a partial get function TypeScript doesn't seem able to work out the return type correctly.

Example

TypeScript 2.5.3

interface Props {
    property: boolean;
    someProperty: string;
    someOtherProperty: number;
}

const obj = {
    property: true,
    someProperty: 'some property',
    someOtherProperty: 9999
}

function get1<T, K extends keyof T = keyof T>(k: K) {
    return (obj: T): T[K] => obj[k]
}

function get2<T, K extends keyof T = keyof T>(k: K, obj: T): T[K] {
    return obj[k]
}

// Why do I need to supply 'someProperty' as the second type argument to get this to work?
const getSomeProperty = get1<Props>('someProperty')
const someProperty = getSomeProperty(obj) // Type is `string | number | boolean`

// The type is worked out correctly here.
const someProperty2 = get2('someProperty', obj); // Type is `string`

Other than calling get1<Props, 'someProperty'>('someProperty') what could I do to dynamically create a getSomeProperty function which returns a string?

// Perhaps it was wrong for me to set a default type for K, but if I remove it completely then
// on usage of `get1` I get an error message: "Expected 2 type arguments, but got 1" which
// as far as I can tell, still has the same problem and forces the same inelegant approaches.

function get1<T, K extends keyof T>(k: K) {
    return (obj: T): T[K] => obj[k]
}

const getSomeProperty = get1<Props, 'someProperty'>('someProperty')
const someProperty = getSomeProperty(obj) // Type is `string`
KiaraGrouwstra commented 6 years ago

@sebinsua: I presume you got the get function from lodash/fp. Over there it's a bit more complicated since it takes both strings and string arrays for the property path.

Ramda's prop function more closely matches your function here, supporting only simple property strings (the array variant delegated to its path function). You'll find a TS typing for it here, with a variant code-gen'd to take care of the currying here.

The core issue here is that your current typing for get1 keeps both type parameters (T, K) on the first parameter set (of just k). This is problematic because T, required there to calculate K, only becomes available after v is supplied, that is, during application of the second parameter set. Normally one could solve this by moving T to the second parameter set, though this fails here as K's definition depends on T.

This is an issue for others Ramda typings as well, and basically means you get to pick either currying, or keyof, but not both. Over there we've largely attempted to deal with that by seeking out alternatives to keyof that do not run into dependent info issue when curried. We have some options there -- if you're interested, you'll find an experimental approach to the array variant path at #12290.

gcnew commented 6 years ago

@sebinsua The classic workaround (also used in ramda, but highly obfuscated) is:

function get1<K extends string>(k: K) {
    return <T extends { [k in K]: any }>(obj: T): T[K] => obj[k]
}

const getSomeProperty = get1('someProperty')
const someProperty = getSomeProperty(obj) // `string` as expected
masaeedu commented 6 years ago

After seeing a few use cases, I think type parameters with defaults are an orthogonal concern from independent inference of type parameters. All explicit type arguments should be treated as independent inputs to the type inference algorithm, exactly on par with the types of the function's value arguments. Inference should be able to work with a satisfactory subset of any generic function's type parameters, even when none of them are marked as optional.

In other words, explicit type arguments should work as if:

If you try the right hand side of the proposed desugaring you'll see inference works just fine. All the information is there, TypeScript for some reason just decides you must supply all type arguments or none. You could easily imagine a syntax for supplying a named subset of type arguments at the function application site, e.g. f<T = "hello">("world"), for which the semantics can be examined by a straightforward mapping to the desugaring above.

I've opened #20122 to describe how I think this should work.

KiaraGrouwstra commented 6 years ago

Fixed by #21496.

Return types aside, this proposal might also serve to enable extracting constituents of unions, or function parameters.

Looks like these have been tackled with it so far.

Raiondesu commented 5 years ago

@tycho01, #21496 does not solve every use-case.

For example, what should we do, if we need to accept one generic parameter and infer another?\ Once you pass one generic - all non-optional ones stop being inferred and become required for passing.

And if you just = something (make optional) the parameter you need to be inferred - it will never be.

The only solution for this as of now is to divide your generic function into noop HO-functions with generic-parameters only, which is a pretty ugly solution:

declare const unnecessaryHOF: <T>() => <U extends T>(input: U) => U /* imagine we do something important with the output here */;

const workingResult = unnecessaryHOF<{ bar: string }>()({ bar: 'asd', foo: 2 });

// Autocomplete and type inference work properly
workingResult.bar; // string
workingResult.foo; // number

See playground for a more comprehensive demo.

So even if the exact scenario that is described in the explanation of this issue can be solved with inference in conditional types, it doesn't mean that scaled production scenarios can be too.

jamesmfriedman commented 5 years ago

@Raiondesu I can confirm that your solution is a viable workaround. Also makes me sad, having to change the API of my project to properly support type inference.

unional commented 5 years ago

I also use the same workaround in type-plus typeAssertion<T>() and typeOverrideIncompatible<T>() functions

jamesmfriedman commented 5 years ago

@unional can you provide an example of that for my own edification (and others)?

unional commented 5 years ago

@jamesmfriedman oh, I mean the same workaround as @Raiondesu .

Essentially wrapping it in an extra function to separate the types that you want to specify and those you want to infer.

Using typeOverrideIncompatible() as an example:

import { ANotB } from './ANotB';

export function typeOverrideIncompatible<A extends object>() {
  return function <B extends object>(source: B, override: ANotB<A, B>): A {
    return {
      ...source,
      ...override,
    } as any
  }
}
oleg-brizy commented 4 years ago
declare function demo<T1, T2 = "">(p1: T1, p2: T2): void;

demo(1, 2);
demo<boolean>(true, 3);
// TS2345 ----------^ Unexpected
// Argument of type '3' is not assignable to parameter of type '""'
nicky1038 commented 4 years ago

I got here from Google trying to find any information about the same problem as @Raiondesu had. Namely the impossibility of writing functions where some generic parameters are mandatory and some should be automatically infered by TypeScript itself.

It would be very useful if that possibility was added to TypeScript. Obviously, #21496 does not help in this case.

However, I should say #21496 solves the OP's use case (not perfectly, but anyway). The problem @Raiondesu and me stuck into is similar but still a bit different from that use case. It can confuse new readers (like me). Maybe it is worth to discuss the new problem in a sepatate issue? Moreover, it seems like #10571 is more specific for it.

es-lynn commented 4 years ago

Same issue as @nicky1038 and @Raiondesu. I have 2 generics where A needs to be declared, and B can be inferred from the parameters.

sirajalam049 commented 4 years ago

I tried from this blog on optional generic types and worked for me.

function fetchData <T = void> ( url: string): T {
        const response:T = fetch(url);
        return response 
}
IgnusG commented 3 years ago

@jamesmfriedman oh, I mean the same workaround as @Raiondesu .

Essentially wrapping it in an extra function to separate the types that you want to specify and those you want to infer.

Using typeOverrideIncompatible() as an example:

import { ANotB } from './ANotB';

export function typeOverrideIncompatible<A extends object>() {
  return function <B extends object>(source: B, override: ANotB<A, B>): A {
    return {
      ...source,
      ...override,
    } as any
  }
}

I've used the same approach for a factory pattern. Separating the types into 2 generic declarations worked pretty well, although it did introduce the need to call the function in a bit of a weird way: FactoryBuilder<ObjectInterface>()(objectDefaults)

To set it up I've had do something along the lines of:

function FactoryBuilder<Target>(/* Our empty definition to separate the 2 generics */): <S extends Defaults<Target>>(
  defaults: S,
) => Factory<Target, S>

In my case it's important to be able to specify the interface for the object we wish to create = It cannot and should not be inferred.

To provide the defaults for it on the other hand, and get proper typing support (properties given defaults should be marked as optional in the returned factory), we can either do the above or we'd need to supply the type of the defaults which leads to more overhead (and no in-lining):

const defaults = {...};
const factory = FactoryBuilder<ObjectInterface, typeof defaults>(defaults);

Having the ability to infer optional generics would shorten this all to FactoryBuilder<ObjectInterface>(defaults); I know its a tiny change (and might not look important) but the developer has to keep this in mind to call the builder with ()(defaults) instead of (defaults) which can lead to some confusion.

akomm commented 3 years ago

The main intention is to infer the type and not being forced to specify the generic manually. For that, a default is used for the generic. Currently, specifying a default for a generic opt-out of inference. Currently the proposal is to add inference to initially picked default value. What about not adding the inference on the default value, which seems like a workaround already, but make generics only required IF it they can not be inferred? Then you don't need to specify the default.

unional commented 3 years ago

FYI I'm able to do generic type inference this way:

https://github.com/unional/type-plus/blob/7a3cf9a984ed7972230251db5bdd03d9141904a8/src/math/GreaterThan.ts#L18-L30

I'm not sure if I should recommend this to everyone thou. 🌷

Stevemoretz commented 3 years ago

@jamesmfriedman oh, I mean the same workaround as @Raiondesu . Essentially wrapping it in an extra function to separate the types that you want to specify and those you want to infer. Using typeOverrideIncompatible() as an example:

import { ANotB } from './ANotB';

export function typeOverrideIncompatible<A extends object>() {
  return function <B extends object>(source: B, override: ANotB<A, B>): A {
    return {
      ...source,
      ...override,
    } as any
  }
}

I've used the same approach for a factory pattern. Separating the types into 2 generic declarations worked pretty well, although it did introduce the need to call the function in a bit of a weird way: FactoryBuilder<ObjectInterface>()(objectDefaults)

To set it up I've had do something along the lines of:

function FactoryBuilder<Target>(/* Our empty definition to separate the 2 generics */): <S extends Defaults<Target>>(
  defaults: S,
) => Factory<Target, S>

In my case it's important to be able to specify the interface for the object we wish to create = It cannot and should not be inferred.

To provide the defaults for it on the other hand, and get proper typing support (properties given defaults should be marked as optional in the returned factory), we can either do the above or we'd need to supply the type of the defaults which leads to more overhead (and no in-lining):

const defaults = {...};
const factory = FactoryBuilder<ObjectInterface, typeof defaults>(defaults);

Having the ability to infer optional generics would shorten this all to FactoryBuilder<ObjectInterface>(defaults); I know its a tiny change (and might not look important) but the developer has to keep this in mind to call the builder with ()(defaults) instead of (defaults) which can lead to some confusion.

I didn't even read all of your text but you gave me the idea to make mine work with two nested functions thanks!

jamesmfriedman commented 3 years ago

@Stevemoretz oh I gotcha. Yeah, it's a bummer to have the extra () at the callsite. Just looks strange. Amazing how much effort I exhausted in order to be able to write myFunc(...args) instead of myFunc()(...args)

Stevemoretz commented 3 years ago

@Stevemoretz oh I gotcha. Yeah, it's a bummer to have the extra () at the callsite. Just looks strange. Amazing how much effort I exhausted in order to be able to write myFunc(...args) instead of myFunc()(...args)

That's true but I use it for a config creator only once in any apps so I'm good with it, but sure this should be implemented it's a pain in the ***.

infacto commented 2 years ago

This would be a great feature. Just another simple example:

class MyService {
  public showModal<R, T, D>(component: ComponentType<T>, data: DialogConfig<D>): DialogRef<T, R> {
    return this.foo(component, data);
  }
}

I want to set the return value type R which is unknown by default.

By default the generic types automatically infer from the arguments or fallback to 'unknown'. It behaves like optionals. Why do I need to set all generics (T and D) when I set only the first of them (R)? Just leave the remaining generics with the default behavior (infer from args or unknown).

I don't want to set T or D with an default value (like T = unknown). Because I want them resolved automatically from the arguments. But only set one of them which are not in arguments (R).

So what can we do? Either we implement a required flag to generics or a flag to mark generics as explizit optional to infer from args. Or just leave it as it is and allow that what we want. Because the generics are optional anyway.

We could do something like this:

class MyService {
  public showModal<R, T?, D?>(component: ComponentType<T>, data: DialogConfig<D>): DialogRef<T, R> {
    return this.foo(component, data);
  }
}

Adding ? to mark it explizit as optional to keep the default behavior of infer from args or unknown. But since the generics are not required anyway, I don't think that we need a syntax for that. It should just work as we want here with the current syntax. There is no support for required generics. But can be done with e.g. foo<T = void>. Therefore we just change the behavior as expected here. Any expected breaking changes? I just want the following to be working:

// Good (expected, but not working)
const result = await this.myService.showModal<string>(MyComponent, someData);

But currently I have to set all three generics types or override the infer default with a static types like unknown.

// Bad (pseudo code, not tested)
const result = await this.myService.showModal<string, MyComponent, Data>(MyComponent, someData);

// Or it forces me to define the method with generics like `<R, T = any, D = any>`.  :(
// Or I have to wrap the methods to define generics in another scope. It's just lame.
// Or I add an optional dummy argument for type infer. lel

Change "All or nothing" to "Nothing or Some". Just do it. Why not? What's the problem here? Please explain me in simple words with coloured hand puppets. It's over 5 years old great request.


Update: Found a related issue (duplicate?): #26242

akomm commented 2 years ago

What if any non inferable generics are implicit unknown (strict) or implicit any (non-strict). Default type has as today to satisfy the constraint, so does the inferred type. Default type replaces "unknown" in case of non-inferability, though evtl. not if the unknown type is explicit at position from which the inference is possible.

jcalz commented 1 year ago

I don't see this explicitly mentioned here, but if this is ever implemented I'd expect something like

type Foo<T = infer> = {x: T}

const f: Foo = {x: 1};
// const f: Foo<number>

where you use the infer keyword as the default type to ask for such inference.

NfNitLoop commented 1 year ago

Relatedly, TS5 introduced const type parameters. I was hoping I could do something like:

interface Foo<const T = string> { t: T }

const f: Foo = { t: 1 }

But no such luck. I get the error:

Screenshot 2023-04-20 at 2 26 53 PM

(I'd be happy with <T = infer> or <const T = someDefault>. Whatever lets me write this. 😊)