Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions spec/compiler/formatter/formatter_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,8 @@ describe Crystal::Formatter do
assert_format "def foo ( @x, @y) \n end", "def foo(@x, @y)\nend"
assert_format "def foo ( @@x) \n end", "def foo(@@x)\nend"
assert_format "def foo ( &@block) \n end", "def foo(&@block)\nend"
assert_format "def foo ( @select) \n end", "def foo(@select)\nend"
assert_format "def foo ( @@select) \n end", "def foo(@@select)\nend"
assert_format "def foo(a, &@b)\nend"
assert_format "def foo ( x = 1 ) \n end", "def foo(x = 1)\nend"
assert_format "def foo ( x : Int32 ) \n end", "def foo(x : Int32)\nend"
Expand Down
4 changes: 3 additions & 1 deletion spec/compiler/parser/parser_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ module Crystal
assert_syntax_error "def foo!=; end", "unexpected token: !="
assert_syntax_error "def foo?=(x); end", "unexpected token: ?"

# #5895 & #6042
# #5895, #6042, #5997
%w(
begin nil true false yield with abstract
def macro require case select if unless include
Expand All @@ -188,6 +188,8 @@ module Crystal
assert_syntax_error "def foo(#{kw}); end", "cannot use '#{kw}' as an argument name", 1, 9
assert_syntax_error "def foo(foo #{kw}); end", "cannot use '#{kw}' as an argument name", 1, 13
it_parses "def foo(#{kw} foo); end", Def.new("foo", [Arg.new("foo", external_name: kw.to_s)])
it_parses "def foo(@#{kw}); end", Def.new("foo", [Arg.new("__arg0", external_name: kw.to_s)], [Assign.new("@#{kw}".instance_var, "__arg0".var)] of ASTNode)
it_parses "def foo(@@#{kw}); end", Def.new("foo", [Arg.new("__arg0", external_name: kw.to_s)], [Assign.new("@@#{kw}".class_var, "__arg0".var)] of ASTNode)
end

it_parses "def self.foo\n1\nend", Def.new("foo", body: 1.int32, receiver: "self".var)
Expand Down
73 changes: 56 additions & 17 deletions src/compiler/crystal/syntax/parser.cr
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ module Crystal
@def_nest = 0
@type_nest = 0
@call_args_nest = 0
@block_arg_count = 0
@temp_arg_count = 0
@in_macro_expression = false
@stop_on_yield = 0
@inside_c_struct = false
Expand Down Expand Up @@ -1386,9 +1386,7 @@ module Crystal
next_token_skip_space

if @token.type == :"."
block_arg_name = "__arg#{@block_arg_count}"
@block_arg_count += 1

block_arg_name = temp_arg_name
obj = Var.new(block_arg_name)

@wants_regex = false
Expand Down Expand Up @@ -3662,6 +3660,20 @@ module Crystal
raise "when specified, external name must be different than internal name", @token
end

# If it's something like @select, we can't transform it to:
#
# @select = select
#
# because if someone uses `to_s` later it will produce invalid code.
# So we do something like:
#
# def method(select __arg0)
# @select = __arg0
# end
if !external_name && invalid_internal_name?(arg_name)
arg_name, external_name = temp_arg_name, arg_name
end

ivar = InstanceVar.new(@token.value.to_s).at(location)
var = Var.new(arg_name).at(location)
assign = Assign.new(ivar, var).at(location)
Expand All @@ -3678,6 +3690,11 @@ module Crystal
raise "when specified, external name must be different than internal name", @token
end

# Same case as :INSTANCE_VAR for things like @select
if !external_name && invalid_internal_name?(arg_name)
arg_name, external_name = temp_arg_name, arg_name
end

cvar = ClassVar.new(@token.value.to_s).at(location)
var = Var.new(arg_name).at(location)
assign = Assign.new(cvar, var).at(location)
Expand Down Expand Up @@ -3714,17 +3731,34 @@ module Crystal

def invalid_internal_name?(keyword)
case keyword
# These names are handled as keyword by `Parser#parse_atomic_without_location`.
# We cannot assign value into them and never reference them,
# so they are invalid internal name.
when :begin, :nil, :true, :false, :yield, :with, :abstract,
:def, :macro, :require, :case, :select, :if, :unless, :include,
:extend, :class, :struct, :module, :enum, :while, :until, :return,
:next, :break, :lib, :fun, :alias, :pointerof, :sizeof,
:instance_sizeof, :typeof, :private, :protected, :asm, :out,
# `end` is also invalid because it maybe terminate `def` block.
:end
true
when Symbol
case keyword
# These names are handled as keyword by `Parser#parse_atomic_without_location`.
# We cannot assign value into them and never reference them,
# so they are invalid internal name.
when :begin, :nil, :true, :false, :yield, :with, :abstract,
:def, :macro, :require, :case, :select, :if, :unless, :include,
:extend, :class, :struct, :module, :enum, :while, :until, :return,
:next, :break, :lib, :fun, :alias, :pointerof, :sizeof,
:instance_sizeof, :typeof, :private, :protected, :asm, :out,
# `end` is also invalid because it maybe terminate `def` block.
:end
true
else
false
end
when String
case keyword
when "begin", "nil", "true", "false", "yield", "with", "abstract",
"def", "macro", "require", "case", "select", "if", "unless", "include",
"extend", "class", "struct", "module", "enum", "while", "until", "return",
"next", "break", "lib", "fun", "alias", "pointerof", "sizeof",
"instance_sizeof", "typeof", "private", "protected", "asm", "out",
"end"
true
else
false
end
else
false
end
Expand Down Expand Up @@ -3995,8 +4029,7 @@ module Crystal
when :UNDERSCORE
arg_name = "_"
when :"("
block_arg_name = "__arg#{@block_arg_count}"
@block_arg_count += 1
block_arg_name = temp_arg_name

next_token_skip_space_or_newline

Expand Down Expand Up @@ -5689,6 +5722,12 @@ module Crystal

token
end

def temp_arg_name
arg_name = "__arg#{@temp_arg_count}"
@temp_arg_count += 1
arg_name
end
end

class StringInterpolation
Expand Down
4 changes: 3 additions & 1 deletion src/compiler/crystal/tools/formatter.cr
Original file line number Diff line number Diff line change
Expand Up @@ -1966,7 +1966,9 @@ module Crystal
end
end

if node.external_name != node.name
at_skip = at_skip?

if !at_skip && node.external_name != node.name
if node.external_name.empty?
write "_"
elsif @token.type == :DELIMITER_START
Expand Down