Skip to content

Commit

Permalink
Merge pull request #2793 from crystal-lang/feature/local_var_assign_s…
Browse files Browse the repository at this point in the history
…hadow

GIve syntax error when using a local variable inside an assignment to…
Ary Borenszweig authored Jul 5, 2016
2 parents 6fb51a8 + 2c9c710 commit e8b1b84
Showing 13 changed files with 31 additions and 23 deletions.
2 changes: 2 additions & 0 deletions spec/compiler/parser/parser_spec.cr
Original file line number Diff line number Diff line change
@@ -1210,6 +1210,8 @@ describe "Parser" do

it_parses "Foo.foo(count: 3).bar { }", Call.new(Call.new("Foo".path, "foo", named_args: [NamedArgument.new("count", 3.int32)]), "bar", block: Block.new)

assert_syntax_error "a = a", "read before definition of local variable 'a'"

assert_syntax_error "{{ {{ 1 }} }}", "can't nest macro expressions"
assert_syntax_error "{{ {% begin %} }}", "can't nest macro expressions"

7 changes: 1 addition & 6 deletions spec/compiler/type_inference/var_spec.cr
Original file line number Diff line number Diff line change
@@ -36,18 +36,13 @@ describe "Type inference: var" do
", "undefined local variable or method 'something'"
end

it "reports read before assignment" do
assert_error "a = a + 1",
"undefined local variable or method 'a'"
end

it "reports there's no self" do
assert_error "self", "there's no self in this scope"
end

it "reports variable always nil" do
assert_error "1 == 2 ? (a = 1) : a",
"read before definition of 'a'"
"read before definition of local variable 'a'"
end

it "lets type on else side of if with a Bool | Nil union" do
2 changes: 1 addition & 1 deletion src/char.cr
Original file line number Diff line number Diff line change
@@ -75,7 +75,7 @@ struct Char
# 'f' + "oo" # => "foo"
# ```
def +(str : String)
bytesize = str.bytesize + bytesize
bytesize = str.bytesize + self.bytesize
String.new(bytesize) do |buffer|
count = 0
each_byte do |byte|
2 changes: 1 addition & 1 deletion src/compiler/crystal/codegen/ast.cr
Original file line number Diff line number Diff line change
@@ -55,7 +55,7 @@ module Crystal
end
end

str << name.gsub('@', '.')
str << self.name.gsub('@', '.')

next_def = self.next
while next_def
4 changes: 2 additions & 2 deletions src/compiler/crystal/codegen/codegen.cr
Original file line number Diff line number Diff line change
@@ -1306,9 +1306,9 @@ module Crystal
block_var = block_context.vars[arg.name]
if i == splat_index
exp_value = allocate_tuple(arg.type.as(TupleInstanceType)) do |tuple_type|
exp_value, exp_type = exp_values[j]
exp_value2, exp_type = exp_values[j]
j += 1
{exp_type, exp_value}
{exp_type, exp_value2}
end
exp_type = arg.type
else
4 changes: 2 additions & 2 deletions src/compiler/crystal/codegen/debug.cr
Original file line number Diff line number Diff line change
@@ -56,8 +56,8 @@ module Crystal

def create_debug_type(type : EnumType)
elements = type.types.map do |name, item|
value = if item.is_a?(Const) && (value = item.value).is_a?(NumberLiteral)
value.value.to_i64 rescue value.value.to_u64
value = if item.is_a?(Const) && (value2 = item.value).is_a?(NumberLiteral)
value2.value.to_i64 rescue value2.value.to_u64
else
0
end
6 changes: 3 additions & 3 deletions src/compiler/crystal/macros/methods.cr
Original file line number Diff line number Diff line change
@@ -1049,7 +1049,7 @@ module Crystal
end
when "size"
interpret_argless_method(method, args) do
type = type.instance_type
type = self.type.instance_type
case type
when TupleInstanceType
NumberLiteral.new(type.tuple_types.size)
@@ -1061,7 +1061,7 @@ module Crystal
end
when "keys"
interpret_argless_method(method, args) do
type = type.instance_type
type = self.type.instance_type
if type.is_a?(NamedTupleInstanceType)
ArrayLiteral.map(type.entries) { |entry| MacroId.new(entry.name) }
else
@@ -1070,7 +1070,7 @@ module Crystal
end
when "[]"
interpret_one_arg_method(method, args) do |arg|
type = type.instance_type
type = self.type.instance_type
case type
when NamedTupleInstanceType
case arg
2 changes: 1 addition & 1 deletion src/compiler/crystal/semantic/ast.cr
Original file line number Diff line number Diff line change
@@ -795,7 +795,7 @@ module Crystal
def update(from = nil)
return unless entries.all? &.value.type?

