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

Exported const type parameters is not renamed in constraint in another const in type declarations #56783

Open
mcheshkov opened this issue Dec 14, 2023 · 4 comments
Assignees
Labels
Bug A bug in TypeScript

Comments

@mcheshkov
Copy link

🔎 Search Terms

Generic methods
Constraints
Type parameters renaming
Type declarations

🕗 Version & Regression Information

⏯ Playground Link

https://www.typescriptlang.org/play?target=7&allowSyntheticDefaultImports=false&ts=5.3.2#code/C4TwDgpgBACgTAHgDIBooCUB8UC8UlQBkUA3lANoDSUAlgHZQDWEIA9gGYYC6AXFHQFcAtgCMIAJygBfANwBYAFCLQkWAgAq2PGSq0GzNp3W9+wsZKmLFAY1Z0AzsCgAxVq1ylFUb1HYIAklAQAB7AEHQAJvawiCRSaP6YmAAUAJSk8V4+AOYBQaHhUWqJKelxKIqyVgq2Dk4AQgCGktpZ3n6BIWGR0fAI5VAlaRkVCj5QuZ0FPcVJw+WV8koKIWCs4k61jlBNAF4eJG2+eV2FvbHxg3NlmWM5J9NFMAHXI4vVq+ubdtsAIhCcVp3dpuPiuVijcYiZp8JriSE+aG7WGNXajKrLIA

💻 Code

type P2<L, R> = L & { [K in keyof R]: number };

type P<T> = { [K in keyof T]: number }

const Foo = {
    f<I extends P2<{}, I>>() {},
    g<I extends P<I>>() {},
};

const Bar = {
    f<I extends P2<{}, I>>() {},
    g<I extends P<I>>() {},
};

export const Baz = {
    f<I extends P2<{}, I>>() {},
    g<I extends P<I>>() {},
};

export const Def = {
    foo: Foo,
    bar: Bar,
    baz: Baz,
};

🙁 Actual behavior

Generated .d.ts on v5.3.2 looks like this:

type P<T> = {
    [K in keyof T]: number;
};
export declare const Baz: {
    f<I extends { [K in keyof I]: number; }>(): void;
    g<I_1 extends P<I_1>>(): void;
};
export declare const Def: {
    foo: {
        f<I extends { [K in keyof I]: number; }>(): void;
        g<I_1 extends P<I_1>>(): void;
    };
    bar: {
        f<I_2 extends { [K_1 in keyof I_2]: number; }>(): void;
        g<I_3 extends P<I_3>>(): void;
    };
    baz: {
        f<I_4 extends { [K in keyof I]: number; }>(): void;
        g<I_5 extends P<I_5>>(): void;
    };
};
export {};

Notice the line f<I_4 extends { [K in keyof I]: number; }>(): void; in baz, there's no I in scope.
Same line in bar, that's f<I_2 extends { [K_1 in keyof I_2]: number; }>(): void; has type parameters correctly renamed, which points to exported consts specifically.
g<I_5 extends P<I_5>>(): void; also have correct type params, so simple generic type like P is not enough.

🙂 Expected behavior

Generated .d.ts on v4.3.5 looks like this:

declare type P<T> = {
    [K in keyof T]: number;
};
export declare const Baz: {
    f<I extends { [K in keyof I]: number; }>(): void;
    g<I_1 extends P<I_1>>(): void;
};
export declare const Def: {
    foo: {
        f<I extends { [K in keyof I]: number; }>(): void;
        g<I_1 extends P<I_1>>(): void;
    };
    bar: {
        f<I_2 extends { [K_1 in keyof I_2]: number; }>(): void;
        g<I_3 extends P<I_3>>(): void;
    };
    baz: {
        f<I_4 extends { [K_2 in keyof I_4]: number; }>(): void;
        g<I_5 extends P<I_5>>(): void;
    };
};
export {};

Notice the line f<I_4 extends { [K_2 in keyof I_4]: number; }>(): void;, all type parameters nave been correctly renamed.

Additional information about the issue

Bisected commit passes sniff test: it introduces type caching and accessibility tracking, so maybe some of that triggers when emitting first constant, and poisons cache for visitDeclarationSubtree -> ... -> signatureToSignatureDeclarationHelper -> ... -> typeParameterToDeclaration call later

Origin of this issue is code generated by ts-proto with outputServices=generic-definitions option. See gist for example. Problem occuring here, on lines 70-79 in a.d.ts, where create and fromPartial have renamed type parameter, but original constraint (seemingly, from export declare const FooResponse)

@fatcerberus
Copy link

I_4 extends { [K_2 in keyof I_4]: number; }

I’m not sure how to interpret this constraint - the definition seems circular.

@RyanCavanaugh RyanCavanaugh added the Bug A bug in TypeScript label Dec 14, 2023
@RyanCavanaugh RyanCavanaugh added this to the TypeScript 5.4.0 milestone Dec 14, 2023
@RyanCavanaugh
Copy link
Member

Have you checked this in nightly? We recently had a fix in this area that may have already addressed it

@mcheshkov
Copy link
Author

Nightly on playground (v5.4.0-dev.20231215) generates broken typings as well: f<I_4 extends { [K in keyof I]: number; }>(): void;

@mcheshkov
Copy link
Author

@fatcerberus it can read as "number should be assignable to every property of I_4"

In ts-proto similar construction is used for exact types. Rougly like this: f<I extends ConcreteType & { [K in Exclude<keyof I, keyof ConcreteType>]: never; }>(i: I), meaning that every property in I, that is not a propery of ConcreteType should be never, rendering type with any extra property never.

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

No branches or pull requests

4 participants