question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Parameter of a generic interface doesn't work when it's a generic index of an another interface

See original GitHub issue

TypeScript Version: typescript@next, typescript@3.5.1

Search Terms: generic index is not assignable

Code

interface SettingsTypes {
  audio: {
    volume: string;
  };
  video: {
    resulution: string;
  }
}

interface Settings<Params extends { [K in keyof Params]?: string }> {
  config: Params;
};

// Example 1
type ThisWorks = Settings<SettingsTypes['audio']>;

// Example 2
type ThisDoesntWork<T extends keyof SettingsTypes> = Settings<SettingsTypes[T]>;

// ERROR:
// Type '{ volume: string; }' is not assignable to type '{ [K in keyof SettingsTypes[T]]?: string | undefined; }'.

// oh, ok but then why this works?

// Example 3
type ThisWorksAgain = Settings<{ volume: string; }>;

Expected behavior: Generic should work the same if we pass an specific index of an interface (1), a generic index of an interface (2) or when we pass the type directly (3)

Actual behavior: Typescript throws an error only for the construction Settings<SettingsTypes[T]>.

Playground Link: https://www.typescriptlang.org/play/#code/JYOwLgpgTgZghgYwgAgMoTGUBzAzgFQE8AHCXZAbwChlk4BXAE2AHsAuSm25ANxYBt6AWwgdcYKDgDcXAL4zaPYIwjtO3ZFDL1BWFiDETpcqrKpVQkWIhTpMOXAB4ACnChwh5CAA9IIRuQUyADaANLIoMgA1hCELDDIru6eALoA-IaSINjIsgB86sgI+jDA2BxJHrgy8uYA9HXIAKLeHsT8KACMVGAkKPgAFsC4AOosUFHkALxoGFjZTnbzeESkuMEA5AzMLBspeTJUDc2tQu0oAEw9fciDwwAiLGTgYxOO+Mg+fgHRsfGz9gWqzIBRmSwcjnBQL663w+0OxyaACUkQB5JFsI6NYHIDZBPiCESZaS5DYRcggFhgOi4XBlEBwABGHWQYBYrJueJC4UiMTiCShKxhwTh6WJ2WQAB9kPR-BBSiAIIwpKSAHT1RosAYAGmQLCiyEZ9GpYAGEBAyAA7gNCKyhuRLeNJmkNSc2iyAMzXUi3e2vSYAQWwcEiYLmEPxAmEomQ4iy2BV+SkQA

Related Issues: 32017 and 31904

Issue Analytics

  • State:open
  • Created 4 years ago
  • Comments:5 (1 by maintainers)

github_iconTop GitHub Comments

1reaction
jack-williamscommented, Jul 14, 2019

@marcingajda

Looking into this abit more, I believe your issue boils down to this:

type SettingsTypesKey<K extends "volume" | "resulution"> = K;
type Broken<T extends keyof SettingsTypes> = SettingsTypesKey<keyof SettingsTypes[T]>;
// Type 'string | number | symbol' does not satisfy the constraint '"volume" | "resulution"'.

Basically keyof SettingsTypes[T] ==> string | number | symbol, which causes later issues in your example because the checker needs:

  • SettingsTypes[T][string | number | symbol] <: (string | undefined)

per the comment:

// A source type T is related to a target type { [P in Q]?: X } 
// if some constituent Q' of Q is related to keyof T and T[Q'] is related to X.

but there are no index signatures on SettingsTypes[T].

Expanding T to its constraint in keyof SettingsTypes[T] looks tempting, but I believe it would be unsound:

type IsVolume<K extends "volume"> = K;
type Broken<T extends keyof SettingsTypes> = IsVolume<keyof SettingsTypes[T]>;

// If we are allowed to expand T we get:
// 1. IsVolume<keyof SettingsTypes[T]>
// 2. IsVolume<keyof SettingsTypes["audio" | "video"]>
// 3. IsVolume<keyof SettingsTypes["audio"] | SettingsTypes["video"]>
// 4. IsVolume<"volume" & "resulution"> --> this would be ok

// Yet clearly this is unsound

type Res = Broken<"video">; // resolution

So I would leave this issue open and let someone on the team have a definitive look. I’m glad you have something that works for you though!

0reactions
RyanCavanaughcommented, Aug 22, 2019

Minimal version:

interface SettingsTypes {
  audio: {
    volume: string;
  };
  video: {
    resolution: string;
  }
}

type SettingsTypesKey<K extends "volume" | "resolution"> = K;
type Broken<T extends keyof SettingsTypes> = SettingsTypesKey<keyof SettingsTypes[T]>;

Note that this version works:

interface SettingsTypes {
  audio: {
    volume: string;
  };
  video: {
    resolution: string;
  }
}

type SettingsTypesKey<K> = K extends "volume" | "resolution" ? K : never;
type Broken<T extends keyof SettingsTypes> = SettingsTypesKey<keyof SettingsTypes[T]>;
Read more comments on GitHub >

github_iconTop Results From Across the Web

Generic interface with index-type property and method
Is there a way in TypeScript to declare the following interface using index types and exactly one generic type, where ...
Read more >
Java Generics Example Tutorial - Generic Method, Class ...
If you have been working on Java Collections and with version 5 ... A generic type is a class or interface that is...
Read more >
Generics are the Generics of Go | Capital One
Read through the Go Generics Draft (formally called the Type Parameters - Draft Design) to see more details on the draft design, things...
Read more >
6.5 Generic units - Ada Resource Association
Remember that the matching of object and subprogram generic parameters is ... we have various interfaces all derived from Printable which serve different...
Read more >
11. Generic Lists
where T is a type parameter. This type must be an Object, not a primitive type. Many of the methods reference this type,...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found