entries = entries.map do |element|
entries = self.entries.map do |element|
NamedArgumentType.new(element.key, element.value.type)
end

2 changes: 1 addition & 1 deletion src/compiler/crystal/semantic/call.cr
Original file line number Diff line number Diff line change
@@ -494,7 +494,7 @@ class Crystal::Call
arg = args.first
if arg.is_a?(SymbolLiteral)
name = arg.value
index = index = instance_type.name_index(name)
index = instance_type.name_index(name)
if index || nilable
indexer_def = yield instance_type, (index || -1)
indexer_match = Match.new(indexer_def, arg_types, MatchContext.new(owner, owner))
2 changes: 1 addition & 1 deletion src/compiler/crystal/semantic/call_error.cr
Original file line number Diff line number Diff line change
@@ -120,7 +120,7 @@ class Crystal::Call

# Check if it's an instance variable that was never assigned a value
if obj.is_a?(InstanceVar)
scope = scope.as(InstanceVarContainer)
scope = self.scope.as(InstanceVarContainer)
ivar = scope.lookup_instance_var(obj.name)
deps = ivar.dependencies?
if deps && deps.size == 1 && deps.first.same?(mod.nil_var)
2 changes: 1 addition & 1 deletion src/compiler/crystal/semantic/main_visitor.cr
Original file line number Diff line number Diff line change
@@ -157,7 +157,7 @@ module Crystal
special_var = define_special_var(node.name, mod.nil_var)
node.bind_to special_var
else
node.raise "read before definition of '#{node.name}'"
node.raise "read before definition of local variable '#{node.name}'"
end
end

6 changes: 3 additions & 3 deletions src/compiler/crystal/semantic/new.cr
Original file line number Diff line number Diff line change
@@ -121,7 +121,7 @@ module Crystal

# Forward block argument if any
if uses_block_arg
block_arg = block_arg.not_nil!
block_arg = self.block_arg.not_nil!
new_def.block_arg = block_arg.clone
new_def.uses_block_arg = true
end
@@ -193,7 +193,7 @@ module Crystal

# Forward block argument if any
if uses_block_arg
block_arg = block_arg.not_nil!
block_arg = self.block_arg.not_nil!
init.block_arg = Var.new(block_arg.name)
end

@@ -256,7 +256,7 @@ module Crystal
expansion.yields = yields
expansion.visibility = Visibility::Private if visibility.private?
if uses_block_arg
block_arg = block_arg.not_nil!
block_arg = self.block_arg.not_nil!
expansion.block_arg = block_arg.clone
expansion.uses_block_arg = true
end
13 changes: 12 additions & 1 deletion src/compiler/crystal/syntax/parser.cr
Original file line number Diff line number Diff line change
@@ -58,6 +58,7 @@ module Crystal
#
# then this flag is set to `true` when parsing `foo`'s arguments.
@stop_on_do = false
@assigned_vars = [] of String
end

def wants_doc=(wants_doc)
@@ -360,7 +361,13 @@ module Crystal
atomic = UninitializedVar.new(atomic, type).at(location)
return atomic
else
value = parse_op_assign_no_control
if atomic.is_a?(Var) && !var?(atomic.name)
@assigned_vars.push atomic.name
value = parse_op_assign_no_control
@assigned_vars.pop
else
value = parse_op_assign_no_control
end
end

pop_def if needs_new_scope
@@ -3661,6 +3668,10 @@ module Crystal
end
Var.new name
else
if !force_call && !block_arg && !named_args && !global && !has_parentheses && @assigned_vars.includes?(name)
raise "read before definition of local variable '#{name}'", location
end

Call.new nil, name, [] of ASTNode, nil, block_arg, named_args, global, name_column_number, has_parentheses
end
end

0 comments on commit e8b1b84

Please sign in to comment.