ponyc: "with blocks" with no "else" clause are desugared incorrectly

The tutorial mentions that the return type of a with block is “the value of the last expression in the block, or of the last expression in the else block if there is one and an error occurred.

However, the above is incorrect in cases where the body of a with block doesn’t raise an error. In that case, the compiler will generate a try block that returns None in the else clause, causing the return type to always be a union type.

This makes with blocks impossible to use without error checking in cases where one is only interested in the auto-disposing behaviour. Consider the following code:

class Foo
  new ref create() => None
  fun apply(): String => ""
  fun dispose() => None

actor Main
  new create(env: Env) =>
    let s: String = with f = Foo do
      f.apply()
    else
      ""
    end

The above fails to compile with “try expression never results in an error”, informing us that we don’t need an else clause (although the message is confusing, since the user never used a try expression in the code).

If we change the above to remove the else clause:

class Foo
  new ref create() => None
  fun apply(): String => ""
  fun dispose() => None

actor Main
  new create(env: Env) =>
    let s: String = with f = Foo do
      f.apply()
    end

Now we’re hit with:

private/tmp/with_test/main.pony:8:19: right side must be a subtype of left side
    let s: String = with f = Foo do
                  ^
    Info:
    /Users/ryan/.local/share/ponyup/ponyc-release-0.41.0-x86_64-darwin/packages/builtin/none.pony:1:1: None val^ is not a subtype of String val^
    primitive None is Stringable
    ^
    /Users/ryan/.local/share/ponyup/ponyc-release-0.41.0-x86_64-darwin/packages/builtin/none.pony:1:1: not every element of (String val | None val^) is a subtype of String val^
    primitive None is Stringable
    ^

The None type above comes from the desugared try block, as we can see from the AST:

(seq
    (= (let (id $1$0) x) (seq (reference (id Foo))))
    (try
        (seq:scope (= (let (id f) x) (reference (id $1$0))) (seq (call (. (reference (id f)) (id apply)) x x x)))
        (seq:scope (= (let (id f) x) (reference (id $1$0))) (seq (reference (id None))))
        (seq:scope (= (let (id f) x) (reference (id $1$0))) (call (. (reference (id f)) (id dispose)) x x x))
    )
)

About this issue

  • Original URL
  • State: closed
  • Created 3 years ago
  • Comments: 19 (19 by maintainers)

Commits related to this issue

Most upvoted comments

Ok, I’m rolling now. Look out @ergl, before too long you’ll have usable with blocks.