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

“Unused” function in generic interface changes inferred return type of other function #61002

Closed
max-m opened this issue Jan 20, 2025 · 2 comments
Labels
Working as Intended The behavior described is the intended behavior; this is not a bug

Comments

@max-m
Copy link

max-m commented Jan 20, 2025

🔎 Search Terms

  • extend generic interface inference
  • different variations of the issue title

🕗 Version & Regression Information

  • This is the behavior in every version I tried (4.0.5 to 5.7.3), and I reviewed the FAQ for entries about type inference on interface Foo<T> { }

⏯ Playground Link

https://www.typescriptlang.org/play/?#code/C4TwDgpgBAshBOBzCAeAKgGigVQHxQF4o0oIAPYCAOwBMBnKAQypAG0BdKAfilYDoBmHJwBcvIdnYBuAFAzyYAPbxgUAJZVK8AGaMAxtACSABUbw6CdPgDeMqFDDxFYMWll2owABbUUATVwAChcoEzMLeH9cAEoxMPNLOCRUIQDcKSgAekyoAGFFAFsC6lVlKHgIAsUAN2hvNQYAGw06xSg9L2ZkTx91Km0ECppPcGhFbSgAAz1FKjpVNHg1RGR4Sbl7ZSjguNMEyIDY0L2I9CgAHygA2QBfOQ0tXQMoE8s0fHJKWgZ40-eoWz2RzOVy3OTZCGQqHQmGwuHwhGIpHI2FyGgQPSNMzQGZzVQVYAAV3gczEVkC1Vc0UI+FekXe7nRmOx7Vm8ygABkINpgHTduFLPMllREBcoFRCY1Guk5Lj2YtlqtCJzubyBfA+MpAgTiXNAhKpdFohlslAAMpeRSS4YAIyMdJQQo0osuBulMiAA

💻 Code

type Merge<T, U> = T extends any[] ? [...T, U] : [T, U];

export interface IParser<T> {
  prop: T;

  then<Y>(p: IParser<Y>): IParser<Merge<T, Y>>; // Comment or remove this line to change the inferred type of `const Trigger`

  or<Y>(p: IParser<Y>): IParser<T | Y>;
}

interface Parser<T> extends IParser<T> {
  prop: T;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////

declare const returns: <T>(v: T) => Parser<T>;

declare const LeftParser: IParser<string | null>;

const Trigger = LeftParser.or(returns(null)); // Should be IParser<string | null>

🙁 Actual behavior

The type of const Trigger gets inferred as IParser<string | [null] | null>.

🙂 Expected behavior

The type of const Trigger gets inferred as IParser<string | null> or IParser<unknown>, the [null] should not be part of the type signature.

Additional information about the issue

I’ve been working on custom typings for Masala Parser in version 0.6 and hit this problem.
Changing line 17 to declare const returns: <T>(v: T) => IParser<T>; fixes the problem as well.

The complete code can be found here: https://gist.github.com/max-m/7678f14661bfe3f6028593c96b66c0b5
The official documentation of Masala Parser’s then function can be found here.

@RyanCavanaugh
Copy link
Member

This behavior follows from structural inference. To spell it out:

declare const LeftParser: IParser<string | null>;
const p = returns(null);
const Trigger = LeftParser.or(p);

the type of p here is

{
    prop: null;
    then: <Y>(p: IParser<Y>) => IParser<[null, Y]>;
    or: <Y>(p: IParser<Y>) => IParser<Y | null>;
}

So when we're doing structural inference, there's a match on the type of then wherein a valid candidate inference site for T is IParser<Merge<T, Y>> <-> IParser<[null, Y]>, and [null, Y] is inferred into Merge<T, U>, which is a match for extends any[] and it appears that [null] is the thing you can substitute for T to get [null, Y] as an output.

@RyanCavanaugh RyanCavanaugh added the Working as Intended The behavior described is the intended behavior; this is not a bug label Jan 21, 2025
@max-m
Copy link
Author

max-m commented Jan 21, 2025

Thank you very much! I had a feeling that it would be something like that, but couldn’t quite put my finger on it.
Changing the Merge type to type Merge<T, U> = T extends [...rest: infer R] ? [...R, U] : [T, U] seems to fix the inferred types for all cases.

@max-m max-m closed this as not planned Won't fix, can't repro, duplicate, stale Jan 21, 2025
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

2 participants