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)
Ok, I’m rolling now. Look out @ergl, before too long you’ll have usable
with
blocks.