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

Emtpy interface reduced to never in nested generic function #49536

Closed
panmenghan opened this issue Jun 14, 2022 · 5 comments
Closed

Emtpy interface reduced to never in nested generic function #49536

panmenghan opened this issue Jun 14, 2022 · 5 comments
Assignees
Labels
Needs Investigation This issue needs a team member to investigate its status. Rescheduled This issue was previously scheduled to an earlier milestone

Comments

@panmenghan
Copy link

Bug Report

I can't find the docs about this behavior or google. I am not sure this is a bug.

My intention was define the IEmpty interface and augment it later, but the compiler reduce the IEmpty interface to never, my current workaround that have to define two dummy properties or not use nested function(or closure).

Is there a right way to force the compiler to keep the IEmpty in nested generic function?

🔎 Search Terms

closure, nested, generic, emtpy interface, never, narrow

🕗 Version & Regression Information

  • This is the behavior from v4.2.3 to v4.7.3

⏯ Playground Link

Playground link with relevant code

💻 Code

// empty interface
interface IEmpty {}

export function func<T extends keyof IEmpty>() {}

export function makeFunc() {
  function nestedFunc<T extends keyof IEmpty>() {}
  return nestedFunc
}


// interface with one property
interface IOneProperty {
  a: string
}

export function funcOneProperty<T extends keyof IOneProperty>() {}

export function makeFuncOneProperty() {
  function nestedFunc<T extends keyof IOneProperty>() {}
  return nestedFunc
}


// interface with two properties
interface ITwoProperty {
  a: string
  b: string
}

export function funcTwoProperty<T extends keyof ITwoProperty>() {}

export function makeFuncTwoProperty() {
  function nestedFunc<T extends keyof ITwoProperty>() {}
  return nestedFunc
}

🙁 Actual behavior

.D.TS output:

interface IEmpty {
}
export declare function func<T extends keyof IEmpty>(): void;
export declare function makeFunc(): <T extends never>() => void;  // <-- unexpected: `keyof IEmpty` replaced by `never`
interface IOneProperty {
    a: string;
}
export declare function funcOneProperty<T extends keyof IOneProperty>(): void;
export declare function makeFuncOneProperty(): <T extends "a">() => void; // <-- same here
interface ITwoProperty {
    a: string;
    b: string;
}
export declare function funcTwoProperty<T extends keyof ITwoProperty>(): void;
export declare function makeFuncTwoProperty(): <T extends keyof ITwoProperty>() => void;
export {};

🙂 Expected behavior

.D.TS output:

interface IEmpty {
}
export declare function func<T extends keyof IEmpty>(): void;
export declare function makeFunc(): <T extends IEmpty>() => void;
interface IOneProperty {
    a: string;
}
export declare function funcOneProperty<T extends keyof IOneProperty>(): void;
export declare function makeFuncOneProperty(): <T extends IOneProperty>() => void;
interface ITwoProperty {
    a: string;
    b: string;
}
export declare function funcTwoProperty<T extends keyof ITwoProperty>(): void;
export declare function makeFuncTwoProperty(): <T extends keyof ITwoProperty>() => void;
export {};
@MartinJohns
Copy link
Contributor

Sounds like a duplicate of #17294 / #27171.

@panmenghan
Copy link
Author

More versions tested:

keyof interface(empty, all version) -> never
keyof interface(one property, all version) -> the only one property name

keyof interface(>= two properties, >= v4.2.3) -> as is
keyof interface(>= two properties, < v4.2.3) -> all the properties name

@RyanCavanaugh RyanCavanaugh added the Needs Investigation This issue needs a team member to investigate its status. label Jun 14, 2022
@RyanCavanaugh RyanCavanaugh added this to the TypeScript 4.8.1 milestone Jun 14, 2022
@weswigham
Copy link
Member

@RyanCavanaugh if we just want some kind of workaround that preserves the keyof type, #49696 should, I think, also create one - keyof (IEmpty & {}) should be preserved as a unique type after that PR, if I understand it correctly, so would be a viable workaround to prevent eager resolution of the keyof type.

@siarheiyelin
Copy link

Hello,

Unfortunately, I was unable to make the workaround above work (suggested by @weswigham keyof (IEmpty & {})).

I have the same issue in a slightly different use case. There is a TS project which contains the code like this:

type DistributiveOmit<T, K extends keyof any> = T extends any ? Omit<T, K> : never;

interface IProps {
    size: '1' | '2';
}
// It is empty during build time, but will be changed via module augmentation by external code
export interface IEmpty {}

type MyType = DistributiveOmit<IProps, keyof IEmpty> & IEmpty;
function withMods<T>() {
    // just an example
    return null as (T & { a: 1 });
}

export const Button = withMods<MyType>();

My goal is to compile the project, generate DTS, and then use module augmentation in a separate TypeScript project to dynamically modify the IEmpty interface.

Actual behavior
After compilation, the corresponding DTS includes a type definition for the Button, where the keyof IEmpty statement has been replaced with never. This makes it impossible to use module augmentation to modify the IEmpty interface. And therefore, the type of Button which depends on IEmpty will always remain unchanged.

Code sandbox: link

Expected behavior
Is there a way to explicitly prevent this behavior and preserve keyof IEmpty statement in the above use cases?

Thanks in advance.

@panmenghan
Copy link
Author

Fixed in v5.5.2 😄

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Needs Investigation This issue needs a team member to investigate its status. Rescheduled This issue was previously scheduled to an earlier milestone
Projects
None yet
Development

No branches or pull requests

5 participants