asoffer / Icarus

An experimental general-purpose programming language
Apache License 2.0
9 stars 2 forks source link

Generic functions produce unusual error messages when called with incompatible arguments #85

Open perimosocordiae opened 2 years ago

perimosocordiae commented 2 years ago

Reproducer:

io ::= import "io.ic"
x: *i8
io.Print(x)

Error message:

Error in examples/test.ic:
Expression cannot be called with the given arguments.

  4 | io.Print(x)
  * (n: u32) -> () -- Parameter at index 0 cannot accept an argument of type `*i8`
  * (f: f64) -> () -- Parameter at index 0 cannot accept an argument of type `*i8`
  * (f: f32) -> () -- Parameter at index 0 cannot accept an argument of type `*i8`
  * (n: u16) -> () -- Parameter at index 0 cannot accept an argument of type `*i8`
  * generic -- The following parameters do not have default arguments and are not provided at the call-site:a1
  * (n: i16) -> () -- Parameter at index 0 cannot accept an argument of type `*i8`
  * (n: u64) -> () -- Parameter at index 0 cannot accept an argument of type `*i8`
  * (b: bool) -> () -- Parameter at index 0 cannot accept an argument of type `*i8`
  * (n: i64) -> () -- Parameter at index 0 cannot accept an argument of type `*i8`
  * generic -- The following parameters do not have default arguments and are not provided at the call-site:a1
  * (n: i8) -> () -- Parameter at index 0 cannot accept an argument of type `*i8`
  * (n: i32) -> () -- Parameter at index 0 cannot accept an argument of type `*i8`
  * generic -- The following parameters do not have default arguments and are not provided at the call-site:a1
  * (n: u8) -> () -- Parameter at index 0 cannot accept an argument of type `*i8`
  * (s: []char) -> () -- Parameter at index 0 cannot accept an argument of type `*i8`
  * (c: char) -> () -- Parameter at index 0 cannot accept an argument of type `*i8`
  * generic -- The following parameters do not have default arguments and are not provided at the call-site:a1

This is actually not too bad, except for the confusing ordering of the messages and the repeated entries starting with * generic --. The fun part comes when you call the two-argument version of Print:

io ::= import "io.ic"
x: *i8
io.Print(x, !\n)

Error message:

Error in examples/test.ic:
Expression cannot be called with the given arguments.

  4 | io.Print(x, !\n)
  * (f: f32) -> () -- Parameter at index 0 cannot accept an argument of type `A0`
  * (n: u8) -> () -- Parameter at index 0 cannot accept an argument of type `A0`
  * (n: i64) -> () -- Parameter at index 0 cannot accept an argument of type `A0`
  * generic -- The following parameters do not have default arguments and are not provided at the call-site:a1
  * (n: i16) -> () -- Parameter at index 0 cannot accept an argument of type `A0`
  * (n: i32) -> () -- Parameter at index 0 cannot accept an argument of type `A0`
  * (n: u64) -> () -- Parameter at index 0 cannot accept an argument of type `A0`
  * (n: i8) -> () -- Parameter at index 0 cannot accept an argument of type `A0`
  * (f: f64) -> () -- Parameter at index 0 cannot accept an argument of type `A0`
  * generic -- The following parameters do not have default arguments and are not provided at the call-site:a1
  * (n: u16) -> () -- Parameter at index 0 cannot accept an argument of type `A0`
  * (s: []char) -> () -- Parameter at index 0 cannot accept an argument of type `A0`
  * (c: char) -> () -- Parameter at index 0 cannot accept an argument of type `A0`
  * generic -- The following parameters do not have default arguments and are not provided at the call-site:a1
  * (b: bool) -> () -- Parameter at index 0 cannot accept an argument of type `A0`
  * (n: u32) -> () -- Parameter at index 0 cannot accept an argument of type `A0`
  * generic -- The following parameters do not have default arguments and are not provided at the call-site:a1

Here, we lose the actual type of the argument (*i8) and instead use the generic placeholder A0.

asoffer commented 2 years ago

This should get better when we implement interfaces, but it's still probably a thing we need to watch out for. What's going on here is that the 2-parameter Print is fully generic and matches unconditionally. It dispatches to the 1-parameter Print for each argument, but one of those fails so it prints something useful from the perspective of the failing code: If you were the author of the 2-parameter Print, this would indeed be useful.

clang also adds an instantiation stack for templates so it would show this and the instantiators all the way up. If we implement interfaces correctly, we should never hit this point, because the binding of *i8 to A0 would fail immediately and generate a corresponding error message. We would also demand "Printable" on the type parameter A0, so even without instantiations of this generic we would be able to verify correctness.

perimosocordiae commented 2 years ago

Update: for the second code snippet above (two arguments to Print), we now get this error:

Error:
Expression cannot be called with the given arguments.

  32 | [139792255997768 ./frontend/source/buffer.h:151] Assertion failed
    Expected: end_offset == 0
         LHS: 2
         RHS: 0
*** SIGABRT received at time=1641172330 on cpu 0 ***
PC: @     0x7f23ebccc808  (unknown)  pthread_kill
    @          0x18b99a0         64  absl::WriteFailureInfo()
    @          0x18b9684        224  absl::AbslFailureSignalHandler()
    @     0x7f23ebc78520  (unknown)  (unknown)
Aborted (core dumped)