Skip to content

Commit

Permalink
Fixed #420: use next to return from a captured block. return is d…
Browse files Browse the repository at this point in the history
…isallowed in that case
  • Loading branch information
asterite committed Jan 21, 2016
1 parent 2492757 commit aa043bb
Show file tree
Hide file tree
Showing 6 changed files with 141 additions and 8 deletions.
32 changes: 32 additions & 0 deletions spec/compiler/codegen/block_spec.cr
Expand Up @@ -1280,4 +1280,36 @@ describe "Code gen: block" do
$x
)).to_i.should eq(1)
end

it "returns from proc literal" do
run(%(
foo = ->{
if 1 == 1
return 10
end
20
}
foo.call
)).to_i.should eq(10)
end

it "does next from captured block" do
run(%(
def foo(&block : -> T)
block
end
f = foo do
if 1 == 1
next 10
end
next 20
end
f.call
)).to_i.should eq(10)
end
end
76 changes: 76 additions & 0 deletions spec/compiler/type_inference/block_spec.cr
Expand Up @@ -975,4 +975,80 @@ describe "Block inference" do
yielder { 1 }
)) { int32.metaclass }
end

it "returns from proc literal" do
assert_type(%(
foo = ->{
if 1 == 1
return 1
end
1.5
}
foo.call
)) { union_of int32, float64 }
end

it "errors if returns from captured block" do
assert_error %(
def foo(&block)
block
end
def bar
foo do
return
end
end
bar
),
"can't return from captured block, use next"
end

it "errors if breaks from captured block" do
assert_error %(
def foo(&block)
block
end
def bar
foo do
break
end
end
bar
),
"can't break from captured block"
end

it "errors if doing next in proc literal" do
assert_error %(
foo = ->{
next
}
foo.call
),
"Invalid next"
end

it "does next from captured block" do
assert_type(%(
def foo(&block : -> T)
block
end
f = foo do
if 1 == 1
next 1
end
next 1.5
end
f.call
)) { union_of int32, float64 }
end
end
12 changes: 9 additions & 3 deletions src/compiler/crystal/codegen/codegen.cr
Expand Up @@ -429,6 +429,12 @@ module Crystal
def visit(node : Return)
node_type = accept_control_expression(node)

codegen_return_node(node, node_type)

false
end

def codegen_return_node(node, node_type)
old_last = @last

execute_ensures_until(node.target as Def)
Expand All @@ -440,8 +446,6 @@ module Crystal
else
codegen_return node_type
end

false
end

def codegen_return(type : NoReturnType | Nil)
Expand Down Expand Up @@ -655,7 +659,9 @@ module Crystal
execute_ensures_until(node.target as While)
br while_block
else
node.raise "Bug: unknown exit for next"
# The only possibility is that we are in a captured block,
# so this is the same as a return
codegen_return_node(node, node_type)
end

false
Expand Down
3 changes: 3 additions & 0 deletions src/compiler/crystal/semantic/ast.cr
Expand Up @@ -251,6 +251,9 @@ module Crystal
property :block_nest
@block_nest = 0

property? :captured_block
@captured_block = false

def macro_owner=(@macro_owner)
end

Expand Down
5 changes: 4 additions & 1 deletion src/compiler/crystal/semantic/call.cr
Expand Up @@ -702,7 +702,10 @@ class Crystal::Call
raise "wrong number of block arguments (#{block.args.size} for #{fun_args.size})"
end

fun_literal = FunLiteral.new(Def.new("->", fun_args, block.body))
a_def = Def.new("->", fun_args, block.body)
a_def.captured_block = true

fun_literal = FunLiteral.new(a_def)
fun_literal.force_void = true unless block_arg_restriction_output
fun_literal.accept parent_visitor
end
Expand Down
21 changes: 17 additions & 4 deletions src/compiler/crystal/semantic/type_visitor.cr
Expand Up @@ -1232,13 +1232,16 @@ module Crystal
end

def visit(node : Return)
node.raise "can't return from top level" unless @typed_def
typed_def = @typed_def || node.raise("can't return from top level")

if typed_def.captured_block?
node.raise "can't return from captured block, use next"
end

node.exp.try &.accept self

node.target = @typed_def
node.target = typed_def

typed_def = @typed_def.not_nil!
typed_def.bind_to(node.exp || mod.nil_var)
@unreachable = true

Expand Down Expand Up @@ -1723,6 +1726,10 @@ module Crystal
break_vars = (target_while.break_vars ||= [] of MetaVars)
break_vars.push @vars.dup
else
if @typed_def.try &.captured_block?
node.raise "can't break from captured block"
end

node.raise "Invalid break"
end

Expand All @@ -1742,7 +1749,13 @@ module Crystal

bind_vars @vars, @while_vars
else
node.raise "Invalid next"
typed_def = @typed_def
if typed_def && typed_def.captured_block?
node.target = typed_def
typed_def.bind_to(node.exp || mod.nil_var)
else
node.raise "Invalid next"
end
end

@unreachable = true
Expand Down

0 comments on commit aa043bb

Please sign in to comment.