Skip to content

Commit 6349b03

Browse files
authored
Handle keyword local variables correctly (#1085)
Local variable can be a keyword. Example: `def f(if:0, and:0); binding.irb; end` IRB prepends local variable assignment code `a=_=nil;` to the input code but keyword local variables should be excluded.
1 parent ef60371 commit 6349b03

File tree

5 files changed

+53
-26
lines changed

5 files changed

+53
-26
lines changed

lib/irb/completion.rb

Lines changed: 2 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -12,26 +12,6 @@ class BaseCompletor # :nodoc:
1212

1313
# Set of reserved words used by Ruby, you should not use these for
1414
# constants or variables
15-
ReservedWords = %w[
16-
__ENCODING__ __LINE__ __FILE__
17-
BEGIN END
18-
alias and
19-
begin break
20-
case class
21-
def defined? do
22-
else elsif end ensure
23-
false for
24-
if in
25-
module
26-
next nil not
27-
or
28-
redo rescue retry return
29-
self super
30-
then true
31-
undef unless until
32-
when while
33-
yield
34-
]
3515

3616
HELP_COMMAND_PREPOSING = /\Ahelp\s+/
3717

@@ -459,12 +439,12 @@ def retrieve_completion_data(input, bind:, doc_namespace:)
459439
eval("#{perfect_match_var}.class.name", bind) rescue nil
460440
else
461441
candidates = (bind.eval_methods | bind.eval_private_methods | bind.local_variables | bind.eval_instance_variables | bind.eval_class_constants).collect{|m| m.to_s}
462-
candidates |= ReservedWords
442+
candidates |= RubyLex::RESERVED_WORDS.map(&:to_s)
463443
candidates.find{ |i| i == input }
464444
end
465445
else
466446
candidates = (bind.eval_methods | bind.eval_private_methods | bind.local_variables | bind.eval_instance_variables | bind.eval_class_constants).collect{|m| m.to_s}
467-
candidates |= ReservedWords
447+
candidates |= RubyLex::RESERVED_WORDS.map(&:to_s)
468448
candidates.grep(/^#{Regexp.quote(input)}/).sort
469449
end
470450
end

lib/irb/ruby-lex.rb

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,27 @@ class RubyLex
5252
on_words_beg on_qwords_beg
5353
]
5454

55+
RESERVED_WORDS = %i[
56+
__ENCODING__ __LINE__ __FILE__
57+
BEGIN END
58+
alias and
59+
begin break
60+
case class
61+
def defined? do
62+
else elsif end ensure
63+
false for
64+
if in
65+
module
66+
next nil not
67+
or
68+
redo rescue retry return
69+
self super
70+
then true
71+
undef unless until
72+
when while
73+
yield
74+
]
75+
5576
class TerminateLineInput < StandardError
5677
def initialize
5778
super("Terminate Line Input")
@@ -77,6 +98,10 @@ def compile_with_errors_suppressed(code, line_no: 1)
7798
end
7899

79100
def generate_local_variables_assign_code(local_variables)
101+
# Some reserved words could be a local variable
102+
# Example: def f(if: 1); binding.irb; end
103+
# These reserved words should be removed from assignment code
104+
local_variables -= RESERVED_WORDS
80105
"#{local_variables.join('=')}=nil;" unless local_variables.empty?
81106
end
82107

test/irb/test_color.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -167,8 +167,8 @@ def test_colorize_code_with_local_variables
167167
result_with_lvars = "a /(b +#{BLUE}#{BOLD}1#{CLEAR})/i"
168168

169169
assert_equal_with_term(result_without_lvars, code)
170-
assert_equal_with_term(result_with_lvar, code, local_variables: ['a'])
171-
assert_equal_with_term(result_with_lvars, code, local_variables: ['a', 'b'])
170+
assert_equal_with_term(result_with_lvar, code, local_variables: [:a])
171+
assert_equal_with_term(result_with_lvars, code, local_variables: [:a, :b])
172172
end
173173

174174
def test_colorize_code_complete_true

test/irb/test_irb.rb

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,20 @@ def test_underscore_stores_last_result
5656
assert_include output, "=> 12"
5757
end
5858

59+
def test_local_variable_handeld_correctly
60+
write_ruby <<~'RUBY'
61+
tap do |if:0, and:0, foo:100|
62+
binding.irb
63+
end
64+
RUBY
65+
66+
output = run_ruby_file do
67+
type 'foo /4#/ do'
68+
type 'exit!'
69+
end
70+
assert_include output, '=> 25'
71+
end
72+
5973
def test_commands_dont_override_stored_last_result
6074
write_ruby <<~'RUBY'
6175
binding.irb

test/irb/test_ruby_lex.rb

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,16 @@ def test_local_variables_dependent_code
3838
lines = ["a /1#/ do", "2"]
3939
assert_indent_level(lines, 1)
4040
assert_code_block_open(lines, true)
41-
assert_indent_level(lines, 0, local_variables: ['a'])
42-
assert_code_block_open(lines, false, local_variables: ['a'])
41+
assert_indent_level(lines, 0, local_variables: [:a])
42+
assert_code_block_open(lines, false, local_variables: [:a])
43+
end
44+
45+
def test_keyword_local_variables
46+
# Assuming `def f(if: 1, and: 2, ); binding.irb; end`
47+
local_variables = [:if, :and]
48+
lines = ['1 + 2']
49+
assert_indent_level(lines, 0, local_variables: local_variables)
50+
assert_code_block_open(lines, false, local_variables: local_variables)
4351
end
4452

4553
def test_literal_ends_with_space

0 commit comments

Comments
 (0)