Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: crystal-lang/crystal
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: b353cc7945fa
Choose a base ref
...
head repository: crystal-lang/crystal
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: c3cd3a7a8d90
Choose a head ref
  • 4 commits
  • 7 files changed
  • 1 contributor

Commits on Jun 13, 2016

  1. Fix type inference error for variable before while that is mutated in…

    … the while's body
    Ary Borenszweig committed Jun 13, 2016
    Copy the full SHA
    6c72492 View commit details
  2. Improve inspect for some internals to make it easier to debug some …

    …things
    Ary Borenszweig committed Jun 13, 2016
    Copy the full SHA
    9514d44 View commit details
  3. Fixed #2767: Crash with NoReturn inside while

    Ary Borenszweig committed Jun 13, 2016
    Copy the full SHA
    e1a103f View commit details
  4. Fixed #2531: Bug: trying to downcast Int32? <- Int32

    Ary Borenszweig committed Jun 13, 2016
    Copy the full SHA
    c3cd3a7 View commit details
73 changes: 73 additions & 0 deletions spec/compiler/codegen/while_spec.cr
Original file line number Diff line number Diff line change
@@ -92,4 +92,77 @@ describe "Codegen: while" do
end
))
end

it "doesn't crash on #2767" do
run(%(
lib LibC
fun exit(Int32) : NoReturn
end
x = 'x'
while 1 == 2
if true
x = (LibC.exit(0); 1)
end
end
x
10
)).to_i.should eq(10)
end

it "doesn't crash on #2767 (2)" do
run(%(
lib LibC
fun exit(Int32) : NoReturn
end
x = 'x'
while 1 == 2
x = LibC.exit(0).as(Int32)
end
x
10
)).to_i.should eq(10)
end

it "doesn't crash on #2767 (3)" do
run(%(
lib LibC
fun exit(Int32) : NoReturn
end
x = 'x'
while 1 == 2
if true
x = if true
LibC.exit(0)
else
3
end
end
end
x
10
)).to_i.should eq(10)
end

it "doesn't crash on #2767 (4)" do
run(%(
lib LibC
fun exit(Int32) : NoReturn
end
x = 'x'
while 1 == 2
if true
x = (LibC.exit(0); 1)
end
y = x
z = y
x = z
end
x
10
)).to_i.should eq(10)
end
end
14 changes: 14 additions & 0 deletions spec/compiler/type_inference/block_spec.cr
Original file line number Diff line number Diff line change
@@ -1312,4 +1312,18 @@ describe "Block inference" do
),
"too many block arguments (given 3, expected maximum 2)"
end

it "doesn't crash on #2531" do
run(%(
def foo
yield
end
value = true ? 1 : nil
foo do
value ? nil : nil
end
value ? 10 : 20
)).to_i.should eq(10)
end
end
11 changes: 11 additions & 0 deletions spec/compiler/type_inference/while_spec.cr
Original file line number Diff line number Diff line change
@@ -105,4 +105,15 @@ describe "Type inference: while" do
a
)) { int32 }
end

it "doesn't modify var's type before while" do
assert_type(%(
x = 'x'
x.ord
while 1 == 2
x = 1
end
x
)) { union_of(int32, char) }
end
end
45 changes: 44 additions & 1 deletion src/compiler/crystal/semantic/ast.cr
Original file line number Diff line number Diff line change
@@ -23,6 +23,7 @@ module Crystal
property input_observer : Call?

@dirty = false
@propagating_after_cleanup = false

@type : Type?

@@ -189,6 +190,7 @@ module Crystal
end

def update(from)
return if @propagating_after_cleanup
return if @type.same? from.type?

if dependencies.size == 1 || !@type
@@ -197,7 +199,47 @@ module Crystal
new_type = Type.merge dependencies
end

return if @type.same? new_type
if @type.same? new_type
# If we are in the cleanup phase it might happen that a dependency's
# type changed (from) but our type didn't. This might happen if
# there's a circular dependencies in nodes (while and blocks can
# cause this), so we basically need to recompute all types in the
# cycle (and depending types).
#
# To solve this, we set our type to NoReturn so observers
# compute their type without taking this note into account.
# Later, we compute our type from our dependencies and propagate
# types as usual.
#
# To avoid infinite recursion we use the `@propagating_after_cleanup`
# flag, which prevents computing and propagating types for this
# node while we are doing the above logic.
if dependencies.size > 0 && (from_type = from.type?) && from_type.program.in_cleanup_phase?
set_type(from_type.program.no_return)

