Skip to content

Submodule functor call is curried, but the target function is uncurried #7245

@dsiu

Description

@dsiu

I am porting bs-bastet to modern ReScript (v12+) (WIP at https://github.com/dsiu/rescript-bastet/tree/rescript-v12)

In rescript@12.0.0-alpha.7 (alpha.4 and later), the generated js for nested module functor invocation is incorrect.

The line:

let EQ = M.Eq(IntEq)(ArbitraryInt);

is assuming M.eq is curried while M.eq is defined as:

function Eq(E, A) {
  let suite = name => "suite: " + name;
  return {
    suite: suite
  };
}

let M = {
  Eq: Eq
};

which clearly isn't curried.

The error is:

{
  "message":"M.Eq(IntEq) is not a function. (In 'M.Eq(IntEq)(ArbitraryInt)', 'M.Eq(IntEq)' is an instance of Object)",
  "line":54,
  "column":21,
  "stack":"eval code@\neval@[native code]\n@about:srcdoc:39:20"
}

In rescript@11, the error line was compiled to :

let EQ = Curry._2(Eq, IntEq, ArbitraryInt);

which runs correctly.

Playground link for test code

Test Code

module type TEST = {
  type test
}

module type ARBITRARY = {
  type t
  type arbitrary<'a>
}

module type QUICKCHECK = {
  type t
  type arbitrary<'a>
}

module Interface = {
  module type EQ = {
    type t

    let eq: (t, t) => bool
  }
}

module Make = (T: TEST, Q: QUICKCHECK with type t = T.test) => {
  type t
  type arbitrary<'a>

  module Eq = (
    E: Interface.EQ,
    A: ARBITRARY with type t := E.t and type arbitrary<'a> := Q.arbitrary<'a>,
  ) => {
    let suite = name => "suite: " ++ name
  }
}

module IntWithTest = (
  T: TEST,
  Q: QUICKCHECK with type t = T.test,
  A: ARBITRARY with type t := int and type arbitrary<'a> := Q.arbitrary<'a>,
) => {
  module IntEq: Interface.EQ = {
    type t = int

    let eq = (a, b) => Int.equal(a, b)
  }
  module M = Make(T, Q)
  module EQ = M.Eq(IntEq, A)
}

module ArbitraryInt: ARBITRARY with type t = int and type arbitrary<'a> = array<'a> = {
  type t = int
  type arbitrary<'a> = array<'a>
}

module JsQuickCheck = {
  type t = int
  type arbitrary<'a> = array<'a>
}

module MochaTest = {
  type test = int
}

module TestInt = IntWithTest(MochaTest, JsQuickCheck, ArbitraryInt)

TestInt.EQ.suite->Console.log
// Generated by ReScript, PLEASE EDIT WITH CARE


let Interface = {};

function Make(T, Q) {
  let Eq = (E, A) => {
    let suite = name => "suite: " + name;
    return {
      suite: suite
    };
  };
  return {
    Eq: Eq
  };
}

function IntWithTest(T, Q, A) {
  let eq = (a, b) => a === b;
  let IntEq = {
    eq: eq
  };
  let Eq = (E, A) => {
    let suite = name => "suite: " + name;
    return {
      suite: suite
    };
  };
  let M = {
    Eq: Eq
  };
  let EQ = M.Eq(IntEq)(A);
  return {
    IntEq: IntEq,
    M: M,
    EQ: EQ
  };
}

let ArbitraryInt = {};

let JsQuickCheck = {};

let MochaTest = {};

function eq(a, b) {
  return a === b;
}

let IntEq = {
  eq: eq
};

function Eq(E, A) {
  let suite = name => "suite: " + name;
  return {
    suite: suite
  };
}

let M = {
  Eq: Eq
};

let EQ = M.Eq(IntEq)(ArbitraryInt);

let TestInt = {
  IntEq: IntEq,
  M: M,
  EQ: EQ
};

console.log(EQ.suite);

export {
  Interface,
  Make,
  IntWithTest,
  ArbitraryInt,
  JsQuickCheck,
  MochaTest,
  TestInt,
}
/* EQ Not a pure module */

Thank you for filing! Check list:

  • Is it a bug? Usage questions should often be asked in the forum instead.
    Concise, focused, friendly issue title & description.
    OS and browser versions, if relevant.
    Is it already fixed in master? Instructions

Activity

changed the title [-]Sub module functor invocation is generated incorrectly with curried sematic while the target function definition is generated as uncurried[/-] [+]Submodule functor call is curried, but the target function is uncurried[/+] on Jan 12, 2025
cristianoc

cristianoc commented on Jan 27, 2025

@cristianoc
Collaborator

Here's a small example:

module Make = (T: {}, Q: {}) => {
  module Eq = (E: {}, A: {}) => {}
}

module M = Make({}, {})

module EQ = M.Eq({}, {})

produces

function Make(T, Q) {
  let Eq = (E, A) => ({});
  return {
    Eq: Eq
  };
}

function Eq(E, A) {
  return {};
}

let M = {
  Eq: Eq
};

let EQ = M.Eq({})({});

Seems like a certain level of nesting is required to observe this.

dsiu

dsiu commented on Feb 2, 2025

@dsiu
Author

@cristianoc : is this issue hard to fix? it was working on v12-alpha.3 and before so the bug was introduced recently. thanks a lot.

cristianoc

cristianoc commented on Feb 2, 2025

@cristianoc
Collaborator

Perhaps one could always emit functors as functions taking one argument at a time, unless there are some issue that could come with that.

cristianoc

cristianoc commented on Feb 2, 2025

@cristianoc
Collaborator

Something like this: #7273
If it works and it is safe, one can explore further.

cristianoc

cristianoc commented on Feb 5, 2025

@cristianoc
Collaborator

@dsiu could you try to install the package from CI and see if it works for you?

dsiu

dsiu commented on Feb 5, 2025

@dsiu
Author

@cristianoc sure thing. Will revert back once I tested it. Thanks!

dsiu

dsiu commented on Feb 6, 2025

@dsiu
Author

@cristianoc, I'm happy to report that #7273 fixes the problem. I ran it against my larger code base, and it only changes the module functor invocations, not any other code. I think it is good to go.

Thanks again.

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

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

      Development

      No branches or pull requests

        Participants

        @dsiu@cristianoc

        Issue actions

          Submodule functor call is curried, but the target function is uncurried · Issue #7245 · rescript-lang/rescript