ruby-changes:54453
From: nobu <ko1@a...>
Date: Tue, 1 Jan 2019 00:10:43 +0900 (JST)
Subject: [ruby-changes:54453] nobu:r66667 (trunk): Method reference operator
nobu 2019-01-01 00:00:37 +0900 (Tue, 01 Jan 2019) New Revision: 66667 https://svn.ruby-lang.org/cgi-bin/viewvc.cgi?view=revision&revision=66667 Log: Method reference operator Introduce the new operator for method reference, `.:`. [Feature #12125] [Feature #13581] [EXPERIMENTAL] Modified files: trunk/NEWS trunk/ast.c trunk/compile.c trunk/defs/id.def trunk/ext/objspace/objspace.c trunk/ext/ripper/eventids2.c trunk/insns.def trunk/node.c trunk/node.h trunk/parse.y trunk/test/ripper/test_parser_events.rb trunk/test/ripper/test_scanner_events.rb trunk/test/ruby/test_ast.rb trunk/test/ruby/test_method.rb trunk/test/ruby/test_syntax.rb Index: defs/id.def =================================================================== --- defs/id.def (revision 66666) +++ defs/id.def (revision 66667) @@ -106,6 +106,7 @@ token_ops = %[\ https://github.com/ruby/ruby/blob/trunk/defs/id.def#L106 ANDOP && OROP || ANDDOT &. + METHREF .: ] class KeywordError < RuntimeError Index: insns.def =================================================================== --- insns.def (revision 66666) +++ insns.def (revision 66667) @@ -702,6 +702,16 @@ checktype https://github.com/ruby/ruby/blob/trunk/insns.def#L702 ret = (TYPE(val) == (int)type) ? Qtrue : Qfalse; } +/* get method reference. */ +DEFINE_INSN +methodref +(ID id) +(VALUE val) +(VALUE ret) +{ + ret = rb_obj_method(val, ID2SYM(id)); +} + /**********************************************************/ /* deal with control flow 1: class/module */ /**********************************************************/ Index: ast.c =================================================================== --- ast.c (revision 66666) +++ ast.c (revision 66667) @@ -468,6 +468,9 @@ node_children(rb_ast_t *ast, NODE *node) https://github.com/ruby/ruby/blob/trunk/ast.c#L468 NEW_CHILD(ast, node->nd_args)); case NODE_VCALL: return rb_ary_new_from_args(1, ID2SYM(node->nd_mid)); + case NODE_METHREF: + return rb_ary_new_from_args(2, NEW_CHILD(ast, node->nd_recv), + ID2SYM(node->nd_mid)); case NODE_SUPER: return rb_ary_new_from_node_args(ast, 1, node->nd_args); case NODE_ZSUPER: Index: compile.c =================================================================== --- compile.c (revision 66666) +++ compile.c (revision 66667) @@ -4583,9 +4583,11 @@ defined_expr0(rb_iseq_t *iseq, LINK_ANCH https://github.com/ruby/ruby/blob/trunk/compile.c#L4583 case NODE_OPCALL: case NODE_VCALL: case NODE_FCALL: + case NODE_METHREF: case NODE_ATTRASGN:{ const int explicit_receiver = (type == NODE_CALL || type == NODE_OPCALL || + type == NODE_METHREF || (type == NODE_ATTRASGN && !private_recv_p(node))); if (!lfinish[1] && (node->nd_args || explicit_receiver)) { @@ -7552,6 +7554,10 @@ iseq_compile_each0(rb_iseq_t *iseq, LINK https://github.com/ruby/ruby/blob/trunk/compile.c#L7554 } break; } + case NODE_METHREF: + CHECK(COMPILE_(ret, "receiver", node->nd_recv, popped)); + ADD_ELEM(ret, &new_insn_body(iseq, line, BIN(methodref), 1, ID2SYM(node->nd_mid))->link); + break; default: UNKNOWN_NODE("iseq_compile_each", node, COMPILE_NG); ng: Index: parse.y =================================================================== --- parse.y (revision 66666) +++ parse.y (revision 66667) @@ -886,6 +886,7 @@ static void token_info_warn(struct parse https://github.com/ruby/ruby/blob/trunk/parse.y#L886 %token tRSHFT RUBY_TOKEN(RSHFT) ">>" %token <id> tANDDOT RUBY_TOKEN(ANDDOT) "&." %token <id> tCOLON2 RUBY_TOKEN(COLON2) "::" +%token <id> tMETHREF RUBY_TOKEN(METHREF) ".:" %token tCOLON3 ":: at EXPR_BEG" %token <id> tOP_ASGN /* +=, -= etc. */ %token tASSOC "=>" @@ -2710,6 +2711,13 @@ primary : literal https://github.com/ruby/ruby/blob/trunk/parse.y#L2711 /*% %*/ /*% ripper: retry! %*/ } + | primary_value tMETHREF operation2 + { + /*%%%*/ + $$ = NEW_METHREF($1, $3, &@$); + /*% %*/ + /*% ripper: methref!($1, $3) %*/ + } ; primary_value : primary @@ -8060,12 +8068,30 @@ parser_yylex(struct parser_params *p) https://github.com/ruby/ruby/blob/trunk/parse.y#L8068 case '.': SET_LEX_STATE(EXPR_BEG); - if ((c = nextc(p)) == '.') { + switch (c = nextc(p)) { + case '.': if ((c = nextc(p)) == '.') { return tDOT3; } pushback(p, c); return tDOT2; + case ':': + switch (c = nextc(p)) { + default: + if (!parser_is_identchar(p)) break; + /* fallthru */ + case '!': case '%': case '&': case '*': case '+': + case '-': case '/': case '<': case '=': case '>': + case '[': case '^': case '`': case '|': case '~': + pushback(p, c); + SET_LEX_STATE(EXPR_DOT); + return tMETHREF; + case -1: + break; + } + pushback(p, c); + c = ':'; + break; } pushback(p, c); if (c != -1 && ISDIGIT(c)) { Index: ext/objspace/objspace.c =================================================================== --- ext/objspace/objspace.c (revision 66666) +++ ext/objspace/objspace.c (revision 66667) @@ -471,6 +471,7 @@ count_nodes(int argc, VALUE *argv, VALUE https://github.com/ruby/ruby/blob/trunk/ext/objspace/objspace.c#L471 COUNT_NODE(NODE_DSYM); COUNT_NODE(NODE_ATTRASGN); COUNT_NODE(NODE_LAMBDA); + COUNT_NODE(NODE_METHREF); #undef COUNT_NODE case NODE_LAST: break; } Index: ext/ripper/eventids2.c =================================================================== --- ext/ripper/eventids2.c (revision 66666) +++ ext/ripper/eventids2.c (revision 66667) @@ -259,6 +259,7 @@ static const struct token_assoc { https://github.com/ruby/ruby/blob/trunk/ext/ripper/eventids2.c#L259 {tSTAR, O(op)}, {tDSTAR, O(op)}, {tANDDOT, O(op)}, + {tMETHREF, O(op)}, {tSTRING_BEG, O(tstring_beg)}, {tSTRING_CONTENT, O(tstring_content)}, {tSTRING_DBEG, O(embexpr_beg)}, Index: NEWS =================================================================== --- NEWS (revision 66666) +++ NEWS (revision 66667) @@ -14,6 +14,9 @@ sufficient information, see the ChangeLo https://github.com/ruby/ruby/blob/trunk/NEWS#L14 === Language changes +* Method reference operator, <code>.:</code> is introduced as an + experimental feature. [Feature #12125] [Feature #13581] + === Core classes updates (outstanding ones only) === Stdlib updates (outstanding ones only) Index: test/ruby/test_ast.rb =================================================================== --- test/ruby/test_ast.rb (revision 66666) +++ test/ruby/test_ast.rb (revision 66667) @@ -249,4 +249,13 @@ class TestAst < Test::Unit::TestCase https://github.com/ruby/ruby/blob/trunk/test/ruby/test_ast.rb#L249 assert_equal(:b, mid) assert_equal(:SCOPE, defn.type) end + + def test_methref + node = RubyVM::AbstractSyntaxTree.parse("obj.:foo") + _, _, body = *node.children + assert_equal(:METHREF, body.type) + recv, mid = body.children + assert_equal(:VCALL, recv.type) + assert_equal(:foo, mid) + end end Index: test/ruby/test_method.rb =================================================================== --- test/ruby/test_method.rb (revision 66666) +++ test/ruby/test_method.rb (revision 66667) @@ -1095,4 +1095,23 @@ class TestMethod < Test::Unit::TestCase https://github.com/ruby/ruby/blob/trunk/test/ruby/test_method.rb#L1095 (f >> 5).call(2) } end + + def test_method_reference_operator + m = 1.:succ + assert_equal(1.method(:succ), m) + assert_equal(2, m.()) + m = 1.:+ + assert_equal(1.method(:+), m) + assert_equal(42, m.(41)) + m = 1.:-@ + assert_equal(1.method(:-@), m) + assert_equal(-1, m.()) + o = Object.new + def o.foo; 42; end + m = o.method(:foo) + assert_equal(m, o.:foo) + def o.method(m); nil; end + assert_equal(m, o.:foo) + assert_nil(o.method(:foo)) + end end Index: test/ruby/test_syntax.rb =================================================================== --- test/ruby/test_syntax.rb (revision 66666) +++ test/ruby/test_syntax.rb (revision 66667) @@ -908,6 +908,7 @@ eom https://github.com/ruby/ruby/blob/trunk/test/ruby/test_syntax.rb#L908 def test_fluent_dot assert_valid_syntax("a\n.foo") assert_valid_syntax("a\n&.foo") + assert_valid_syntax("a\n.:foo") end def test_no_warning_logop_literal Index: test/ripper/test_parser_events.rb =================================================================== --- test/ripper/test_parser_events.rb (revision 66666) +++ test/ripper/test_parser_events.rb (revision 66667) @@ -434,6 +434,13 @@ class TestRipper::ParserEvents < Test::U https://github.com/ruby/ruby/blob/trunk/test/ripper/test_parser_events.rb#L434 assert_equal "[call(ref(self),&.,foo,[])]", tree end + def test_methref + thru_methref = false + tree = parse("obj.:foo", :on_methref) {thru_methref = true} + assert_equal true, thru_methref + assert_equal "[methref(vcall(obj),foo)]", tree + end + def test_excessed_comma thru_excessed_comma = false parse("proc{|x,|}", :on_excessed_comma) {thru_excessed_comma = true} Index: test/ripper/test_scanner_events.rb =================================================================== --- test/ripper/test_scanner_events.rb (revision 66666) +++ test/ripper/test_scanner_events.rb (revision 66667) @@ -550,6 +550,8 @@ class TestRipper::ScannerEvents < Test:: https://github.com/ruby/ruby/blob/trunk/test/ripper/test_scanner_events.rb#L550 scan('op', ':[]=') assert_equal ['&.'], scan('op', 'a&.f') + assert_equal %w(.:), + scan('op', 'obj.:foo') assert_equal [], scan('op', %q[`make all`]) end Index: node.c =================================================================== --- node.c (revision 66666) +++ node.c (revision 66667) @@ -934,6 +934,15 @@ dump_node(VALUE buf, VALUE indent, int c https://github.com/ruby/ruby/blob/trunk/node.c#L934 F_NODE(nd_args, "arguments"); return; + case NODE_METHREF: + ANN("method reference"); + ANN("format: [nd_recv].:[nd_mid]"); + ANN("example: foo.:method"); + F_NODE(nd_recv, "receiver"); + LAST_NODE; + F_ID(nd_mid, "method name"); + return; + case NODE_LAMBDA: ANN("lambda expression"); ANN("format: -> [nd_body]"); Index: node.h =================================================================== --- node.h (revision 66666) +++ node.h (revision 66667) @@ -120,6 +120,7 @@ enum node_type { https://github.com/ruby/ruby/blob/trunk/node.h#L120 NODE_DSYM, NODE_ATTRASGN, NODE_LAMBDA, + NODE_METHREF, NODE_LAST }; @@ -361,6 +362,7 @@ typedef struct RNode { https://github.com/ruby/ruby/blob/trunk/node.h#L362 #define NEW_PREEXE(b,loc) NEW_SCOPE(b,loc) #define NEW_POSTEXE(b,loc) NEW_NODE(NODE_POSTEXE,0,b,0,loc) #define NEW_ATTRASGN(r,m,a,loc) NEW_NODE(NODE_ATTRASGN,r,m,a,loc) +#define NEW_METHREF(r,m,loc) NEW_NODE(NODE_METHREF,r,m,0,loc) #define NODE_SPECIAL_REQUIRED_KEYWORD ((NODE *)-1) #define NODE_REQUIRED_KEYWORD_P(node) ((node)->nd_value == NODE_SPECIAL_REQUIRED_KEYWORD) -- ML: ruby-changes@q... Info: http://www.atdot.net/~ko1/quickml/