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

Generics lost when accessing a property #41134

Closed
Harpush opened this issue Oct 16, 2020 · 3 comments
Closed

Generics lost when accessing a property #41134

Harpush opened this issue Oct 16, 2020 · 3 comments
Labels
Working as Intended The behavior described is the intended behavior; this is not a bug

Comments

@Harpush
Copy link

Harpush commented Oct 16, 2020

TypeScript Version: 4.1.0-dev.20201015

Search Terms:
lost generic types, generics property access, generic function invocation

Code:

declare const abc: {
  a: {
    val: number,
    fn: (a: number) => string
  },
  b: {
    val: string,
    fn: (a: boolean) => number
  }
};

type Param<T extends 'a' | 'b'> = Parameters<typeof abc[T]['fn']>[0];
type Model<T extends 'a' | 'b'> = ReturnType<typeof abc[T]['fn']>;

const tester = <T extends 'a' | 'b'>(type: T, p: Param<T>) => {
  const a = abc[type]; // Generic is OK here
  const b = a.val; // Generic lost and becomes union :(
  const bb = a.val as typeof abc[T]['val']; // Explicit cast - Either number or string but in generic context
  // b expected to be bb

  const c = a.fn // generic lost and becomes union :(
  const d = c(p); // accepts never and returns string | number - generic lost :(
  const cc = a.fn as (a: Param<T>) => Model<T>; // Explicit cast
  const dd = cc(p); // Either number or string but in generic context
  // c expected to be cc
}

Expected behavior:
The example is based on two problems which are essentially the same.
The first is more awkward - abc[type] retains the generic type and doesn't expand it,
while for some reason abc[type].val loses the generics. I would expect it to be the same type as doing typeof abc[T]['val']
especially when abc[type] works correctly.
The second example is concerning function invocation too but the problem's base is the same. When accessing abc[type].fn the generic type is lost and becomes a functions union which isn't invokable (parameter becomes never).
The wanted result is actually (a: Param<T>) => Model<T> retaining the generics and allowing invocation.

Actual behavior:
Concerning the function i kind of understand the design limitation but for abc[type].val it seems like a bug.

Playground Link: https://www.typescriptlang.org/play?ts=4.1.0-dev.20201015#code/CYUwxgNghgTiAEYD2A7AzgF3lARmAXPAN4CwAUPNoaRZfAG5QSEoCuAtjiDADTl3wAZikIAKKCw5cYASngBeAHzxMMAJYoA5v3gBfPrRzUdlRsxUZ1WgwOFiJ8HEiQQQUFHKXw2nbjt3kugDc5OQYAJ4ADggACrBQ7AA8ACrwIAAeGCAowGjwAORQ+fAAPgU4+cry8HEwCSBZMGiJEdFIgth4ANrJALpd+cL5vYpdAAy9IWStCACySKAQKWmZ2bkFRaXllQrwAEoNrDAoyVEgLWftnWA9-YMow4pT5MjoWFmY3LvLGVk5eYVimV8hVFKIZoRkjx4JFCLUEilFJ5lDRKK9MNhdrgbjNJvAAPT4+AAcWy3DUYHgajyAHkANLwAAW3BAOnRWBwWIAdGYggSiaSUOTKRAkBj3MBHOAkOwQHlWCg1Kh4PhRGzUBicJzqlAeUxsHkZldsbcBmZhnzCfAAKLpSIQClqLBgKAYgC0NqdzJg3ikXyQPtUGk0jlYWA08E0ZPUlNeWUyOitnIy0TAWUlGCQUscOFCtHZiG5wn5kejFPgovFOSlyFl8sVytV6re8El1TAokiMktRKgYDAIEiGDyQvoXwl8DgGCO6AsVhDZR80ngHqjQpjFbFWCb+Y1zspOq5xdd8HEcPiSWSSIUynmi0RPZtdodYCdiFdGGbGOAbcQHa7j7Wl6XxLv6gaWMGobhigpbruWca-ImRKUim4DpvAmbZv2gTkEAA

Related Issues: #36737 (although seems the first part of mine isn't related)

@RyanCavanaugh RyanCavanaugh added the Working as Intended The behavior described is the intended behavior; this is not a bug label Oct 16, 2020
@RyanCavanaugh
Copy link
Member

This is the intended behavior - lookup types are generally only preserved when it's not possible to resolve them through a constraint on that type parameter. The alternative universe where everything is processed in terms of deferred lookup types is really unpleasant to work in since there's no reasoning you can apply to a deferred operation. In general this only decreases completeness rather than inducing unsoundness, so overall it's a good compromise done on purpose.

@Harpush
Copy link
Author

Harpush commented Oct 16, 2020

@RyanCavanaugh But isn't it the idea of generics? I could pass a union directly and then handle the type problems with type casts. But i decided to go for generics saying that everything is based on it. I agree that those types can become ugly but maybe that's another point of how to display such types. The union fallback seems wrong... The only time i would expect it is if everything was the same type which means the generic parameter had no meaning anymore. But there is a big difference between string | number and the generic based type which i think worth it compared to type casts.
The behaviour here should be for regular union and not for generics imo. Or at least i should have a way to specify that this is what i mean to the compiler. Maybe both can be supported somehow based on the scenario?

@typescript-bot
Copy link
Collaborator

This issue has been marked 'Working as Intended' 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
Working as Intended The behavior described is the intended behavior; this is not a bug
Projects
None yet
Development

No branches or pull requests

3 participants