@propagating_after_cleanup = true
@dirty = true
propagate

new_type = Type.merge dependencies
if new_type
set_type_from(map_type(new_type), from)
else
unless @type
@propagating_after_cleanup = false
return
end
set_type(nil)
end

@dirty = true
propagate
@propagating_after_cleanup = false
return
else
return
end
end

if new_type
set_type_from(map_type(new_type), from)
@@ -846,6 +888,7 @@ module Crystal
io << " (nil-if-read)" if nil_if_read
io << " (closured)" if closured
io << " (assigned-to)" if assigned_to
io << " (object id: #{object_id})"
end
end

8 changes: 8 additions & 0 deletions src/compiler/crystal/semantic/cleanup_transformer.cr
Original file line number Diff line number Diff line change
@@ -4,15 +4,21 @@ require "../types"

module Crystal
class Program
getter? in_cleanup_phase = false

def cleanup(node)
transformer = CleanupTransformer.new(self)
@in_cleanup_phase = true
node = transformer.transform_loop(node)
@in_cleanup_phase = false
puts node if ENV["AFTER"]? == "1"
node
end

def cleanup_types
transformer = CleanupTransformer.new(self)

@in_cleanup_phase = true
after_inference_types.each do |type|
cleanup_type type, transformer
end
@@ -22,6 +28,8 @@ module Crystal
initializer.node = transformer.transform_loop(initializer.node)
end
end

@in_cleanup_phase = false
end

def cleanup_type(type, transformer)
2 changes: 1 addition & 1 deletion src/compiler/crystal/semantic/dependencies.cr
Original file line number Diff line number Diff line change
@@ -98,7 +98,7 @@ module Crystal
io << "["
each_with_index do |node, i|
io << ", " if i > 0
node.to_s(io)
node.inspect(io)
end
io << "]"
end
24 changes: 20 additions & 4 deletions src/compiler/crystal/semantic/main_visitor.cr
Original file line number Diff line number Diff line change
@@ -1701,6 +1701,16 @@ module Crystal

def visit(node : While)
old_while_vars = @while_vars

before_cond_vars_copy = @vars.dup

@vars.each do |name, var|
before_var = MetaVar.new(name)
before_var.bind_to(var)
before_var.nil_if_read = var.nil_if_read
@vars[name] = before_var
end

before_cond_vars = @vars.dup

request_type_filters do
@@ -1721,7 +1731,7 @@ module Crystal
node.body.accept self

endless_while = node.cond.true_literal?
merge_while_vars node.cond, endless_while, before_cond_vars, after_cond_vars, @vars, node.break_vars
merge_while_vars node.cond, endless_while, before_cond_vars_copy, before_cond_vars, after_cond_vars, @vars, node.break_vars

@while_stack.pop
@block = old_block
@@ -1740,7 +1750,7 @@ module Crystal
end

# Here we assign the types of variables after a while.
def merge_while_vars(cond, endless, before_cond_vars, after_cond_vars, while_vars, all_break_vars)
def merge_while_vars(cond, endless, before_cond_vars_copy, before_cond_vars, after_cond_vars, while_vars, all_break_vars)
after_while_vars = MetaVars.new

cond_var = get_while_cond_assign_target(cond)
@@ -1759,20 +1769,26 @@ module Crystal
# If there was a previous variable, we use that type merged
# with the last type inside the while.
elsif before_cond_var
before_cond_var.bind_to(while_var)
after_while_var = MetaVar.new(name)

# If the loop is endless
if endless
after_while_var.bind_to(while_var)
after_while_var.nil_if_read = while_var.nil_if_read
else
after_while_var.bind_to(before_cond_var)
# We need to bind to the variable *before* the condition, even
# after before the variables that are used in the condition
# `before_cond_vars` are modified in the while body
after_while_var.bind_to(before_cond_vars_copy[name])
after_while_var.bind_to(while_var)
after_while_var.nil_if_read = before_cond_var.nil_if_read || while_var.nil_if_read
end
after_while_vars[name] = after_while_var

# We must also bind the variable before the condition, because
# its type now must also include the type at the exit of the while
before_cond_var.bind_to(while_var)

# Otherwise, it's a new variable inside the while: used
# outside it must be nilable, unless the loop is endless.
else