Skip to content

Array.flatten and pipe losing type information, inducing run time crash #5963

@FredericEspiau

Description

@FredericEspiau

What version of Effect is running?

3.19.13

What steps can reproduce the bug?

Not sure you can reproduce the bug with this but the situation is similar to the one I describe here, also I propose a solution at the end of this message.

declare const arg1: Effect.Effect<
  void,
  Error1 | Error2,
  Requirement1 | Requirement2
>;

declare const arg2: Effect.Effect<
  void,
  Error1 | Error2,
  Requirement1 | Requirement2 | Requirement3
>

const arg1BeforeArg2 = pipe(
    [
      arg1,
      arg2
    ],
    Array.flatten,
  );

const arg2BeforeArg1 = pipe(
    [
      arg2,
      arg1
    ],
    Array.flatten,
  );

What is the expected behavior?

Same signature for both arg1BeforeArg2 and arg2BeforeArg1

arg1BeforeArg2;
// ^
// Effect.Effect<
//   void,
//   Error1 | Error2,
//   Requirement1 | Requirement2 | Requirement3
// >

arg2BeforeArg1;
// ^
// Effect.Effect<
//   void,
//   Error1 | Error2,
//   Requirement1 | Requirement2 | Requirement3
// >

What do you see instead?

arg1BeforeArg2;
// ^
// Effect.Effect<
//   void,
//   Error1 | Error2,
//   Requirement1 | Requirement2
// >

arg1BeforeArg2;
// ^
// Effect.Effect<
//   void,
//   Error1 | Error2,
//   Requirement1 | Requirement2 | Requirement3
// >

The order of the items in the array shouldn't change the union type for Requirements.

Of course, this lead to a runtime error as we couldn't know that we were supposed to provide Requirement3 as the type system wasn't aware of it

Additional information

I was able to make it work by changing the type returned by the flatten function to be the one from this great library: https://github.com/inocan-group/inferred-types/blob/12da2b1282f2bab01f1e1d450aa87aa3bed85bcc/modules/types/src/lists/Flatten.ts

But it's really complex type 😅.

// packages/effect/src/Array.ts
import { Flatten } from 'inferred-types';

export const flatten: <const S extends ReadonlyArray<ReadonlyArray<any>>>(self: S) => Flatten<S> = flatMap(
  identity
) as any

Also by adding a const type parameter in pipe

// packages/effect/src/Pipeable.ts
export interface Pipeable {
  pipe<const A>(this: A): A
  pipe<const A, B = never>(this: A, ab: (_: A) => B): B
  pipe<const A, B = never, C = never>(this: A, ab: (_: A) => B, bc: (_: B) => C): C
  ...
}
// packages/effect/src/Function.ts
export function pipe<const A>(a: A): A
export function pipe<const A, B = never>(a: A, ab: (a: A) => B): B
export function pipe<const A, B = never, C = never>(
  a: A,
  ab: (a: A) => B,
  bc: (b: B) => C
): C
...

with both those changes, the type is inferred correctly.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions