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

keyof interface that extends dynamic key interface does not return keys #38664

Closed
filame opened this issue May 19, 2020 · 7 comments
Closed

keyof interface that extends dynamic key interface does not return keys #38664

filame opened this issue May 19, 2020 · 7 comments

Comments

@filame
Copy link

filame commented May 19, 2020

TypeScript Version: Nightly

Search Terms:
keyof dynamic keys, keyof extended dynamic interface, keyof generic interface

Expected behavior:
bKey should have "a" as type.

Actual behavior:
bKey has string | number as type.

Related Issues:
14359

Code

interface A {
    [key: string]: number;
}

interface B extends A {
    a: number;
}

const bKey: keyof B = 'foo';
Output
"use strict";
const bKey = 'foo';
Compiler Options
{
  "compilerOptions": {
    "noImplicitAny": true,
    "strictNullChecks": true,
    "strictFunctionTypes": true,
    "strictPropertyInitialization": true,
    "strictBindCallApply": true,
    "noImplicitThis": true,
    "noImplicitReturns": true,
    "useDefineForClassFields": false,
    "alwaysStrict": true,
    "allowUnreachableCode": false,
    "allowUnusedLabels": false,
    "downlevelIteration": false,
    "noEmitHelpers": false,
    "noLib": false,
    "noStrictGenericChecks": false,
    "noUnusedLocals": false,
    "noUnusedParameters": false,
    "esModuleInterop": true,
    "preserveConstEnums": false,
    "removeComments": false,
    "skipLibCheck": false,
    "checkJs": false,
    "allowJs": false,
    "declaration": true,
    "experimentalDecorators": false,
    "emitDecoratorMetadata": false,
    "target": "ES2017",
    "module": "ESNext"
  }
}

Playground Link: Provided

@jcalz
Copy link
Contributor

jcalz commented May 19, 2020

This is working as intended. B extends A, so all keys of A are also keys of B. That means keyof B is string | number | "a", which reduces to string | number.

@filame
Copy link
Author

filame commented May 19, 2020

Ok, I understand. Thank you!

Is there a way to only access keyof B? Using Exclude<keyof B, string | number> resolves as expected to never.

@filame
Copy link
Author

filame commented May 19, 2020

That means keyof B is string | number | "a", which reduces to string | number.

That was the hint I missed out. I found a comment in another issue that solves my problem. Thank you! I really appreciate your support!

#25987 (comment)

@filame filame closed this as completed May 19, 2020
@jcalz
Copy link
Contributor

jcalz commented May 19, 2020

I guess you're asking for something like "the keys of B which are not explicitly declared keys of A", since keyof B and keyof A are the same.

Teasing apart the keys of an object type with an index signature is possible but of dubious usefulness. You could. for example, use this method to get the literal/"known" keys of an object site:

type KnownKeys<T> = {
    [K in keyof T]: string extends K ? never : number extends K ? never : K
} extends { [_ in keyof T]: infer U } ? U : never;

And then KnownKeys<A> is never and KnownKeys<B> is "a":

const bKey: KnownKeys<B> = "foo"; // error!

But what is the goal here? If you really need to keep track of "the part of B not mentioned in A", you should probably start with that and then build B out of it:

interface A {
  [key: string]: number;
}

interface BminusA {
    a: number;
}

interface B extends A, BminusA { }

Playground link

@filame
Copy link
Author

filame commented May 19, 2020

I implement a module system where I modules have dependencies. I need the types of the dependencies but I also need to check if all dependencies are passed in runtime. Therefore I need an array of the dependency keys. The following is a pseudo code explanation of what I need.

interface Module<
  ConcreteModuleDependencies extends ModuleDependencies = ModuleDependencies
> {
  dependencies: KnownKeys<ConcreteModuleDependencies>;
  defaultDependencies: Partial<ConcreteModuleDependencies>;
}

interface ModuleDependencies {
  [key: string]: Module
}

interface FooModuleDependencies extends ModuleDependencies {
  bar: BarModule
}

type FooModule = Module<FooModuleDependencies>;

const fooModule: FooModule = {
  dependencies: ['bar'],
  defaultDependencies: {
    bar: BarModule
  }
}

@jcalz
Copy link
Contributor

jcalz commented May 19, 2020

What's BarModule? If your make your code into a minimal reproducible example suitable for dropping in a standalone IDE like the Playground I might be able to help guide you. Of course GitHub issues aren't the right place for this; it should happen in Stack Overflow or a forum, etc.

@filame
Copy link
Author

filame commented May 20, 2020

You already answered my question. Thank you!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants