Navigation Menu

Skip to content

Commit

Permalink
Fixed #4242: while and until don't restrict variables after them
Browse files Browse the repository at this point in the history
  • Loading branch information
asterite committed Apr 10, 2017
1 parent cf207b0 commit b7e048b
Show file tree
Hide file tree
Showing 2 changed files with 74 additions and 8 deletions.
45 changes: 45 additions & 0 deletions spec/compiler/semantic/while_spec.cr
Expand Up @@ -116,4 +116,49 @@ describe "Semantic: while" do
x
)) { union_of(int32, char) }
end

it "restricts type after while (#4242)" do
assert_type(%(
a = nil
while a.nil?
a = 1
end
a
)) { int32 }
end

it "restricts type after while with not (#4242)" do
assert_type(%(
a = nil
while !a
a = 1
end
a
)) { int32 }
end

it "restricts type after while with not and and (#4242)" do
assert_type(%(
a = nil
b = nil
while !(a && b)
a = 1
b = 'a'
end
{a, b}
)) { tuple_of [int32, char] }
end

it "doesn't restrict type after while if there's a break (#4242)" do
assert_type(%(
a = nil
while a.nil?
if 1 == 1
break
end
a = 1
end
a
)) { nilable int32 }
end
end
37 changes: 29 additions & 8 deletions src/compiler/crystal/semantic/main_visitor.cr
Expand Up @@ -88,6 +88,9 @@ module Crystal
@or_left_type_filters : TypeFilters?
@or_right_type_filters : TypeFilters?

# Type filters for `exp` in `!exp`, used after a `while`
@before_not_type_filters : TypeFilters?

def initialize(program, vars = MetaVars.new, @typed_def = nil, meta_vars = nil)
super(program, vars)
@while_stack = [] of While
Expand Down Expand Up @@ -1977,6 +1980,16 @@ module Crystal
node.type = program.no_return
return
end

if node.cond.is_a?(Not)
after_while_type_filters = @not_type_filters
else
after_while_type_filters = not_type_filters(node.cond, cond_type_filters)
end

if after_while_type_filters
filter_vars(after_while_type_filters)
end
end

node.type = @program.nil
Expand Down Expand Up @@ -2849,18 +2862,26 @@ module Crystal
node.exp.add_observer node
node.update

if needs_type_filters? && (type_filters = @type_filters)
# `Not` can only deduce type filters for these nodes
case exp = node.exp
if needs_type_filters?
@not_type_filters = @type_filters
@type_filters = not_type_filters(node.exp, @type_filters)
else
@type_filters = nil
@not_type_filters = nil
end

false
end

private def not_type_filters(exp, type_filters)
if type_filters
case exp
when Var, IsA, RespondsTo, Not
@type_filters = type_filters.not
return false
return type_filters.not
end
end

@type_filters = nil

false
nil
end

def visit(node : VisibilityModifier)
Expand Down

0 comments on commit b7e048b

Please sign in to comment.