Search code examples
typesocamlfunctorreasonbucklescript

ReasonML Type mismatch with same type


I'm receiving a type mismatch even though I'm using the same type in ReasonML. The error is:

[1]   We've found a bug for you!
[1]   /Users/gt/work/real-and-open/frontend/src/domain/classroom/views/RosterLayoutHeader.re 42:10-70
[1]
[1]   40 ┆ <Classroom.Mutation.AddStudent>
[1]   41 ┆   ...{
[1]   42 ┆     (addStudent: (~id: UUID.t, ~classroomId: UUID.t, unit) => unit) =>
[1]         {
[1]   43 ┆       <div>
[1]   44 ┆         <StudentRowHeader
[1]
[1]   This pattern matches values of type
[1]     (~id: UUID.t, ~classroomId: UUID.t, unit) => unit
[1]   but a pattern was expected which matches values of type
[1]     AddStudent.ContainerMutation.mutationFunctionType (defined as
[1]       AddStudent.MutationInternals.mutationFunctionType)

When I replace addStudent: (~id: UUID.t, ~classroomId: UUID.t, unit) => unit with addStudent: AddStudent.MutationInternals.mutationFunctionType

The error becomes:

[1]   We've found a bug for you!
[1]   /Users/gt/work/real-and-open/frontend/src/domain/classroom/views/RosterLayoutHeader.re 53:61-70
[1]
[1]   51 ┆ _ =>
[1]   52 ┆   updateClassroom(
[1]   53 ┆     Classroom.Action.ApolloAddStudent(() => addStudent(
[1]   54 ┆       ~id=classroom.local.newStudentId |> Student.Model.getUUIDFromId,
[1]   55 ┆       ~classroomId=classroom.data.id,
[1]
[1]   This expression has type AddStudent.MutationInternals.mutationFunctionType
[1]   It is not a function.

In the code AddStudent looks like the following:

[@bs.config {jsx: 3}];
module Mutation = [%graphql
  {|
    mutation addStudent($id: ID!, $classroomId: ID!) {
      addStudent(student: {id: $id, classroomId: $classroomId}){
        ...Classroom_Model.Fragment.ClassroomFields
      }
    }
  |}
];

module MutationInternals : ApolloMutation.MutationInternal = {
  type mutationFunctionType = (~id: UUID.t, ~classroomId: UUID.t, unit) => unit;
  let componentName = "AddStudent";

  module Config = Mutation;
  module InternalMutation = ReasonApollo.CreateMutation(Config);

  let callMutationWithApollo = (apolloMutation : ApolloMutation.apolloMutationType(Config.t)) => 
    ((~id: UUID.t, ~classroomId: UUID.t, ()): unit => {
      let newStudent = Config.make(~id, ~classroomId, ());
      apolloMutation(
        ~variables=newStudent##variables,
        // ~refetchQueries=[|"member"|],
        // ~optimisticResponse=Config.t,
        (),
      ) |> ignore;
      () |> ignore;
    }: mutationFunctionType);
};

module ContainerMutation = ApolloMutation.CreateMutationContainer(MutationInternals);

module Jsx2 = ContainerMutation.Jsx2;
let make = ContainerMutation.make;

So notice 3 things

1) The type: type mutationFunctionType = (~id: UUID.t, ~classroomId: UUID.t, unit) => unit; is referenced here and in the error it says AddStudent.MutationInternals.mutationFunctionType is mismatched with (~id: UUID.t, ~classroomId: UUID.t, unit) => unit even though they are the same.

2) When I reference the type directly in the calling function it says mutationFunctionType is not a function and it is a function.

3) That I am using a Functor to process my MutationInternals modules... I wonder if that affects the types.

Thanks


Solution

  • The AddStudent.MutationInternals.mutationFunctionType type is abstract and the compiler doesn't know that underneath the hood it actually is implemented as a function of type

    (~id: UUID.t, ~classroomId: UUID.t, unit) => unit
    

    It is hard to guess, what is the root of the problem in your case. Either you accidentally over abstracted your code, by sealing your module, or you are trying to break the necessary abstraction. In any case, compiler is stopping your from doing it.

    Usually, this kind of errors happen when you seal your module, e.g., when you add a module type, e.g.,

    module type S = {type t};
    module M : S = { type t = int};
    

    This makes the type M.t abstract, so it won't be usable in the context where int is expected. The solution is to either remove this unnecessary sealing, e.g.,

    module type S = {type t};
    module M = { type t = int};
    

    or tell the compiler explicitly, that t is not an abstract type, but a concrete int, e.g.,

    module type S = {type t};
    module M: S with type t = int = {
      type t = int;
    };
    

    Note, in your case I'm talking about this piece of code

    MutationInternals : ApolloMutation.MutationInternal
    

    which hides the mutationFunctionType definition.