diff --git a/spec/compiler/formatter/formatter_spec.cr b/spec/compiler/formatter/formatter_spec.cr index 4bf1d468f7aa..c342ad859cc0 100644 --- a/spec/compiler/formatter/formatter_spec.cr +++ b/spec/compiler/formatter/formatter_spec.cr @@ -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" diff --git a/spec/compiler/parser/parser_spec.cr b/spec/compiler/parser/parser_spec.cr index 9f16d059515e..dc2ad1413ef9 100644 --- a/spec/compiler/parser/parser_spec.cr +++ b/spec/compiler/parser/parser_spec.cr @@ -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 @@ -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) diff --git a/src/compiler/crystal/syntax/parser.cr b/src/compiler/crystal/syntax/parser.cr index 69911b6d2040..d93a726da6b8 100644 --- a/src/compiler/crystal/syntax/parser.cr +++ b/src/compiler/crystal/syntax/parser.cr @@ -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 @@ -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 @@ -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) @@ -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) @@ -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 @@ -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 @@ -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 diff --git a/src/compiler/crystal/tools/formatter.cr b/src/compiler/crystal/tools/formatter.cr index 68bf3bc11c85..0b815c42bfef 100644 --- a/src/compiler/crystal/tools/formatter.cr +++ b/src/compiler/crystal/tools/formatter.cr @@ -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