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

Cannot assign to iftype-narrowed trait reference. #1960

Closed
jemc opened this issue Jun 11, 2017 · 2 comments
Closed

Cannot assign to iftype-narrowed trait reference. #1960

jemc opened this issue Jun 11, 2017 · 2 comments
Assignees

Comments

@jemc
Copy link
Member

jemc commented Jun 11, 2017

I ran into this odd error with iftype, where it is disallowing an assignment to an iftype-narrowed reference ~~~that should be allowed~~~.

EDIT: @plietar demonstrated why this shouldn't be allowed, but also found some other related issues, so this ticket remains open.

trait val AST

trait val Method is AST fun val with_changes(): Method

class val MethodFun is Method fun val with_changes(): MethodFun => create()
class val MethodBe  is Method fun val with_changes(): MethodBe  => create()
class val MethodNew is Method fun val with_changes(): MethodNew => create()

actor Main
  new create(env: Env) =>
    let ast = apply[MethodFun](MethodFun)
  
  fun apply[A: AST val](ast': A): AST =>
    var ast = ast'
    
    iftype A <: Method then
      ast = ast.with_changes()
    end
    
    ast
Error:
/home/jemc/1/code/gitx/ponyc/test.jemc/test.pony:18:11: right side must be a subtype of left side
      ast = ast.with_changes()
          ^
    Info:
    /home/jemc/1/code/gitx/ponyc/test.jemc/test.pony:4:49: Method val is not a subtype of A val: the type parameter has no lower bounds
    trait val Method is AST fun val with_changes(): Method
                                                    ^

Note that this works fine if I remove the intermediate Method trait, and use one of the specific classes directly as the supertype in the iftype clause:

trait val AST

class val MethodFun is AST fun val with_changes(): MethodFun => create()

actor Main
  new create(env: Env) =>
    let ast = apply[MethodFun](MethodFun)
  
  fun apply[A: AST val](ast': A): AST =>
    var ast = ast'
    
    iftype A <: MethodFun then
      ast = ast.with_changes()
    end
    
    ast
@plietar
Copy link
Contributor

plietar commented Jun 12, 2017

That seems like the right behaviour to me. Say you have the MethodOther class, whose with_changes() returns a different type.

trait val AST
trait val Method is AST
  fun val with_changes(): Method

class val MethodNew is Method
  fun val with_changes(): MethodNew =>
    create()

class val MethodOther is Method
  fun val with_changes(): MethodNew =>
    MethodNew.create()

actor Main
  new create(env: Env) =>
    let ast = apply[MethodOther](MethodOther)
  
  fun apply[A: AST val](ast': A): AST =>
    var ast = ast'
    
    iftype A <: Method then
      ast = ast.with_changes()
    end
    
    ast

If this did build, ast = ast.with_changes() would assign a MethodNew to a variable of type A aka MethodOther


What you want here is a Self type in return position of Method's with_changes. You can emulate it with F-bounded polymorphism

trait val AST

trait val Method[X] is AST
  fun val with_changes(): X

class val MethodFun is Method[MethodFun]
  fun val with_changes(): MethodFun => create()

class val MethodBe is Method[MethodBe]
  fun val with_changes(): MethodBe  => create()

class val MethodNew is Method[MethodNew]
  fun val with_changes(): MethodNew => create()

actor Main
  new create(env: Env) =>
    let ast = apply[MethodFun](MethodFun)
  
  fun apply[A: AST val](ast': A): AST =>
    var ast = ast'
    
    iftype A <: Method[A] then
      ast = ast.with_changes()
    end
    
    ast

This is still not working, but I'm not sure why.

Error:
main.pony:29:11: right side must be a subtype of left side
      ast = ast.with_changes()
          ^
    Info:
    main.pony:25:16: AST val is not a subtype of A val: the type parameter has no lower bounds
      fun apply[A: AST val](ast': A): AST =>

The compiler thinks the with_changes call returns an AST val, when it should be an A

@plietar
Copy link
Contributor

plietar commented Jun 12, 2017

This is interesting.

trait val AST

trait val Method[X] is AST
  fun val with_changes(): X

actor Main
  new create(env: Env) => None

  fun foo[A: Method[A] val](ast: A) : A =>
    ast.with_changes()
  
  fun apply[A: AST val](ast': A): AST =>
    var ast = ast'
    
    iftype A <: Method[A] then
      ast = foo[A](ast.with_changes())
    end

    ast
Error:
main.pony:26:13: type argument is outside its constraint
      ast = foo[A](ast.with_changes())
            ^
    Info:
    main.pony:26:17: argument: A val
          ast = foo[A](ast.with_changes())
                    ^
    main.pony:19:11: constraint: Method[A val] val
      fun foo[A: Method[A] val](ast: A) : A =>
              ^
    main.pony:25:17: no element of (AST val & Method[A val] val) is a subtype of Method[A val] val
        iftype A <: Method[A] then

no element of (AST val & Method[A val] val) is a subtype of Method[A val] val.
From what I understand of the implementation of iftype, it introduces a new type variable in scope
with the same name but different bounds. It seems to me like the right hand side of the iftype is being resolved in the global scope.

Which results in this sort of "desugaring"

fun apply[Aouter: AST val](ast': Aouter): AST =>
    var ast = ast'
    iftype [Ainner: AST val & Method[Aouter] val] =>
      ast = foo[Ainner](ast.with_changes())

And the error message would then be
no element of (AST val & Method[Aouter val] val) is a subtype of Method[Ainner val] val.


Similarly, this doesn't work now, but I believe it should

trait val Method[X: Method[X]]

actor Main
  new create(env: Env) => None
  fun apply[X]() =>
    iftype X <: Method[X] then
        None
    end
Error:
main.pony:6:23: type argument is outside its constraint
    iftype X <: Method[X] then
                      ^
    Info:
    main.pony:6:24: argument: X #any
        iftype X <: Method[X] then
                           ^
    main.pony:1:18: constraint: Method[X #any] #any
    trait val Method[X: Method[X]]
                     ^
    main.pony:6:24: X #any is not a subtype of Method[X #any] #any: the subtype has no constraint
        iftype X <: Method[X] then
                           ^

@plietar plietar self-assigned this Jun 12, 2017
plietar added a commit to plietar/ponyc that referenced this issue Jun 12, 2017
By defining the shadowing TK_TYPEPARAM on the entire iftype clause, which
contains both the constraint and the body, the appropriate definition
of the type variable is used when it appears in the constraint.

Fixes ponylang#1960
plietar added a commit to plietar/ponyc that referenced this issue Jul 2, 2017
By defining the shadowing TK_TYPEPARAM on the entire iftype clause, which
contains both the constraint and the body, the appropriate definition
of the type variable is used when it appears in the constraint.

Fixes ponylang#1960
@jemc jemc closed this as completed in #1961 Jul 2, 2017
jemc pushed a commit that referenced this issue Jul 2, 2017
* Keep the data of TK_TYPEPARAM pointing to the original definition.

iftype is implemented by shadowing the type parameter with a new definition
and a new constraint. Storing the pointer of the original definition
allows us to compare identity of type parameters easily.

This replaces the more complicated subtyping-based check to determine
whether a type parameter should be reified.

* Create a new AST node for iftype clauses.

This creates a new scope with both the constraint and the body of the
iftype, but without the else clause.

* Fix support for recursive constraints in iftype conditions.

By defining the shadowing TK_TYPEPARAM on the entire iftype clause, which
contains both the constraint and the body, the appropriate definition
of the type variable is used when it appears in the constraint.

Fixes #1960

* Rename TK_IFTYPE and TK_IFTYPE_CLAUSE to be clearer
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

No branches or pull requests

2 participants