dfinity / motoko

Simple high-level language for writing Internet Computer canisters
Apache License 2.0
513 stars 97 forks source link

Specify in error messages, where exactly two Actor objects differ #4571

Open vporton opened 5 months ago

vporton commented 5 months ago

It is very inconvenient, when I receive a long message of two Actor classes differing. I want the message to show where (which methods with their signatures) is the point(s) of difference.

I adopted to copy the error message, extract from it two Actor classes manually and compare them with wdiff command to find what's wrong. But that's inconvenient.

cannot implicitly instantiate function of type
  <K, V>(Tree<K, V>, (K, K) -> Order, K) -> ?V
to argument of type
  ({
     #leaf;
     #node :
       (Color, Tree<(Partition__1, OuterSubDBKey), GUID__2>,
        ((Partition__1, OuterSubDBKey), ?GUID__2),
        Tree<(Partition__1, OuterSubDBKey), GUID__2>)
   },
   ((Partition__1, OuterSubDBKey), (Partition__1, OuterSubDBKey)) ->
     {#equal; #greater; #less},
   (actor {
      createOuter :
        shared {
                 innerKey : InnerSubDBKey;
                 outerKey : OuterSubDBKey;
                 part : Principal
               } ->
          async
            {
              inner : {canister : Principal; key : InnerSubDBKey};
              outer : {canister : Principal; key : OuterSubDBKey}
            };
      deleteInner : shared {innerKey : InnerSubDBKey; sk : SK} -> async ();
      deleteSubDBInner : shared {innerKey : InnerSubDBKey} -> async ();
      deleteSubDBOuter : shared {outerKey : OuterSubDBKey} -> async ();
      getByInner :
        shared query {innerKey : InnerSubDBKey; sk : SK} ->
          async ?AttributeValue;
      getByOuter :
        shared {outerKey : OuterSubDBKey; sk : SK} -> async ?AttributeValue;
      getInner :
        shared query {outerKey : OuterSubDBKey} ->
          async ?{canister : Principal; key : InnerSubDBKey};
      getSubDBUserDataInner :
        shared {innerKey : InnerSubDBKey} -> async ?Text;
      getSubDBUserDataOuter :
        shared {outerKey : OuterSubDBKey} -> async ?Text;
      hasByInner :
        shared query {innerKey : InnerSubDBKey; sk : SK} -> async Bool;
      hasByOuter : shared {outerKey : OuterSubDBKey; sk : SK} -> async Bool;
      hasSubDBByInner : shared query {innerKey : InnerSubDBKey} -> async Bool;
      hasSubDBByOuter : shared {outerKey : OuterSubDBKey} -> async Bool;
      isOverflowed : shared query () -> async Bool;
      putLocation :
        shared {
                 innerCanister : Principal;
                 innerKey : InnerSubDBKey;
                 outerKey : OuterSubDBKey
               } ->
          async ();
      rawDeleteSubDB : shared {innerKey : InnerSubDBKey} -> async ();
      rawGetSubDB :
        shared query {innerKey : InnerSubDBKey} ->
          async ?{map : [(SK, AttributeValue)]; userData : Text};
      rawInsertSubDB :
        shared {
                 hardCap : ?Nat;
                 innerKey : ?InnerSubDBKey;
                 map : [(SK, AttributeValue)];
                 userData : Text
               } ->
          async {innerKey : InnerSubDBKey};
      rawInsertSubDBAndSetOuter :
        shared {
                 hardCap : ?Nat;
                 keys : ?{innerKey : InnerSubDBKey; outerKey : OuterSubDBKey};
                 map : [(SK, AttributeValue)];
                 userData : Text
               } ->
          async {innerKey : InnerSubDBKey; outerKey : OuterSubDBKey};
      scanLimitInner :
        shared query {
                       dir : Direction__1;
                       innerKey : InnerSubDBKey;
                       limit : Nat;
                       lowerBound : SK;
                       upperBound : SK
                     } ->
          async ScanLimitResult__1<Text, AttributeValue>;
      scanLimitOuter :
        shared {
                 dir : Direction__1;
                 limit : Nat;
                 lowerBound : SK;
                 outerKey : OuterSubDBKey;
                 upperBound : SK
               } ->
          async ScanLimitResult__1<Text, AttributeValue>;
      scanSubDBs :
        shared query () ->
          async
            [(OuterSubDBKey, {canister : Principal; key : InnerSubDBKey})];
      startInsertingImpl :
        shared {innerKey : InnerSubDBKey; sk : SK; value : AttributeValue} ->
          async ();
      subDBSizeByInner :
        shared query {innerKey : InnerSubDBKey} -> async ?Nat;
      subDBSizeByOuter : shared {outerKey : OuterSubDBKey} -> async ?Nat;
      subDBSizeOuterImpl : shared {outerKey : OuterSubDBKey} -> async ?Nat;
      superDBSize : shared query () -> async Nat
    }, Nat))
because implicit instantiation of type parameter K is over-constrained with
  (actor {
     createOuter :
       shared {
                innerKey : InnerSubDBKey;
                outerKey : OuterSubDBKey;
                part : Principal
              } ->
         async
           {
             inner : {canister : Principal; key : InnerSubDBKey};
             outer : {canister : Principal; key : OuterSubDBKey}
           };
     deleteInner : shared {innerKey : InnerSubDBKey; sk : SK} -> async ();
     deleteSubDBInner : shared {innerKey : InnerSubDBKey} -> async ();
     deleteSubDBOuter : shared {outerKey : OuterSubDBKey} -> async ();
     getByInner :
       shared query {innerKey : InnerSubDBKey; sk : SK} ->
         async ?AttributeValue;
     getByOuter :
       shared {outerKey : OuterSubDBKey; sk : SK} -> async ?AttributeValue;
     getInner :
       shared query {outerKey : OuterSubDBKey} ->
         async ?{canister : Principal; key : InnerSubDBKey};
     getSubDBUserDataInner : shared {innerKey : InnerSubDBKey} -> async ?Text;
     getSubDBUserDataOuter : shared {outerKey : OuterSubDBKey} -> async ?Text;
     hasByInner :
       shared query {innerKey : InnerSubDBKey; sk : SK} -> async Bool;
     hasByOuter : shared {outerKey : OuterSubDBKey; sk : SK} -> async Bool;
     hasSubDBByInner : shared query {innerKey : InnerSubDBKey} -> async Bool;
     hasSubDBByOuter : shared {outerKey : OuterSubDBKey} -> async Bool;
     isOverflowed : shared query () -> async Bool;
     putLocation :
       shared {
                innerCanister : Principal;
                innerKey : InnerSubDBKey;
                outerKey : OuterSubDBKey
              } ->
         async ();
     rawDeleteSubDB : shared {innerKey : InnerSubDBKey} -> async ();
     rawGetSubDB :
       shared query {innerKey : InnerSubDBKey} ->
         async ?{map : [(SK, AttributeValue)]; userData : Text};
     rawInsertSubDB :
       shared {
                hardCap : ?Nat;
                innerKey : ?InnerSubDBKey;
                map : [(SK, AttributeValue)];
                userData : Text
              } ->
         async {innerKey : InnerSubDBKey};
     rawInsertSubDBAndSetOuter :
       shared {
                hardCap : ?Nat;
                keys : ?{innerKey : InnerSubDBKey; outerKey : OuterSubDBKey};
                map : [(SK, AttributeValue)];
                userData : Text
              } ->
         async {innerKey : InnerSubDBKey; outerKey : OuterSubDBKey};
     scanLimitInner :
       shared query {
                      dir : Direction__1;
                      innerKey : InnerSubDBKey;
                      limit : Nat;
                      lowerBound : SK;
                      upperBound : SK
                    } ->
         async ScanLimitResult__1<Text, AttributeValue>;
     scanLimitOuter :
       shared {
                dir : Direction__1;
                limit : Nat;
                lowerBound : SK;
                outerKey : OuterSubDBKey;
                upperBound : SK
              } ->
         async ScanLimitResult__1<Text, AttributeValue>;
     scanSubDBs :
       shared query () ->
         async [(OuterSubDBKey, {canister : Principal; key : InnerSubDBKey})];
     startInsertingImpl :
       shared {innerKey : InnerSubDBKey; sk : SK; value : AttributeValue} ->
         async ();
     subDBSizeByInner : shared query {innerKey : InnerSubDBKey} -> async ?Nat;
     subDBSizeByOuter : shared {outerKey : OuterSubDBKey} -> async ?Nat;
     subDBSizeOuterImpl : shared {outerKey : OuterSubDBKey} -> async ?Nat;
     superDBSize : shared query () -> async Nat
   }, OuterSubDBKey)  <: 
    K  <:  (Partition__1, OuterSubDBKey)
where
  (actor {
     createOuter :
       shared {
                innerKey : InnerSubDBKey;
                outerKey : OuterSubDBKey;
                part : Principal
              } ->
         async
           {
             inner : {canister : Principal; key : InnerSubDBKey};
             outer : {canister : Principal; key : OuterSubDBKey}
           };
     deleteInner : shared {innerKey : InnerSubDBKey; sk : SK} -> async ();
     deleteSubDBInner : shared {innerKey : InnerSubDBKey} -> async ();
     deleteSubDBOuter : shared {outerKey : OuterSubDBKey} -> async ();
     getByInner :
       shared query {innerKey : InnerSubDBKey; sk : SK} ->
         async ?AttributeValue;
     getByOuter :
       shared {outerKey : OuterSubDBKey; sk : SK} -> async ?AttributeValue;
     getInner :
       shared query {outerKey : OuterSubDBKey} ->
         async ?{canister : Principal; key : InnerSubDBKey};
     getSubDBUserDataInner : shared {innerKey : InnerSubDBKey} -> async ?Text;
     getSubDBUserDataOuter : shared {outerKey : OuterSubDBKey} -> async ?Text;
     hasByInner :
       shared query {innerKey : InnerSubDBKey; sk : SK} -> async Bool;
     hasByOuter : shared {outerKey : OuterSubDBKey; sk : SK} -> async Bool;
     hasSubDBByInner : shared query {innerKey : InnerSubDBKey} -> async Bool;
     hasSubDBByOuter : shared {outerKey : OuterSubDBKey} -> async Bool;
     isOverflowed : shared query () -> async Bool;
     putLocation :
       shared {
                innerCanister : Principal;
                innerKey : InnerSubDBKey;
                outerKey : OuterSubDBKey
              } ->
         async ();
     rawDeleteSubDB : shared {innerKey : InnerSubDBKey} -> async ();
     rawGetSubDB :
       shared query {innerKey : InnerSubDBKey} ->
         async ?{map : [(SK, AttributeValue)]; userData : Text};
     rawInsertSubDB :
       shared {
                hardCap : ?Nat;
                innerKey : ?InnerSubDBKey;
                map : [(SK, AttributeValue)];
                userData : Text
              } ->
         async {innerKey : InnerSubDBKey};
     rawInsertSubDBAndSetOuter :
       shared {
                hardCap : ?Nat;
                keys : ?{innerKey : InnerSubDBKey; outerKey : OuterSubDBKey};
                map : [(SK, AttributeValue)];
                userData : Text
              } ->
         async {innerKey : InnerSubDBKey; outerKey : OuterSubDBKey};
     scanLimitInner :
       shared query {
                      dir : Direction__1;
                      innerKey : InnerSubDBKey;
                      limit : Nat;
                      lowerBound : SK;
                      upperBound : SK
                    } ->
         async ScanLimitResult__1<Text, AttributeValue>;
     scanLimitOuter :
       shared {
                dir : Direction__1;
                limit : Nat;
                lowerBound : SK;
                outerKey : OuterSubDBKey;
                upperBound : SK
              } ->
         async ScanLimitResult__1<Text, AttributeValue>;
     scanSubDBs :
       shared query () ->
         async [(OuterSubDBKey, {canister : Principal; key : InnerSubDBKey})];
     startInsertingImpl :
       shared {innerKey : InnerSubDBKey; sk : SK; value : AttributeValue} ->
         async ();
     subDBSizeByInner : shared query {innerKey : InnerSubDBKey} -> async ?Nat;
     subDBSizeByOuter : shared {outerKey : OuterSubDBKey} -> async ?Nat;
     subDBSizeOuterImpl : shared {outerKey : OuterSubDBKey} -> async ?Nat;
     superDBSize : shared query () -> async Nat
   }, OuterSubDBKey)  </: 
    (Partition__1, OuterSubDBKey)
so that no valid instantiation exists
crusso commented 4 months ago

I feel your pain.

(Note to self: to address this properly we have to extend our subtyping check to not just return a bool but, in the failure case, a path indicating which subgoal during subtying failed. Moscow ML does something similar at the module level.)