Migration from ReasonML: new type errors

Hello everyone.

I am trying to migrate from ReasonML to rescript (v9), and I am stuck here:

  We've found a bug for you!
  /home/ju/workspace/nomadic-labs/umami-wallet/umami/src/api/MetadataAPI.res:301:13-22

  299 ┆ 
  300 ┆ let readContractMetadata = (contract: Tzip12Tzip16Contract.t) =>
  301 ┆   contract["tzip16"]().getMetadata(.)
  302 ┆   ->ReTaquitoError.fromPromiseParsed
  303 ┆   ->Promise.mapError(x =>

  This expression has type
    Js_OO.Meth.arity0<\"ReTaquitoTypes-Umami".Tzip16.t>
  It is not a function.

Here is the source code: Files · sagotch@rescript-migration · Nomadic Labs / Umami Wallet group / umami · GitLab

And here are the files that might be interesting:

$ git grep tzip16
package.json:    "@taquito/tzip16": "12.0.3",
src/api/MetadataAPI.res:  toolkit->Toolkit.addExtension(ReTaquitoContracts.Extension.tzip16Module())
src/api/MetadataAPI.res:    toolkit->Toolkit.addExtension(Extension.tzip16Module())
src/api/MetadataAPI.res:    contract["tzip16"]().getMetadata(.)
src/api/MetadataAPI.res:    toolkit->Toolkit.addExtension(Extension.tzip16Module())
src/api/MetadataAPI.res:    contract["tzip16"]().getMetadata(.)
src/vendors/ReTaquitoContracts.res:  @module("@taquito/tzip16") @new
src/vendors/ReTaquitoContracts.res:  external tzip16Module: unit => t = "Tzip16Module"
src/vendors/ReTaquitoContracts.res:  @module("@taquito/tzip16") external tzip16: abstraction = "tzip16"
src/vendors/ReTaquitoContracts.res:  let at = (contract, pkh) => at(contract, pkh, ~extension=Extension.tzip16)
src/vendors/ReTaquitoContracts.res:        compose(tzip12, tzip16)
src/vendors/ReTaquitoContracts.res:        compose(tzip12, tzip16)
src/vendors/ReTaquitoContracts.res:        compose(tzip12, tzip16)
src/vendors/ReTaquitoTypes.res:    "tzip16": unit => Tzip16.t,
src/vendors/ReTaquitoTypes.res:    "tzip16": unit => Tzip16.t,
src/vendors/ReTaquitoTypes.res:    "tzip16": unit => Tzip16.t,
src/vendors/ReTaquitoTypes.res:    "tzip16": unit => Tzip16.t,
yarn.lock:    "@taquito/tzip16" "^12.0.3"
yarn.lock:"@taquito/tzip16@12.0.3", "@taquito/tzip16@^12.0.3":

(Can’t post more than 2 links)

Any hint?

PS: I already saw Upgrading to 9.1 and calling methods and tried the workaround explained without success (I did not keep the code to show, sorry).

PPS: I am from the OCaml world, so a lot of things that may be obvious are likely to be not that obvious to me.

Can you show the type Tzip12Tzip16Contract.t?

Would you try one of two?

// 1
(contract["tzip16"])()

// 2
let tzip = contract["tzip16"]
tzip()

First, thanks for your answer.

Unfortunately, both of your proposals throw the same error.

Here is the Tzip12Tzip16Contract.t type definition

module Tzip12Tzip16Contract = {
  type methods
  type storage = Tzip12Storage.storage
  type entrypoints

  type t = {
    "address": PublicKeyHash.t,
    "entrypoints": entrypoints,
    "methods": methods,
    @meth
    "storage": unit => Js.Promise.t<storage>,
    @meth
    "tzip12": unit => Tzip12.t,
    @meth
    "tzip16": unit => Tzip16.t,
  }
}

You linked to this: Upgrading to 9.1 and calling methods , and said that fix didn’t work. You mentioned not keeping the code to show why it didn’t work, but still, you will have to restructure your code to use @send or something other than method.

What about this link: @meth decorator-related error due to Reason → Rescript Migration . Same problem really, you can’t use the method decorator.

@@ -336,17 +336,13 @@ module Tzip12Tzip16Contract = {
   type storage = Tzip12Storage.storage
   type entrypoints
 
-  type t = {
-    "address": PublicKeyHash.t,
-    "entrypoints": entrypoints,
-    "methods": methods,
-    @meth
-    "storage": unit => Js.Promise.t<storage>,
-    @meth
-    "tzip12": unit => Tzip12.t,
-    @meth
-    "tzip16": unit => Tzip16.t,
-  }
+  type t
+  @val external address: PublicKeyHash.t = "address"
+  @val external entrypoints: entrypoints = "entrypoints"
+  @val external methods: methods = "methods"
+  @send external storage: (t, unit) => Js.Promise.t<storage> = "storage"
+  @send external tzip12: (t, unit) => Tzip12.t = "tzip12"
+  @send external tzip16: (t, unit) => Tzip16.t = "tzip16"
 }
@@ -298,7 +298,7 @@ module Tzip12 = {
   }
 
   let readContractMetadata = (contract: Tzip12Tzip16Contract.t) =>
-    contract["tzip16"]().getMetadata(.)
+    contract->tzip16.getMetadata(.)
     ->ReTaquitoError.fromPromiseParsed
     ->Promise.mapError(x =>
       switch x {

And I get this error (the same as the one I had when I tried the first time):

  299 ┆ 
  300 ┆ let readContractMetadata = (contract: Tzip12Tzip16Contract.t) =>
  301 ┆   contract->tzip16.getMetadata(.)
  302 ┆   ->ReTaquitoError.fromPromiseParsed
  303 ┆   ->Promise.mapError(x =>

  This has type:
    Umami.ReTaquitoContracts.Tzip12Tzip16Contract.t (defined as
      \"ReTaquitoTypes-Umami".Tzip12Tzip16Contract.t)
  Somewhere wanted: (. 'a) => 'b

Note: The contract on line 301 is the part highlighted in the console. I tried various parenthesing, removing the unit thing, error message changes, but error message is here anyway :confused:

Note that I will have to make a couple of assumptions about your code, which may be incorrect, but hopefully this will help get you on the right track.


You have got a couple of issues that I can see right away.

+  @send external storage: (t, unit) => Js.Promise.t<storage> = "storage"
+  @send external tzip12: (t, unit) => Tzip12.t = "tzip12"
+  @send external tzip16: (t, unit) => Tzip16.t = "tzip16"

should be like this:

  @send external storage: t => Js.Promise.t<storage> = "storage"
  @send external tzip12: t => Tzip12.t = "tzip12"
  @send external tzip16: t => Tzip16.t = "tzip16"

Each take one argument, t and return whatever. You don’t want them to take t and unit.

Here’s an example of what I mean about the extra unit argument (playground):

type x
@module("x") external make_x: unit => x = "make"
// what you probably want
@send external foo_1: x => int = "foo"
// what you probably don't want
@send external foo_2: (x, unit) => int = "foo"

let an_x = make_x()
let y = foo_1(an_x)
let y' = foo_2(an_x, ())

Next you have contract->tzip16. As you have the @send currently defined, you would need to call it like this: contract->Tzip12Tzip16Contract.tzip16() as it is now a function on the Tzip12Tzip16Contract module. However, if you change the @send as I did above to not have the extra unit argument that isn’t needed it would be contract->Tzip12Tzip16Contract.tzip16 (without the trailing ()).


Next, I see you are trying to “call” .getMetadata(.) right on contract->tzip16, as if it were a “method” on an object of type Tzip16. That won’t work either. Now, based on how you’re trying to use it, I’m going to assume your getMetadata has a signature like this: (. Tzip16.t) => 'a, and is defined in the Tzip16 module. (If it doesn’t you may still get the idea though, just adjust it so for your particular needs.)

The trick here is that if you want to use the pipe (->), you will need to specify the location of the argument like ->Tzip16.getMetadata(. _), using the _. This is because you want to use it uncurried.


Putting it all together, the first part of your readContractMetadata would be something like this:

let readContractMetadata = (contract: Tzip12Tzip16Contract.t) => {
  contract
    ->Tzip12Tzip16Contract.tzip16
    ->Tzip16.getMetadata(. _)
    -> ... more stuff here ...
}

If you want to see a playground with the changes to the @send and to see how it all typechecks, check it out here.

Oops… I realised that rescipt is using types from another file.

ReTaquitoTypes:

module type ContractAbstraction = {
  /* This corresponds to ContractAbstraction for the TaquitoAPI */

  /* Abstract storage representation */
  type storage
  /* Entrypoints representation in Michelson */
  type entrypoints
  /* Concrete entrypoints calls */
  type methods

  /* Taquito's `ContractAbstraction` object. */
  type t
}

ReTaquitoContracts:

module Types = ReTaquitoTypes

module Contract = (Abstraction: Types.ContractAbstraction) => {
  include Abstraction

  @send
  external at: (
    Types.Toolkit.contract,
    PublicKeyHash.t,
    option<Extension.abstraction>,
  ) => Js.Promise.t<t> = "at"

  let at = (~extension=?, contract, pkh) => at(contract, pkh, extension)
}

module Tzip12Tzip16Contract = {
  include Contract(Types.Tzip12Tzip16Contract)

  let at = (contract, pkh) =>
    at(
      contract,
      pkh,
      ~extension={
        open Extension
        compose(tzip12, tzip16)
      },
    )
}

So, now I am just wondering why the initial contract["tzip16"]().getMetadata(.) worked, contract being a Tzip12Tzip16Contract contract from Tzip12Tzip16Contract module.

Note: we converted the codebase using npx rescript convert -all.

That being said, I am not sure about rescript world, but in OCaml, the Contract functor would not restrict the interface of produced module.

module type Abstraction = sig
  type t
end

module Contract (Abstraction : Abstraction) = struct
  include Abstraction
end

module Tzip16 = struct
  type t = { tzip16 : unit -> unit }
end

module ContractTzip16 = Contract (Tzip16)

let f (c : ContractTzip16.t) = c.tzip16 ()
module type Abstraction = sig type t end
module Contract :
  functor (Abstraction : Abstraction) -> sig type t = Abstraction.t end
module Tzip16 : sig type t = { tzip16 : unit -> unit; } end
module ContractTzip16 : sig type t = Tzip16.t end
val f : ContractTzip16.t -> unit = <fun>

Ok. that’s the case for rescript too.

So I just need to use the new syntax for type definitions, use the right prefixes where it needs to be added, and use parentheses where needed and I’ll should be ok.

Thanks for your help!

EDIT: I’ll post the solution to my problem in another post and tag it as “solution” so other people can have the complete story.

And now, it compiles thanks to this patch

diff --git a/src/api/MetadataAPI.res b/src/api/MetadataAPI.res
index e7f2b823..6f33a0ea 100644
--- a/src/api/MetadataAPI.res
+++ b/src/api/MetadataAPI.res
@@ -52,11 +52,11 @@ module Tzip16 = {
   }
 
   let read = contract =>
-    contract["tzip16"]().getMetadata(.)
+    (contract->ReTaquitoTypes.Tzip16Contract.tzip16).getMetadata(.)
     ->ReTaquitoError.fromPromiseParsed
     ->Promise.mapError(x =>
       switch x {
-      | ReTaquitoError.NoMetadata => NoTzip16Metadata(contract["address"])
+      | ReTaquitoError.NoMetadata => NoTzip16Metadata(contract->ReTaquitoTypes.Tzip16Contract.address)
       | e => e
       }
     )
@@ -68,7 +68,7 @@ module Tzip12 = {
   /* Read TZIP12 Metadata directly from the storage, should be used only if
    Taquito's embedded API cannot read them. */
   module Storage = {
-    let read = contract => contract["storage"]()->ReTaquitoError.fromPromiseParsed
+    let read = contract => (contract->ReTaquitoTypes.Tzip12Tzip16Contract.storage)->ReTaquitoError.fromPromiseParsed
 
     module Decode = {
       let getOptString = (storage, f) =>
@@ -274,17 +274,17 @@ module Tzip12 = {
 
   let readFromStorage = (contract, tokenId) =>
     Storage.read(contract)->Promise.flatMapOk(storage =>
-      Storage.getToken(contract["address"], storage, tokenId)
+      Storage.getToken(contract->ReTaquitoTypes.Tzip12Tzip16Contract.address, storage, tokenId)
     )
 
   let read = (contract, tokenId) => {
     let metadata =
-      contract["tzip12"]().getTokenMetadata(. tokenId)
+      (contract->ReTaquitoTypes.Tzip12Tzip16Contract.tzip12).getTokenMetadata(. tokenId)
       ->ReTaquitoError.fromPromiseParsed
       ->Promise.mapError(x =>
         switch x {
-        | ReTaquitoError.TokenIdNotFound => TokenIdNotFound(contract["address"], tokenId)
-        | ReTaquitoError.NoTokenMetadata => NoTzip12Metadata(contract["address"])
+        | ReTaquitoError.TokenIdNotFound => TokenIdNotFound(contract->ReTaquitoTypes.Tzip12Tzip16Contract.address, tokenId)
+        | ReTaquitoError.NoTokenMetadata => NoTzip12Metadata(contract->ReTaquitoTypes.Tzip12Tzip16Contract.address)
         | e => e
         }
       )
@@ -298,11 +298,11 @@ module Tzip12 = {
   }
 
   let readContractMetadata = (contract: Tzip12Tzip16Contract.t) =>
-    contract["tzip16"]().getMetadata(.)
+    (contract->ReTaquitoTypes.Tzip12Tzip16Contract.tzip16).getMetadata(.)
     ->ReTaquitoError.fromPromiseParsed
     ->Promise.mapError(x =>
       switch x {
-      | ReTaquitoError.NoMetadata => NoTzip16Metadata(contract["address"])
+      | ReTaquitoError.NoMetadata => NoTzip16Metadata(contract->ReTaquitoTypes.Tzip12Tzip16Contract.address)
       | e => e
       }
     )
diff --git a/src/vendors/ReTaquitoTypes.res b/src/vendors/ReTaquitoTypes.res
index 1e781b44..50559a02 100644
--- a/src/vendors/ReTaquitoTypes.res
+++ b/src/vendors/ReTaquitoTypes.res
@@ -320,15 +320,12 @@ module Tzip16Contract = {
   type storage
   type entrypoints
 
-  type t = {
-    "address": PublicKeyHash.t,
-    "entrypoints": entrypoints,
-    "methods": methods,
-    @meth
-    "storage": unit => Js.Promise.t<storage>,
-    @meth
-    "tzip16": unit => Tzip16.t,
-  }
+  type t
+  @send external address: t => PublicKeyHash.t = "address"
+  @send external entrypoints: t => entrypoints = "entrypoints"
+  @send external methods: t => methods = "methods"
+  @send external storage: t => Js.Promise.t<storage> = "storage"
+  @send external tzip16: t => Tzip16.t = "tzip16"
 }
 
 module Tzip12Tzip16Contract = {
@@ -336,17 +333,13 @@ module Tzip12Tzip16Contract = {
   type storage = Tzip12Storage.storage
   type entrypoints
 
-  type t = {
-    "address": PublicKeyHash.t,
-    "entrypoints": entrypoints,
-    "methods": methods,
-    @meth
-    "storage": unit => Js.Promise.t<storage>,
-    @meth
-    "tzip12": unit => Tzip12.t,
-    @meth
-    "tzip16": unit => Tzip16.t,
-  }
+  type t
+  @send external address: t => PublicKeyHash.t = "address"
+  @send external entrypoints: t => entrypoints = "entrypoints"
+  @send external methods: t => methods = "methods"
+  @send external storage: t => Js.Promise.t<storage> = "storage"
+  @send external tzip12: t => Tzip12.t = "tzip12"
+  @send external tzip16: t => Tzip16.t = "tzip16"
 }
1 Like