diff options
-rw-r--r-- | NEWS | 3 | ||||
-rw-r--r-- | ast.c | 3 | ||||
-rw-r--r-- | compile.c | 6 | ||||
-rw-r--r-- | defs/id.def | 1 | ||||
-rw-r--r-- | ext/objspace/objspace.c | 1 | ||||
-rw-r--r-- | ext/ripper/eventids2.c | 1 | ||||
-rw-r--r-- | insns.def | 10 | ||||
-rw-r--r-- | node.c | 9 | ||||
-rw-r--r-- | node.h | 2 | ||||
-rw-r--r-- | parse.y | 28 | ||||
-rw-r--r-- | test/ripper/test_parser_events.rb | 7 | ||||
-rw-r--r-- | test/ripper/test_scanner_events.rb | 2 | ||||
-rw-r--r-- | test/ruby/test_ast.rb | 9 | ||||
-rw-r--r-- | test/ruby/test_method.rb | 19 | ||||
-rw-r--r-- | test/ruby/test_syntax.rb | 1 |
15 files changed, 101 insertions, 1 deletions
@@ -14,6 +14,9 @@ sufficient information, see the ChangeLog file or Redmine === 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) @@ -468,6 +468,9 @@ node_children(rb_ast_t *ast, NODE *node) 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: @@ -4583,9 +4583,11 @@ defined_expr0(rb_iseq_t *iseq, LINK_ANCHOR *const ret, 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_ANCHOR *const ret, const NODE *node, in } 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: diff --git a/defs/id.def b/defs/id.def index bb7cc6f922..9ab9af950d 100644 --- a/defs/id.def +++ b/defs/id.def @@ -106,6 +106,7 @@ token_ops = %[\ ANDOP && OROP || ANDDOT &. + METHREF .: ] class KeywordError < RuntimeError diff --git a/ext/objspace/objspace.c b/ext/objspace/objspace.c index 1ac69af844..dced0740bc 100644 --- a/ext/objspace/objspace.c +++ b/ext/objspace/objspace.c @@ -471,6 +471,7 @@ count_nodes(int argc, VALUE *argv, VALUE os) COUNT_NODE(NODE_DSYM); COUNT_NODE(NODE_ATTRASGN); COUNT_NODE(NODE_LAMBDA); + COUNT_NODE(NODE_METHREF); #undef COUNT_NODE case NODE_LAST: break; } diff --git a/ext/ripper/eventids2.c b/ext/ripper/eventids2.c index e876e95ab3..039034af48 100644 --- a/ext/ripper/eventids2.c +++ b/ext/ripper/eventids2.c @@ -259,6 +259,7 @@ static const struct token_assoc { {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)}, @@ -702,6 +702,16 @@ checktype 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 */ /**********************************************************/ @@ -934,6 +934,15 @@ dump_node(VALUE buf, VALUE indent, int comment, const NODE * node) 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]"); @@ -120,6 +120,7 @@ enum node_type { NODE_DSYM, NODE_ATTRASGN, NODE_LAMBDA, + NODE_METHREF, NODE_LAST }; @@ -361,6 +362,7 @@ typedef struct RNode { #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) @@ -886,6 +886,7 @@ static void token_info_warn(struct parser_params *p, const char *token, token_in %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 /*% %*/ /*% 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) 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)) { diff --git a/test/ripper/test_parser_events.rb b/test/ripper/test_parser_events.rb index 95ec661fcf..3c78daf16e 100644 --- a/test/ripper/test_parser_events.rb +++ b/test/ripper/test_parser_events.rb @@ -434,6 +434,13 @@ class TestRipper::ParserEvents < Test::Unit::TestCase 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} diff --git a/test/ripper/test_scanner_events.rb b/test/ripper/test_scanner_events.rb index c9fce34a77..94df6616e8 100644 --- a/test/ripper/test_scanner_events.rb +++ b/test/ripper/test_scanner_events.rb @@ -550,6 +550,8 @@ class TestRipper::ScannerEvents < Test::Unit::TestCase scan('op', ':[]=') assert_equal ['&.'], scan('op', 'a&.f') + assert_equal %w(.:), + scan('op', 'obj.:foo') assert_equal [], scan('op', %q[`make all`]) end diff --git a/test/ruby/test_ast.rb b/test/ruby/test_ast.rb index 055567d574..ed4bcb37c0 100644 --- a/test/ruby/test_ast.rb +++ b/test/ruby/test_ast.rb @@ -249,4 +249,13 @@ class TestAst < Test::Unit::TestCase 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 diff --git a/test/ruby/test_method.rb b/test/ruby/test_method.rb index a9051b81fa..5cfcdb8dc5 100644 --- a/test/ruby/test_method.rb +++ b/test/ruby/test_method.rb @@ -1095,4 +1095,23 @@ class TestMethod < Test::Unit::TestCase (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 diff --git a/test/ruby/test_syntax.rb b/test/ruby/test_syntax.rb index dee9187a9c..5534056327 100644 --- a/test/ruby/test_syntax.rb +++ b/test/ruby/test_syntax.rb @@ -908,6 +908,7 @@ eom 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 |