From 80f1817ec850f44a80f7d10fafb79d45c184fab8 Mon Sep 17 00:00:00 2001 From: Alexander Momchilov Date: Tue, 30 Jul 2024 18:50:55 -0400 Subject: [PATCH 01/10] Add `expect_syntax_error` spec helper --- spec_helper.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/spec_helper.rb b/spec_helper.rb index af1c38587..34bc068a5 100644 --- a/spec_helper.rb +++ b/spec_helper.rb @@ -36,3 +36,7 @@ def report_on_exception=(value) ARGV.unshift $0 MSpecRun.main end + +def expect_syntax_error(ruby_src) + -> { eval(ruby_src) }.should raise_error(SyntaxError) +end From 2cc654628b1cb0cbf13c07b0153228d9a7ca4826 Mon Sep 17 00:00:00 2001 From: Alexander Momchilov Date: Tue, 30 Jul 2024 19:11:07 -0400 Subject: [PATCH 02/10] Add specs for reserved keywords --- language/reserved_keywords.rb | 155 ++++++++++++++++++++++++++++++++++ spec_helper.rb | 10 +++ 2 files changed, 165 insertions(+) create mode 100644 language/reserved_keywords.rb diff --git a/language/reserved_keywords.rb b/language/reserved_keywords.rb new file mode 100644 index 000000000..ca0f368a9 --- /dev/null +++ b/language/reserved_keywords.rb @@ -0,0 +1,155 @@ +require_relative '../spec_helper' + +describe "Ruby's reserved keywords" do + # Copied from Prism::Translation::Ripper + keywords = [ + "alias", + "and", + "begin", + "BEGIN", + "break", + "case", + "class", + "def", + "defined?", + "do", + "else", + "elsif", + "end", + "END", + "ensure", + "false", + "for", + "if", + "in", + "module", + "next", + "nil", + "not", + "or", + "redo", + "rescue", + "retry", + "return", + "self", + "super", + "then", + "true", + "undef", + "unless", + "until", + "when", + "while", + "yield", + "__ENCODING__", + "__FILE__", + "__LINE__" + ] + + keywords.each do |kw| + describe "keyword '#{kw}'" do + it "can't be used as local variable name" do + expect_syntax_error <<~RUBY + #{kw} = "a local variable named '#{kw}'" + RUBY + end + + invalid_ivar_names = ["defined?"] + + if invalid_ivar_names.include?(kw) + it "can't be used as an instance variable name" do + expect_syntax_error <<~RUBY + @#{kw} = "an instance variable named '#{kw}'" + RUBY + end + else + it "can be used as an instance variable name" do + result = sandboxed_eval <<~RUBY + @#{kw} = "an instance variable named '#{kw}'" + @#{kw} + RUBY + + result.should == "an instance variable named '#{kw}'" + end + end + + invalid_class_var_names = ["defined?"] + + if invalid_class_var_names.include?(kw) + it "can't be used as a class variable name" do + expect_syntax_error <<~RUBY + @@#{kw} = "a class variable named '#{kw}'" + RUBY + end + else + it "can be used as a class variable name" do + result = sandboxed_eval <<~RUBY + @@#{kw} = "a class variable named '#{kw}'" + @@#{kw} + RUBY + + result.should == "a class variable named '#{kw}'" + end + end + + invalid_global_var_names = ["defined?"] + + if invalid_global_var_names.include?(kw) + it "can't be used as a global variable name" do + expect_syntax_error <<~RUBY + $#{kw} = "a global variable named '#{kw}'" + RUBY + end + else + it "can be used as a global variable name" do + result = sandboxed_eval <<~RUBY + $#{kw} = "a global variable named '#{kw}'" + $#{kw} + RUBY + + result.should == "a global variable named '#{kw}'" + end + end + + it "can't be used as a positional parameter name" do + expect_syntax_error <<~RUBY + def x(#{kw}); end + RUBY + end + + invalid_kw_param_names = ["BEGIN","END","defined?"] + + if invalid_kw_param_names.include?(kw) + it "can't be used a keyword parameter name" do + expect_syntax_error <<~RUBY + def m(#{kw}:); end + RUBY + end + else + it "can be used a keyword parameter name" do + result = sandboxed_eval <<~RUBY + def m(#{kw}:) + binding.local_variable_get(:#{kw}) + end + + m(#{kw}: "an argument to '#{kw}'") + RUBY + + result.should == "an argument to '#{kw}'" + end + end + + it "can be used as a method name" do + result = sandboxed_eval <<~RUBY + def #{kw} + "a method named '#{kw}'" + end + + send(:#{kw}) + RUBY + + result.should == "a method named '#{kw}'" + end + end + end +end diff --git a/spec_helper.rb b/spec_helper.rb index 34bc068a5..21029ff8d 100644 --- a/spec_helper.rb +++ b/spec_helper.rb @@ -40,3 +40,13 @@ def report_on_exception=(value) def expect_syntax_error(ruby_src) -> { eval(ruby_src) }.should raise_error(SyntaxError) end + +# Evaluates the given Ruby source in a temporary Module, to prevent +# the surrounding context from being polluted with the new methods. +def sandboxed_eval(ruby_src) + Module.new do + # Allows instance methods defined by `ruby_src` to be called directly. + extend self + end + .class_eval(ruby_src) +end From 0f54929c3f0fcf80693c1318c5b72c30136e79fb Mon Sep 17 00:00:00 2001 From: Alexander Momchilov Date: Sat, 10 May 2025 14:43:38 -0400 Subject: [PATCH 03/10] Apply `%w[]` string array syntax --- language/reserved_keywords.rb | 96 +++++++++++++++++------------------ spec_helper.rb | 4 -- 2 files changed, 48 insertions(+), 52 deletions(-) diff --git a/language/reserved_keywords.rb b/language/reserved_keywords.rb index ca0f368a9..8c306c60f 100644 --- a/language/reserved_keywords.rb +++ b/language/reserved_keywords.rb @@ -2,54 +2,54 @@ describe "Ruby's reserved keywords" do # Copied from Prism::Translation::Ripper - keywords = [ - "alias", - "and", - "begin", - "BEGIN", - "break", - "case", - "class", - "def", - "defined?", - "do", - "else", - "elsif", - "end", - "END", - "ensure", - "false", - "for", - "if", - "in", - "module", - "next", - "nil", - "not", - "or", - "redo", - "rescue", - "retry", - "return", - "self", - "super", - "then", - "true", - "undef", - "unless", - "until", - "when", - "while", - "yield", - "__ENCODING__", - "__FILE__", - "__LINE__" + keywords = %w[ + alias + and + begin + BEGIN + break + case + class + def + defined? + do + else + elsif + end + END + ensure + false + for + if + in + module + next + nil + not + or + redo + rescue + retry + return + self + super + then + true + undef + unless + until + when + while + yield + __ENCODING__ + __FILE__ + __LINE__ ] keywords.each do |kw| describe "keyword '#{kw}'" do it "can't be used as local variable name" do - expect_syntax_error <<~RUBY + -> { eval(<<~RUBY) }.should raise_error(SyntaxError) #{kw} = "a local variable named '#{kw}'" RUBY end @@ -58,7 +58,7 @@ if invalid_ivar_names.include?(kw) it "can't be used as an instance variable name" do - expect_syntax_error <<~RUBY + -> { eval(<<~RUBY) }.should raise_error(SyntaxError) @#{kw} = "an instance variable named '#{kw}'" RUBY end @@ -77,7 +77,7 @@ if invalid_class_var_names.include?(kw) it "can't be used as a class variable name" do - expect_syntax_error <<~RUBY + -> { eval(<<~RUBY) }.should raise_error(SyntaxError) @@#{kw} = "a class variable named '#{kw}'" RUBY end @@ -96,7 +96,7 @@ if invalid_global_var_names.include?(kw) it "can't be used as a global variable name" do - expect_syntax_error <<~RUBY + -> { eval(<<~RUBY) }.should raise_error(SyntaxError) $#{kw} = "a global variable named '#{kw}'" RUBY end @@ -112,7 +112,7 @@ end it "can't be used as a positional parameter name" do - expect_syntax_error <<~RUBY + -> { eval(<<~RUBY) }.should raise_error(SyntaxError) def x(#{kw}); end RUBY end @@ -121,7 +121,7 @@ def x(#{kw}); end if invalid_kw_param_names.include?(kw) it "can't be used a keyword parameter name" do - expect_syntax_error <<~RUBY + -> { eval(<<~RUBY) }.should raise_error(SyntaxError) def m(#{kw}:); end RUBY end diff --git a/spec_helper.rb b/spec_helper.rb index 21029ff8d..cfbbdfc38 100644 --- a/spec_helper.rb +++ b/spec_helper.rb @@ -37,10 +37,6 @@ def report_on_exception=(value) MSpecRun.main end -def expect_syntax_error(ruby_src) - -> { eval(ruby_src) }.should raise_error(SyntaxError) -end - # Evaluates the given Ruby source in a temporary Module, to prevent # the surrounding context from being polluted with the new methods. def sandboxed_eval(ruby_src) From fde3d4b35a257ed0e7bab025d1bb3ba48ac94527 Mon Sep 17 00:00:00 2001 From: Alexander Momchilov Date: Sat, 10 May 2025 14:39:33 -0400 Subject: [PATCH 04/10] Replace `sandboxed_eval` with `eval` --- language/reserved_keywords.rb | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/language/reserved_keywords.rb b/language/reserved_keywords.rb index 8c306c60f..ed0f851e1 100644 --- a/language/reserved_keywords.rb +++ b/language/reserved_keywords.rb @@ -64,7 +64,7 @@ end else it "can be used as an instance variable name" do - result = sandboxed_eval <<~RUBY + result = eval <<~RUBY @#{kw} = "an instance variable named '#{kw}'" @#{kw} RUBY @@ -78,14 +78,18 @@ if invalid_class_var_names.include?(kw) it "can't be used as a class variable name" do -> { eval(<<~RUBY) }.should raise_error(SyntaxError) - @@#{kw} = "a class variable named '#{kw}'" + class C + @@#{kw} = "a class variable named '#{kw}'" + end RUBY end else it "can be used as a class variable name" do - result = sandboxed_eval <<~RUBY - @@#{kw} = "a class variable named '#{kw}'" - @@#{kw} + result = eval <<~RUBY + class C + @@#{kw} = "a class variable named '#{kw}'" + @@#{kw} + end RUBY result.should == "a class variable named '#{kw}'" @@ -102,7 +106,7 @@ end else it "can be used as a global variable name" do - result = sandboxed_eval <<~RUBY + result = eval <<~RUBY $#{kw} = "a global variable named '#{kw}'" $#{kw} RUBY @@ -122,13 +126,13 @@ def x(#{kw}); end if invalid_kw_param_names.include?(kw) it "can't be used a keyword parameter name" do -> { eval(<<~RUBY) }.should raise_error(SyntaxError) - def m(#{kw}:); end + def self.m(#{kw}:); end RUBY end else it "can be used a keyword parameter name" do - result = sandboxed_eval <<~RUBY - def m(#{kw}:) + result = eval <<~RUBY + def self.m(#{kw}:) binding.local_variable_get(:#{kw}) end @@ -140,8 +144,8 @@ def m(#{kw}:) end it "can be used as a method name" do - result = sandboxed_eval <<~RUBY - def #{kw} + result = eval <<~RUBY + def self.#{kw} "a method named '#{kw}'" end From 62a023af65ab175efab35d6cb49dccea3e1638b4 Mon Sep 17 00:00:00 2001 From: Alexander Momchilov Date: Sat, 7 Jun 2025 22:22:32 -0400 Subject: [PATCH 05/10] `instance_eval` to remove `self.` --- language/reserved_keywords.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/language/reserved_keywords.rb b/language/reserved_keywords.rb index ed0f851e1..ee9fee10e 100644 --- a/language/reserved_keywords.rb +++ b/language/reserved_keywords.rb @@ -126,13 +126,13 @@ def x(#{kw}); end if invalid_kw_param_names.include?(kw) it "can't be used a keyword parameter name" do -> { eval(<<~RUBY) }.should raise_error(SyntaxError) - def self.m(#{kw}:); end + def m(#{kw}:); end RUBY end else it "can be used a keyword parameter name" do - result = eval <<~RUBY - def self.m(#{kw}:) + result = instance_eval <<~RUBY + def m(#{kw}:) binding.local_variable_get(:#{kw}) end @@ -144,8 +144,8 @@ def self.m(#{kw}:) end it "can be used as a method name" do - result = eval <<~RUBY - def self.#{kw} + result = instance_eval <<~RUBY + def #{kw} "a method named '#{kw}'" end From a1d77260619dd36e45b954011c308625ee4fca71 Mon Sep 17 00:00:00 2001 From: Alexander Momchilov Date: Sat, 7 Jun 2025 22:28:40 -0400 Subject: [PATCH 06/10] Group together `defined?` special case --- language/reserved_keywords.rb | 40 +++++++++++++---------------------- 1 file changed, 15 insertions(+), 25 deletions(-) diff --git a/language/reserved_keywords.rb b/language/reserved_keywords.rb index ee9fee10e..748aa2a88 100644 --- a/language/reserved_keywords.rb +++ b/language/reserved_keywords.rb @@ -54,14 +54,26 @@ RUBY end - invalid_ivar_names = ["defined?"] - - if invalid_ivar_names.include?(kw) + if kw == "defined?" it "can't be used as an instance variable name" do -> { eval(<<~RUBY) }.should raise_error(SyntaxError) @#{kw} = "an instance variable named '#{kw}'" RUBY end + + it "can't be used as a class variable name" do + -> { eval(<<~RUBY) }.should raise_error(SyntaxError) + class C + @@#{kw} = "a class variable named '#{kw}'" + end + RUBY + end + + it "can't be used as a global variable name" do + -> { eval(<<~RUBY) }.should raise_error(SyntaxError) + $#{kw} = "a global variable named '#{kw}'" + RUBY + end else it "can be used as an instance variable name" do result = eval <<~RUBY @@ -71,19 +83,7 @@ result.should == "an instance variable named '#{kw}'" end - end - - invalid_class_var_names = ["defined?"] - if invalid_class_var_names.include?(kw) - it "can't be used as a class variable name" do - -> { eval(<<~RUBY) }.should raise_error(SyntaxError) - class C - @@#{kw} = "a class variable named '#{kw}'" - end - RUBY - end - else it "can be used as a class variable name" do result = eval <<~RUBY class C @@ -94,17 +94,7 @@ class C result.should == "a class variable named '#{kw}'" end - end - - invalid_global_var_names = ["defined?"] - if invalid_global_var_names.include?(kw) - it "can't be used as a global variable name" do - -> { eval(<<~RUBY) }.should raise_error(SyntaxError) - $#{kw} = "a global variable named '#{kw}'" - RUBY - end - else it "can be used as a global variable name" do result = eval <<~RUBY $#{kw} = "a global variable named '#{kw}'" From 1e727ee785e661d43bc76f63ebb69496c24d18a7 Mon Sep 17 00:00:00 2001 From: Alexander Momchilov Date: Sat, 7 Jun 2025 22:33:00 -0400 Subject: [PATCH 07/10] Remove interpolations --- language/reserved_keywords.rb | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/language/reserved_keywords.rb b/language/reserved_keywords.rb index 748aa2a88..c8e01d05b 100644 --- a/language/reserved_keywords.rb +++ b/language/reserved_keywords.rb @@ -50,58 +50,58 @@ describe "keyword '#{kw}'" do it "can't be used as local variable name" do -> { eval(<<~RUBY) }.should raise_error(SyntaxError) - #{kw} = "a local variable named '#{kw}'" + #{kw} = :local_variable RUBY end if kw == "defined?" it "can't be used as an instance variable name" do -> { eval(<<~RUBY) }.should raise_error(SyntaxError) - @#{kw} = "an instance variable named '#{kw}'" + @#{kw} = :instance_variable RUBY end it "can't be used as a class variable name" do -> { eval(<<~RUBY) }.should raise_error(SyntaxError) class C - @@#{kw} = "a class variable named '#{kw}'" + @@#{kw} = :class_variable end RUBY end it "can't be used as a global variable name" do -> { eval(<<~RUBY) }.should raise_error(SyntaxError) - $#{kw} = "a global variable named '#{kw}'" + $#{kw} = :global_variable RUBY end else it "can be used as an instance variable name" do result = eval <<~RUBY - @#{kw} = "an instance variable named '#{kw}'" + @#{kw} = :instance_variable @#{kw} RUBY - result.should == "an instance variable named '#{kw}'" + result.should == :instance_variable end it "can be used as a class variable name" do result = eval <<~RUBY class C - @@#{kw} = "a class variable named '#{kw}'" + @@#{kw} = :class_variable @@#{kw} end RUBY - result.should == "a class variable named '#{kw}'" + result.should == :class_variable end it "can be used as a global variable name" do result = eval <<~RUBY - $#{kw} = "a global variable named '#{kw}'" + $#{kw} = :global_variable $#{kw} RUBY - result.should == "a global variable named '#{kw}'" + result.should == :global_variable end end @@ -126,23 +126,23 @@ def m(#{kw}:) binding.local_variable_get(:#{kw}) end - m(#{kw}: "an argument to '#{kw}'") + m(#{kw}: :argument) RUBY - result.should == "an argument to '#{kw}'" + result.should == :argument end end it "can be used as a method name" do result = instance_eval <<~RUBY def #{kw} - "a method named '#{kw}'" + :method_return_value end send(:#{kw}) RUBY - result.should == "a method named '#{kw}'" + result.should == :method_return_value end end end From d6dec784a607040aec13f692cba3c89b573ff20c Mon Sep 17 00:00:00 2001 From: Alexander Momchilov Date: Sat, 7 Jun 2025 22:34:29 -0400 Subject: [PATCH 08/10] =?UTF-8?q?Rename=20`kw`=20=E2=86=92=20`name`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- language/reserved_keywords.rb | 42 +++++++++++++++++------------------ 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/language/reserved_keywords.rb b/language/reserved_keywords.rb index c8e01d05b..96f9769d9 100644 --- a/language/reserved_keywords.rb +++ b/language/reserved_keywords.rb @@ -46,39 +46,39 @@ __LINE__ ] - keywords.each do |kw| - describe "keyword '#{kw}'" do + keywords.each do |name| + describe "keyword '#{name}'" do it "can't be used as local variable name" do -> { eval(<<~RUBY) }.should raise_error(SyntaxError) - #{kw} = :local_variable + #{name} = :local_variable RUBY end - if kw == "defined?" + if name == "defined?" it "can't be used as an instance variable name" do -> { eval(<<~RUBY) }.should raise_error(SyntaxError) - @#{kw} = :instance_variable + @#{name} = :instance_variable RUBY end it "can't be used as a class variable name" do -> { eval(<<~RUBY) }.should raise_error(SyntaxError) class C - @@#{kw} = :class_variable + @@#{name} = :class_variable end RUBY end it "can't be used as a global variable name" do -> { eval(<<~RUBY) }.should raise_error(SyntaxError) - $#{kw} = :global_variable + $#{name} = :global_variable RUBY end else it "can be used as an instance variable name" do result = eval <<~RUBY - @#{kw} = :instance_variable - @#{kw} + @#{name} = :instance_variable + @#{name} RUBY result.should == :instance_variable @@ -87,8 +87,8 @@ class C it "can be used as a class variable name" do result = eval <<~RUBY class C - @@#{kw} = :class_variable - @@#{kw} + @@#{name} = :class_variable + @@#{name} end RUBY @@ -97,8 +97,8 @@ class C it "can be used as a global variable name" do result = eval <<~RUBY - $#{kw} = :global_variable - $#{kw} + $#{name} = :global_variable + $#{name} RUBY result.should == :global_variable @@ -107,26 +107,26 @@ class C it "can't be used as a positional parameter name" do -> { eval(<<~RUBY) }.should raise_error(SyntaxError) - def x(#{kw}); end + def x(#{name}); end RUBY end invalid_kw_param_names = ["BEGIN","END","defined?"] - if invalid_kw_param_names.include?(kw) + if invalid_kw_param_names.include?(name) it "can't be used a keyword parameter name" do -> { eval(<<~RUBY) }.should raise_error(SyntaxError) - def m(#{kw}:); end + def m(#{name}:); end RUBY end else it "can be used a keyword parameter name" do result = instance_eval <<~RUBY - def m(#{kw}:) - binding.local_variable_get(:#{kw}) + def m(#{name}:) + binding.local_variable_get(:#{name}) end - m(#{kw}: :argument) + m(#{name}: :argument) RUBY result.should == :argument @@ -135,11 +135,11 @@ def m(#{kw}:) it "can be used as a method name" do result = instance_eval <<~RUBY - def #{kw} + def #{name} :method_return_value end - send(:#{kw}) + send(:#{name}) RUBY result.should == :method_return_value From 8cc2c027e2eca9b1683c5e1088f3b39c05940fa2 Mon Sep 17 00:00:00 2001 From: Alexander Momchilov Date: Tue, 10 Jun 2025 19:49:06 -0400 Subject: [PATCH 09/10] Delete unused `sandboxed_eval` helper --- spec_helper.rb | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/spec_helper.rb b/spec_helper.rb index cfbbdfc38..af1c38587 100644 --- a/spec_helper.rb +++ b/spec_helper.rb @@ -36,13 +36,3 @@ def report_on_exception=(value) ARGV.unshift $0 MSpecRun.main end - -# Evaluates the given Ruby source in a temporary Module, to prevent -# the surrounding context from being polluted with the new methods. -def sandboxed_eval(ruby_src) - Module.new do - # Allows instance methods defined by `ruby_src` to be called directly. - extend self - end - .class_eval(ruby_src) -end From 618b86093ec632f03938002fe839d5ab9152272e Mon Sep 17 00:00:00 2001 From: Alexander Momchilov Date: Tue, 10 Jun 2025 19:50:43 -0400 Subject: [PATCH 10/10] Link to definitive list of keywords --- language/reserved_keywords.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/language/reserved_keywords.rb b/language/reserved_keywords.rb index 96f9769d9..6c40e34cc 100644 --- a/language/reserved_keywords.rb +++ b/language/reserved_keywords.rb @@ -1,7 +1,7 @@ require_relative '../spec_helper' describe "Ruby's reserved keywords" do - # Copied from Prism::Translation::Ripper + # Copied from https://github.com/ruby/ruby/blob/master/defs/keywords keywords = %w[ alias and