mirror of
https://github.com/ruby/ruby.git
synced 2022-11-09 12:17:21 -05:00
Assoc pattern matching (#3703)
[Feature #17260] One-line pattern matching using tASSOC R-assignment is rejected instead.
This commit is contained in:
parent
cffdacb15a
commit
52c630da00
Notes:
git
2020-10-26 18:00:56 +09:00
Merged-By: nobu <nobu@ruby-lang.org>
8 changed files with 37 additions and 65 deletions
37
parse.y
37
parse.y
|
@ -1150,7 +1150,7 @@ static int looking_at_eol_p(struct parser_params *p);
|
||||||
%type <node> string_contents xstring_contents regexp_contents string_content
|
%type <node> string_contents xstring_contents regexp_contents string_content
|
||||||
%type <node> words symbols symbol_list qwords qsymbols word_list qword_list qsym_list word
|
%type <node> words symbols symbol_list qwords qsymbols word_list qword_list qsym_list word
|
||||||
%type <node> literal numeric simple_numeric ssym dsym symbol cpath def_name defn_head defs_head
|
%type <node> literal numeric simple_numeric ssym dsym symbol cpath def_name defn_head defs_head
|
||||||
%type <node> top_compstmt top_stmts top_stmt begin_block rassign
|
%type <node> top_compstmt top_stmts top_stmt begin_block
|
||||||
%type <node> bodystmt compstmt stmts stmt_or_begin stmt expr arg primary command command_call method_call
|
%type <node> bodystmt compstmt stmts stmt_or_begin stmt expr arg primary command command_call method_call
|
||||||
%type <node> expr_value expr_value_do arg_value primary_value fcall rel_expr
|
%type <node> expr_value expr_value_do arg_value primary_value fcall rel_expr
|
||||||
%type <node> if_tail opt_else case_body case_args cases opt_rescue exc_list exc_var opt_ensure
|
%type <node> if_tail opt_else case_body case_args cases opt_rescue exc_list exc_var opt_ensure
|
||||||
|
@ -1242,7 +1242,7 @@ static int looking_at_eol_p(struct parser_params *p);
|
||||||
%nonassoc tLOWEST
|
%nonassoc tLOWEST
|
||||||
%nonassoc tLBRACE_ARG
|
%nonassoc tLBRACE_ARG
|
||||||
|
|
||||||
%nonassoc modifier_if modifier_unless modifier_while modifier_until keyword_in
|
%nonassoc modifier_if modifier_unless modifier_while modifier_until
|
||||||
%left keyword_or keyword_and
|
%left keyword_or keyword_and
|
||||||
%right keyword_not
|
%right keyword_not
|
||||||
%nonassoc keyword_defined
|
%nonassoc keyword_defined
|
||||||
|
@ -1548,40 +1548,9 @@ stmt : keyword_alias fitem {SET_LEX_STATE(EXPR_FNAME|EXPR_FITEM);} fitem
|
||||||
/*% %*/
|
/*% %*/
|
||||||
/*% ripper: massign!($1, $3) %*/
|
/*% ripper: massign!($1, $3) %*/
|
||||||
}
|
}
|
||||||
| rassign
|
|
||||||
| expr
|
| expr
|
||||||
;
|
;
|
||||||
|
|
||||||
rassign : arg_value tASSOC lhs
|
|
||||||
{
|
|
||||||
/*%%%*/
|
|
||||||
$$ = node_assign(p, $3, $1, &@$);
|
|
||||||
/*% %*/
|
|
||||||
/*% ripper: assign!($3, $1) %*/
|
|
||||||
}
|
|
||||||
| arg_value tASSOC mlhs
|
|
||||||
{
|
|
||||||
/*%%%*/
|
|
||||||
$$ = node_assign(p, $3, $1, &@$);
|
|
||||||
/*% %*/
|
|
||||||
/*% ripper: massign!($3, $1) %*/
|
|
||||||
}
|
|
||||||
| rassign tASSOC lhs
|
|
||||||
{
|
|
||||||
/*%%%*/
|
|
||||||
$$ = node_assign(p, $3, $1, &@$);
|
|
||||||
/*% %*/
|
|
||||||
/*% ripper: assign!($3, $1) %*/
|
|
||||||
}
|
|
||||||
| rassign tASSOC mlhs
|
|
||||||
{
|
|
||||||
/*%%%*/
|
|
||||||
$$ = node_assign(p, $3, $1, &@$);
|
|
||||||
/*% %*/
|
|
||||||
/*% ripper: massign!($3, $1) %*/
|
|
||||||
}
|
|
||||||
;
|
|
||||||
|
|
||||||
command_asgn : lhs '=' command_rhs
|
command_asgn : lhs '=' command_rhs
|
||||||
{
|
{
|
||||||
/*%%%*/
|
/*%%%*/
|
||||||
|
@ -1677,7 +1646,7 @@ expr : command_call
|
||||||
{
|
{
|
||||||
$$ = call_uni_op(p, method_cond(p, $2, &@2), '!', &@1, &@$);
|
$$ = call_uni_op(p, method_cond(p, $2, &@2), '!', &@1, &@$);
|
||||||
}
|
}
|
||||||
| arg keyword_in
|
| arg tASSOC
|
||||||
{
|
{
|
||||||
value_expr($1);
|
value_expr($1);
|
||||||
SET_LEX_STATE(EXPR_BEG|EXPR_LABEL);
|
SET_LEX_STATE(EXPR_BEG|EXPR_LABEL);
|
||||||
|
|
|
@ -32,10 +32,10 @@ ruby_version_is "2.7" do
|
||||||
|
|
||||||
describe "The -W command line option with :no-experimental" do
|
describe "The -W command line option with :no-experimental" do
|
||||||
it "suppresses experimental warnings" do
|
it "suppresses experimental warnings" do
|
||||||
result = ruby_exe('0 in a', args: '2>&1')
|
result = ruby_exe('case 0; in a; end', args: '2>&1')
|
||||||
result.should =~ /is experimental/
|
result.should =~ /is experimental/
|
||||||
|
|
||||||
result = ruby_exe('0 in a', options: '-W:no-experimental', args: '2>&1')
|
result = ruby_exe('case 0; in a; end', options: '-W:no-experimental', args: '2>&1')
|
||||||
result.should == ""
|
result.should == ""
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -68,13 +68,13 @@ describe "Processing RUBYOPT" do
|
||||||
|
|
||||||
it "suppresses experimental warnings for '-W:no-experimental'" do
|
it "suppresses experimental warnings for '-W:no-experimental'" do
|
||||||
ENV["RUBYOPT"] = '-W:no-experimental'
|
ENV["RUBYOPT"] = '-W:no-experimental'
|
||||||
result = ruby_exe('0 in a', args: '2>&1')
|
result = ruby_exe('case 0; in a; end', args: '2>&1')
|
||||||
result.should == ""
|
result.should == ""
|
||||||
end
|
end
|
||||||
|
|
||||||
it "suppresses deprecation and experimental warnings for '-W:no-deprecated -W:no-experimental'" do
|
it "suppresses deprecation and experimental warnings for '-W:no-deprecated -W:no-experimental'" do
|
||||||
ENV["RUBYOPT"] = '-W:no-deprecated -W:no-experimental'
|
ENV["RUBYOPT"] = '-W:no-deprecated -W:no-experimental'
|
||||||
result = ruby_exe('($; = "") in a', args: '2>&1')
|
result = ruby_exe('case ($; = ""); in a; end', args: '2>&1')
|
||||||
result.should == ""
|
result.should == ""
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -8,8 +8,8 @@ ruby_version_is '2.7' do
|
||||||
end
|
end
|
||||||
|
|
||||||
it "emits and suppresses warnings for :experimental" do
|
it "emits and suppresses warnings for :experimental" do
|
||||||
ruby_exe('Warning[:experimental] = true; eval("0 in a")', args: "2>&1").should =~ /is experimental/
|
ruby_exe('Warning[:experimental] = true; eval("case 0; in a; end")', args: "2>&1").should =~ /is experimental/
|
||||||
ruby_exe('Warning[:experimental] = false; eval("0 in a")', args: "2>&1").should == ""
|
ruby_exe('Warning[:experimental] = false; eval("case 0; in a; end")', args: "2>&1").should == ""
|
||||||
end
|
end
|
||||||
|
|
||||||
it "raises for unknown category" do
|
it "raises for unknown category" do
|
||||||
|
|
|
@ -9,11 +9,13 @@ ruby_version_is "2.7" do
|
||||||
ScratchPad.record []
|
ScratchPad.record []
|
||||||
end
|
end
|
||||||
|
|
||||||
it "can be standalone in operator that deconstructs value" do
|
ruby_version_is "3.0" do
|
||||||
eval(<<-RUBY).should == [0, 1]
|
it "can be standalone assoc operator that deconstructs value" do
|
||||||
[0, 1] in [a, b]
|
eval(<<-RUBY).should == [0, 1]
|
||||||
[a, b]
|
[0, 1] => [a, b]
|
||||||
RUBY
|
[a, b]
|
||||||
|
RUBY
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
it "extends case expression with case/in construction" do
|
it "extends case expression with case/in construction" do
|
||||||
|
|
|
@ -412,7 +412,7 @@ eot
|
||||||
[:@int, "0", [1, 5]],
|
[:@int, "0", [1, 5]],
|
||||||
[:in, [:aryptn, nil, nil, nil, nil], [[:void_stmt]], nil]],
|
[:in, [:aryptn, nil, nil, nil, nil], [[:void_stmt]], nil]],
|
||||||
|
|
||||||
[__LINE__, %q{ 0 in [*, a, *] }] =>
|
[__LINE__, %q{ 0 => [*, a, *] }] =>
|
||||||
[:case,
|
[:case,
|
||||||
[:@int, "0", [1, 0]],
|
[:@int, "0", [1, 0]],
|
||||||
[:in,
|
[:in,
|
||||||
|
@ -424,7 +424,7 @@ eot
|
||||||
nil,
|
nil,
|
||||||
nil]],
|
nil]],
|
||||||
|
|
||||||
[__LINE__, %q{ 0 in [*a, b, *c] }] =>
|
[__LINE__, %q{ 0 => [*a, b, *c] }] =>
|
||||||
[:case,
|
[:case,
|
||||||
[:@int, "0", [1, 0]],
|
[:@int, "0", [1, 0]],
|
||||||
[:in,
|
[:in,
|
||||||
|
@ -436,7 +436,7 @@ eot
|
||||||
nil,
|
nil,
|
||||||
nil]],
|
nil]],
|
||||||
|
|
||||||
[__LINE__, %q{ 0 in A(*a, b, c, *d) }] =>
|
[__LINE__, %q{ 0 => A(*a, b, c, *d) }] =>
|
||||||
[:case,
|
[:case,
|
||||||
[:@int, "0", [1, 0]],
|
[:@int, "0", [1, 0]],
|
||||||
[:in,
|
[:in,
|
||||||
|
|
|
@ -272,7 +272,7 @@ class TestPatternMatching < Test::Unit::TestCase
|
||||||
end
|
end
|
||||||
|
|
||||||
assert_syntax_error(%q{
|
assert_syntax_error(%q{
|
||||||
0 in [a, a]
|
0 => [a, a]
|
||||||
}, /duplicated variable name/)
|
}, /duplicated variable name/)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -737,10 +737,10 @@ END
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_find_pattern
|
def test_find_pattern
|
||||||
[0, 1, 2] in [*, 1 => a, *]
|
[0, 1, 2] => [*, 1 => a, *]
|
||||||
assert_equal(1, a)
|
assert_equal(1, a)
|
||||||
|
|
||||||
[0, 1, 2] in [*a, 1 => b, *c]
|
[0, 1, 2] => [*a, 1 => b, *c]
|
||||||
assert_equal([0], a)
|
assert_equal([0], a)
|
||||||
assert_equal(1, b)
|
assert_equal(1, b)
|
||||||
assert_equal([2], c)
|
assert_equal([2], c)
|
||||||
|
@ -763,7 +763,7 @@ END
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
[0, 1, 2] in [*a, 1 => b, 2 => c, *d]
|
[0, 1, 2] => [*a, 1 => b, 2 => c, *d]
|
||||||
assert_equal([0], a)
|
assert_equal([0], a)
|
||||||
assert_equal(1, b)
|
assert_equal(1, b)
|
||||||
assert_equal(2, c)
|
assert_equal(2, c)
|
||||||
|
@ -1451,18 +1451,18 @@ END
|
||||||
|
|
||||||
################################################################
|
################################################################
|
||||||
|
|
||||||
def test_modifier_in
|
def test_assoc
|
||||||
1 in a
|
1 => a
|
||||||
assert_equal 1, a
|
assert_equal 1, a
|
||||||
assert_raise(NoMatchingPatternError) do
|
assert_raise(NoMatchingPatternError) do
|
||||||
{a: 1} in {a: 0}
|
{a: 1} => {a: 0}
|
||||||
end
|
end
|
||||||
assert_syntax_error("if {} in {a:}; end", /void value expression/)
|
assert_syntax_error("if {} => {a:}; end", /void value expression/)
|
||||||
assert_syntax_error(%q{
|
assert_syntax_error(%q{
|
||||||
1 in a, b
|
1 => a, b
|
||||||
}, /unexpected/, '[ruby-core:95098]')
|
}, /unexpected/, '[ruby-core:95098]')
|
||||||
assert_syntax_error(%q{
|
assert_syntax_error(%q{
|
||||||
1 in a:
|
1 => a:
|
||||||
}, /unexpected/, '[ruby-core:95098]')
|
}, /unexpected/, '[ruby-core:95098]')
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -1480,7 +1480,7 @@ END
|
||||||
|
|
||||||
def test_experimental_warning
|
def test_experimental_warning
|
||||||
assert_experimental_warning("case 0; in 0; end")
|
assert_experimental_warning("case 0; in 0; end")
|
||||||
assert_experimental_warning("0 in 0")
|
assert_experimental_warning("0 => 0")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
END_of_GUARD
|
END_of_GUARD
|
||||||
|
|
|
@ -1430,8 +1430,6 @@ eom
|
||||||
assert_syntax_error('private def obj.foo = 42', /unexpected '='/)
|
assert_syntax_error('private def obj.foo = 42', /unexpected '='/)
|
||||||
assert_valid_syntax('private def obj.foo() = 42')
|
assert_valid_syntax('private def obj.foo() = 42')
|
||||||
assert_valid_syntax('private def obj.inc(x) = x + 1')
|
assert_valid_syntax('private def obj.inc(x) = x + 1')
|
||||||
eval('def self.inc(x) = x + 1 => @x')
|
|
||||||
assert_equal(:inc, @x)
|
|
||||||
k = Class.new do
|
k = Class.new do
|
||||||
class_eval('def rescued(x) = raise("to be caught") rescue "instance #{x}"')
|
class_eval('def rescued(x) = raise("to be caught") rescue "instance #{x}"')
|
||||||
class_eval('def self.rescued(x) = raise("to be caught") rescue "class #{x}"')
|
class_eval('def self.rescued(x) = raise("to be caught") rescue "class #{x}"')
|
||||||
|
@ -1715,10 +1713,13 @@ eom
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_rightward_assign
|
def test_rightward_assign
|
||||||
assert_equal(1, eval("1 => a"))
|
a = b = nil
|
||||||
assert_equal([2,3], eval("13.divmod(5) => a,b; [a, b]"))
|
EnvUtil.suppress_warning {eval("1 => a")}
|
||||||
assert_equal([2,3,2,3], eval("13.divmod(5) => a,b => c, d; [a, b, c, d]"))
|
assert_equal(1, a)
|
||||||
assert_equal(3, eval("1+2 => a"))
|
EnvUtil.suppress_warning {eval("13.divmod(5) => [a,b]")}
|
||||||
|
assert_equal([2,3], [a, b])
|
||||||
|
EnvUtil.suppress_warning {eval("1+2 => a")}
|
||||||
|
assert_equal(3, a)
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
Loading…
Reference in a new issue