wybe icon indicating copy to clipboard operation
wybe copied to clipboard

Type checking fails with on equality of user-defined types that use a concrete instance of a generic type

Open jimbxb opened this issue 3 years ago • 4 comments

Example:

pub type foo { foo(int, list(int)) }

The error produced is:

Error detected during type checking of module(s) test, test.foo
Type error in call to =, argument 1
Type error in call to =, argument 2

jimbxb avatar Dec 03 '21 07:12 jimbxb

Just ran into this too, with incidentally the exact same test case of xyz(int, list(int)) Below is a dump of the members of the module just before typechecking.

It looks like it tries to automatically derive an implementation of = for the structure type, but then it fails to type-check as I assume list(int) doesn't have an implementation of =

AFTER FLATTENING:
 Module out_by_ref_directly_into_structure
  representation  : address
  public submods  : 
  public resources: 
  public procs    : out_by_ref_directly_into_structure.=<0>
                    out_by_ref_directly_into_structure.out_by_ref_directly_into_structure<0>
                    out_by_ref_directly_into_structure.out_by_ref_directly_into_structure<1>
                    out_by_ref_directly_into_structure.~=<0>
  imports         : use wybe
                    use wybe.int
                    use wybe.list
  resources       : 
  procs           : 

= > public {test,inline} (0 calls)
0: =(#left:out_by_ref_directly_into_structure, #right:out_by_ref_directly_into_structure):
    if {(   foreign llvm icmp_uge(#left:!wybe.int, 0, ?tmp#0:!wybe.bool)
        & testbool tmp#0:!wybe.bool)::

        (   out_by_ref_directly_into_structure(?#left#x, ?#left#y, #left)
        & out_by_ref_directly_into_structure(?#right#x, ?#right#y, #right)
        & =(#left#x, #right#x)
        & =(#left#y, #right#y))
    else::
        fail

    }


out_by_ref_directly_into_structure > public {inline} (0 calls)
0: out_by_ref_directly_into_structure(x:wybe.int, y:wybe.list(wybe.int), ?#result:out_by_ref_directly_into_structure):
    foreign lpvm alloc(16, ?#rec:out_by_ref_directly_into_structure)
    foreign lpvm mutate(#rec:out_by_ref_directly_into_structure, ?#rec:out_by_ref_directly_into_structure, 0, 1, 16, 0, x:wybe.int)
    foreign lpvm mutate(#rec:out_by_ref_directly_into_structure, ?#rec:out_by_ref_directly_into_structure, 8, 1, 16, 0, y:wybe.list(wybe.int))
    foreign llvm or(#rec:out_by_ref_directly_into_structure, 0, ?#result:out_by_ref_directly_into_structure)
out_by_ref_directly_into_structure > public {inline} (0 calls)
1: out_by_ref_directly_into_structure(?x:wybe.int, ?y:wybe.list(wybe.int), #result:out_by_ref_directly_into_structure):
    pass
    foreign lpvm access(#result, 0, 16, 0, ?x)
    foreign lpvm access(#result, 8, 16, 0, ?y)


x > {inline} (0 calls)
0: x(#rec:out_by_ref_directly_into_structure, ?#result:wybe.int):
    pass
    foreign lpvm access(#rec, 0, 16, 0, ?#result)
x > {inline} (0 calls)
1: x(!#rec:out_by_ref_directly_into_structure, #field:wybe.int):
    pass
    foreign lpvm mutate(#rec:out_by_ref_directly_into_structure, ?#rec:out_by_ref_directly_into_structure, 0, 0, 16, 0, #field)


y > {inline} (0 calls)
0: y(#rec:out_by_ref_directly_into_structure, ?#result:wybe.list(wybe.int)):
    pass
    foreign lpvm access(#rec, 8, 16, 0, ?#result)
y > {inline} (0 calls)
1: y(!#rec:out_by_ref_directly_into_structure, #field:wybe.list(wybe.int)):
    pass
    foreign lpvm {noalias} mutate(#rec:out_by_ref_directly_into_structure, ?#rec:out_by_ref_directly_into_structure, 8, 0, 16, 0, #field)


~= > public {test,inline} (0 calls)
0: ~=(#left:out_by_ref_directly_into_structure, #right:out_by_ref_directly_into_structure):
    ~(=(#left, #right))

Kinda annoying that there is no pos for this error, I guess since the = implementation is generated internally so there's no obvious source position to attach to it?

neon64 avatar May 17 '22 13:05 neon64

Yep. It's a little bit annoying when there's no source pos.

Regardless, I believe the ultimate for this again is... Drum roll type classes.

We can fake it right now, though, by jerry-rigging our own type classes. Ala:

def {test} `=`(`eqX`{test}(X, X), left:list(X), right:list(X)) {
    if { left = [?l | ?left] & right = [?right | ?r] ::
        eqX(l, r)
        left = right
    | else :: 
        # simple test for constant constructors
        # I forget the foreign proc right now...
    }
}

This should generalise to any structure with no higher order members

One annoying point about the current end-user usage of such a test requires manual `=` args, but you could hypothetically generate these automatically :)

jimbxb avatar May 17 '22 14:05 jimbxb

Yes, exactly.  I agree about the source position, but also the code for generating the equality predicate should not try to generate one in the first place when it won't type check.

The code James gave above is exactly the code that should be generated for equality when we do support type classes.

pschachte avatar May 17 '22 23:05 pschachte

As a temporary fix for this we could check for equality for generic fields in the automatically generated = procs. I know this isn't correct, but at least for now it's better than the annoying error message and requiring writing a stub = every time there is a list field.

jimbxb avatar Dec 12 '22 06:12 jimbxb