Skip to content

Generics inside array doesn't determined correctly #47393

Closed

Description

Bug Report

🔎 Search Terms

Generics inside array doesn't determined correctly

🕗 Version & Regression Information

  • This changed between versions 3.8.3 and 3.9.7 - types inferred correctly if function argument is omited.
  • If the argument is present not working in any version.

⏯ Playground Link

Playground link with relevant code

💻 Code

interface IPipe<I, O> { }
interface Pipeline<I, O> { }

class Bus<T> {
    pipe<A>(pipes: [IPipe<T, A>]): Pipeline<T, A>;
    pipe<A, B>(pipes: [IPipe<T, A>, IPipe<A, B>]): Pipeline<T, B>;
    pipe<A, B, C>(pipes: [IPipe<T, A>, IPipe<A, B>, IPipe<B, C>]): Pipeline<T, C>;
    pipe<A, B, C, D>(pipes: [IPipe<T, A>, IPipe<A, B>, IPipe<B, C>, IPipe<C, D>]): Pipeline<T, D>;
    pipe(pipes: IPipe<any, any>[]): Pipeline<any, any> {
        return null;
    }
}

function map<I, O>(mapper: (value: I) => O): IPipe<I, O> { return; }

const bus = new Bus<number>();

bus.pipe([
    map(() => ``),
    map(() => [true]),
    map(() => [``]),
    map(() => false)
]);

🙁 Actual behavior

Every next pipe first generic argument determined as Bus basic type.

bus.pipe(
   map((x) => ``),
     // ^ x: number
   map((x) => [true]),
     // ^ x: number
   map((x) => [``]),
     // ^ x: number
   map((x) => false)
     // ^ x: number
);

🙂 Expected behavior

Every next pipe first generic argument should be based on previous pipe second generic type.

bus.pipe(
   map((x) => ``),
     // ^ x: number
   map((x) => [true]),
     // ^ x: string
   map((x) => [``]),
     // ^ x: boolean[]
   map((x) => false)
     // ^ x: string[]
);

Activity

  1. whzx5byb commented on Jan 12, 2022

    @whzx5byb
    • This changed between versions 3.8.3 and 3.9.7

    Actually your example doesn't work in 3.8.3. Add the omitted first argument in the functions like bus.pipe(map((x) => ``), map((x) => [true]), map((x) => [``]), map((x) => false)); and these x will still be inferred as number.

    And I would suggest using variadic arguments as workaround.

    interface IPipe<I, O> { }
    interface Pipeline<I, O> { }
    
    declare class Bus<T> {
        pipe<A>(...pipes: [IPipe<T, A>]): Pipeline<T, A>;
        pipe<A, B>(...pipes: [IPipe<T, A>, IPipe<A, B>]): Pipeline<T, B>;
        pipe<A, B, C>(...pipes: [IPipe<T, A>, IPipe<A, B>, IPipe<B, C>]): Pipeline<T, C>;
        pipe<A, B, C, D>(...pipes: [IPipe<T, A>, IPipe<A, B>, IPipe<B, C>, IPipe<C, D>]): Pipeline<T, D>;
    }
    
    declare function map<I, O>(mapper: (value: I) => O): IPipe<I, O>
    
    const bus = new Bus<number>();
    
    bus.pipe(
        map((x) => ``),
          // ^ x: number
        map((x) => [true]),
          // ^ x: string
        map((x) => [``]),
          // ^ x: boolean[]
        map((x) => false)
          // ^ x: string[]
    );

    Playground

  2. Flairax commented on Jan 12, 2022

    @Flairax
    Author

    whzx5byb yes, you are right, doesn't seems to ever worked. Unfortunately variadic arguments is not an option because in my code I want to use few more arguments after pipes.

    interface IConfig<I, O> {}
    
    class Bus<T> {
       pipe<A>(pipes: [IPipe<T, A>], config?: IConfig<T, A>): Pipeline<T, A>;
       pipe<A, B>(pipes: [IPipe<T, A>, IPipe<A, B>], config?: IConfig<T, B>): Pipeline<T, B>;
       pipe<A, B, C>(pipes: [IPipe<T, A>, IPipe<A, B>, IPipe<B, C>], config?: IConfig<T, C>): Pipeline<T, C>;
       pipe<A, B, C, D>(pipes: [IPipe<T, A>, IPipe<A, B>, IPipe<B, C>, IPipe<C, D>], config?: IConfig<T, D>): Pipeline<T, D>;
       pipe(pipes: IPipe<any, any>[]): Pipeline<any, any> {
          return null;
       }
    }

    Because of this such solution is also not fitting

    class Bus<T> {
       pipe<A>(pipe_1: IPipe<T, A>): Pipeline<T, A>;
       pipe<A, B>(pipe_1: IPipe<T, A>, pipe_2: IPipe<A, B>): Pipeline<T, B>;
       pipe<A, B, C>(pipe_1: IPipe<T, A>, pipe_2: IPipe<A, B>, pipe_3: IPipe<B, C>): Pipeline<T, C>;
       pipe<A, B, C, D>(pipe_1: IPipe<T, A>, pipe_2: IPipe<A, B>, pipe_3: IPipe<B, C>, pipe_4: IPipe<C, D>): Pipeline<T, D>;
       pipe(pipes: IPipe<any, any>[]): Pipeline<any, any> {
          return null;
       }
    }

    Thanks anyway :)

  3. RyanCavanaugh commented on Jan 12, 2022

    @RyanCavanaugh
    Member

    I don't understand what the actual vs expected problem is. Can you state it more clearly?

  4. Flairax commented on Jan 12, 2022

    @Flairax
    Author

    Ryan Cavanaugh (@RyanCavanaugh), changed description in whzx5byb way. Think it more clearer now.

  5. added
    Design LimitationConstraints of the existing architecture prevent this from being fixed
    and removed
    Needs More InfoThe issue still hasn't been fully clarified
    on Jan 12, 2022
  6. RyanCavanaugh commented on Jan 13, 2022

    @RyanCavanaugh
    Member

    Thanks, that's much clearer.

    Generic inference has a fixed number of passes for fixing type parameters and isn't capable of inferring this sort of pattern.

    See also #30134

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Design LimitationConstraints of the existing architecture prevent this from being fixed

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions