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

Case for inference failure in T extends F<T> #51377

Open
devanshj opened this issue Nov 1, 2022 · 5 comments Β· May be fixed by #51511
Open

Case for inference failure in T extends F<T> #51377

devanshj opened this issue Nov 1, 2022 · 5 comments Β· May be fixed by #51511
Labels
Experience Enhancement Noncontroversial enhancements Help Wanted You can do this Suggestion An idea for TypeScript
Milestone

Comments

@devanshj
Copy link

devanshj commented Nov 1, 2022

Bug Report

πŸ”Ž Search Terms

circular type parameter constraint

Related issues: #40439 #30134

πŸ•— Version & Regression Information

Tested with 4.8.4

⏯ Playground Link

Playground

πŸ’» Code

declare const f:
  <T extends F<T>>(t: T) => T

type F<T> =
  { a: unknown
  , b: (a: T extends { a: infer X } ? X : never) => unknown
  }

f({
  a: "hello",
  b: x => x.toUpperCase()
})
// doesn't compile because infers `x` as `unknown` instead of `string`

f({
  a: "hello",
  b: (x: string) => x.toUpperCase()
})
// compiles

Note that there is a workaround but it's only a workaround for this minimal case and not for the real world case from which this minimal case was derived.

πŸ™ Actual behavior

x in the first f call gets inferred as unknown

πŸ™‚ Expected behavior

x in the first f call should get inferred as string

@RyanCavanaugh RyanCavanaugh added Suggestion An idea for TypeScript Experience Enhancement Noncontroversial enhancements Help Wanted You can do this labels Nov 1, 2022
@RyanCavanaugh RyanCavanaugh added this to the Backlog milestone Nov 1, 2022
@isaacudofia
Copy link

In TypeScript, generics usually work just fine, however when you start using already extended generic types to define new generic types that will also be extended - this is where the trouble occurs. In general, Generic Constraints (e.g. ) in TypeScript are not considered as types that can be inferred further, meaning that whilst extending an extended type like you have in f4 is harmless, extending a newly defined type that also uses generics is problematic:

f4 is perfectly fine:

<Type1 extends Type2, Type2 extends OrderType>

f3 is also fine as it is not extending the type to define the callback but is instead creating directly:

f: (t: Type) => Promise

f2 is where the problem begins to occur as the parameter type is using an extended generic type that also uses extended values. This causes the type to default itself back to OrderType and because FullType does not directly equal OrderType you are observing that error:

Fn extends (t: Type) => Promise

@devanshj
Copy link
Author

devanshj commented Nov 2, 2022

Btw one can write a class equivalent of this too...

interface F
  { a: unknown
  , b: (a: this["a"]) => unknown
  }

new class implements F {
  a = "hello"
  b = x => x.toUpperCase()
}
// doesn't compile because `x` is inferred as `any` instead of `string`.

new class implements F {
  a = "hello"
  b = (x: string) => x.toUpperCase()
}
// compiles

@devanshj
Copy link
Author

devanshj commented Nov 13, 2022

@RyanCavanaugh Could I use this thread to ask some quick trivial questions about the codebase? I'm giving a silly shot at writing a PR for this. The question I have right now is how do you create a type from a type alias symbol and a type passed in as an argument? I imagine something like this...?

let yType = createType(0) // What flags to pass in?
yType.aliasSymbol = fSymbol
yType.aliasArguments = [xType]

yType.__debugTypeToString() does give me "F<X>" but I'm not sure if this is the right way or if it works.

Also let me know if there's a better place/way to ask questions like this.

Edit: I don't need the answer anymore turns out I didn't exactly needed it (see #51511), but still I'd be bonus if I get to know the answer anyway :P

Edit 2: Ah I think getTypeAliasInstantiation(fSymbol, [xType]) would do the trick.

@devanshj devanshj linked a pull request Nov 13, 2022 that will close this issue
5 tasks
@Mike96Angelo
Copy link

This would be helpful for excluding certain types in a generic type T. For example Optional<null> doesn't really make sense so it would be nice if we could tell TypeScript that some types should not be allowed for generic type T

Example:

type Optional<T extends NonNullable<T>> = 
  | { kind: 'Some'; value: T; }
  | { kind: 'None'; };
  
type OptionalNull = Optional<null>;
//                           ^^^^
// Error: Type 'null' does not satisfy the constraint 'NonNullable<null>'. 
// Because type 'null' does not satisfy the constraint 'never'.

type OptionalNull = Optional<null | string>;
//                           ^^^^^^^^^^^^^
// Error: Type 'null | string' does not satisfy the constraint 'NonNullable<null | string>'. 
// Because type 'null' does not satisfy the constraint 'string'.

@devanshj
Copy link
Author

devanshj commented Jan 29, 2023

Your comment/usecase belongs to #51011.

This issue is for this specific inference failure in the OP, and here T extends F<T> is already allowed because it's not immediately circular.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Experience Enhancement Noncontroversial enhancements Help Wanted You can do this Suggestion An idea for TypeScript
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants