Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve error message for failure of generic subtyping of concrete types #4514

Closed
chalcolith opened this issue Apr 29, 2024 · 8 comments · Fixed by #4517
Closed

Improve error message for failure of generic subtyping of concrete types #4514

chalcolith opened this issue Apr 29, 2024 · 8 comments · Fixed by #4517
Assignees

Comments

@chalcolith
Copy link
Member

chalcolith commented Apr 29, 2024

The following program:

class box B

class box A
  let bs: Array[B] = Array[B]
  
  new create(bs': Array[B box] box) =>
    bs.append(bs')
    
actor Main
  new create(env: Env) =>
    let bs: Array[B val] trn = [ recover B end ]
    let bs': Array[B val] val = consume bs
    let a = A(bs')
    env.out.print("done")

https://playground.ponylang.io/?gist=5cdad1a2e82f962b034e277e23d1a91f

Produces the errors:

main.pony:14:15: argument not assignable to parameter
    let a = A(bs')
              ^
    Info:
    main.pony:14:15: argument type is Array[B val] val
        let a = A(bs')
                  ^
    main.pony:7:14: parameter type requires Array[B box] box^
      new create(bs': Array[B box] box) =>
                 ^
    main.pony:7:25: B box is not a subtype of B val: box is not a subcap of val
      new create(bs': Array[B box] box) =>
                            ^
    main.pony:13:14: Array[B val] val has different type arguments than Array[B box] box^
        let bs': Array[B val] val = consume bs
                 ^

Where the compiler is not allowing us to pass an Array[B val] to a parameter of type Array[B box]. It would be nice to allow covariance for type parameters' capabilities so that we could do this.

The code in question seems to be

if(!is_eq_typeargs(sub, super, errorf, opt))
; there is some discussion on Zulip: https://ponylang.zulipchat.com/#narrow/stream/189985-beginner-help/topic/parameter.20subtyping

@chalcolith chalcolith added the discuss during sync Should be discussed during an upcoming sync label Apr 29, 2024
@SeanTAllen
Copy link
Member

The code in question is something under the is_sub_cap_and_ephcheck that happens immediately after the is_eq_typeargs that @chalcolith pointed out.

@SeanTAllen
Copy link
Member

So the code is definitely being asked to check if box is a subcap of val just as the error message says. Which is to say, everything is working as intended based on the code that exists.

@jemc can you explain to me how this is correct based on the current type system? I would expect the test to be "is val a subtype of box".

@jemc
Copy link
Member

jemc commented Apr 30, 2024

I gave some explanation in the sync call.

For anyone who wants to understand this issue, I recommend reading first this RFC that I made, proposing a new language feature related to this topic. The RFC stalled because Sylvan suggested that this problem is better solved by HKT (higher-kinded types), but there is no RFC for HKT at this time.

To summarize, the key reason why the code from @chalcolith doesn't work is because in Pony, generic classes require identical type parameters for subtyping. In other words, type parameters are always treated as invariant for generic classes/actors/primitives. That's because Pony's type system "assumes the worst" about expecting that the class/actor/primitive can use the type parameter in uses both "covariant positions" (e.g. return types) and "contravariant positions" (e.g. parameter types), meaning that the type parameter is treated as invariant.

And in fact Pony's "assume the worst" expectation turns out to be true for Array - if you're reading values out of an array the element type is being used in a covariant way, but if you're writing values into an array the element type is being used in a contravariant way. For example, if you have an Array[B box], the "stuff you read out of it" can be read as B box or B tag (but not B val!), and "the stuff you write into it" can come from B val or B box (but not B tag!). Therefore I can't subsume either an Array[B tag] or Array[B val] into Array[B box], because the former would make for capability-violating read operations, and the latter would make for capability-violating write operations.

The way to make this work in Pony is to use an interface which only defines the operations that you want to use. For example, if you only need to read from the array, you could use something like ReadSeq (or a similar interface that you define), which only contains the read operations. And then covariance works as desired - both Array[B box] and Array[B val] (but not Array[B tag] are subtypes of ReadSeq[B box].

The RFC I linked above was about a new syntax for automatically "summoning" an interface type which included only the covariant or only the contravariant (or only the bivariant) methods of the relevant class, without you needing to define such an interface by hand.

If that RFC were to be accepted/implemented, you'd be able to use Array[+B box] as a type to imply "an interface composed of the subset of Array methods which can safely be used for a covariant T, where T is set to B box". But as it stands in Pony today, you'll either need to use ReadSeq[B box] or define your own interface which has some different subset of "read-only" operations from Array.

@jemc
Copy link
Member

jemc commented Apr 30, 2024

Regarding the error message shown in @chalcolith 's code above, I think the error message is correct and understandable, as long as you know the background about Pony concrete types (classes/actors/primitives) always using their type parameters in an invariant way (never covariant, contravariant, or bivariant). If you want covariance/contravariance/bivariance you need to use a correctly-defined interface type that matches the relevant methods of the class.

To improve the error message I guess we could try to say something to that effect as a "hint" after the current message says "Array[B val] val has different type arguments than Array[B box] box^".

@SeanTAllen
Copy link
Member

Can this be closed?

@jemc
Copy link
Member

jemc commented May 1, 2024

If somebody thinks we should improve the error message for !is_eq_typeargs(sub, super, errorf, opt) then we can keep the ticket open for that purpose.

Otherwise, yes, it can be closed.

@SeanTAllen
Copy link
Member

The error isn't coming from the typeargs check. That passes just fine. The error is from the sub cap and ephemeral check.

@jemc
Copy link
Member

jemc commented May 2, 2024

I'm talking about this error message, which is emitted from the is_eq_typeargs function.

jemc added a commit that referenced this issue May 7, 2024
This make the error message a bit more helpful in cases like those seen in #4514.

Resolves #4514
@jemc jemc changed the title Allow generic types for parameters to be covariant in capability Improve error message for failure of generic subtyping of concrete types May 7, 2024
@jemc jemc self-assigned this May 7, 2024
@jemc jemc removed the discuss during sync Should be discussed during an upcoming sync label May 7, 2024
jemc added a commit that referenced this issue May 14, 2024
* Improve error message for generic subtyping.

This make the error message a bit more helpful in cases like those seen in #4514.

Resolves #4514

* More improvements for generic subtpying error message.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants