summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--NEWS3
-rw-r--r--ast.c3
-rw-r--r--compile.c6
-rw-r--r--defs/id.def1
-rw-r--r--ext/objspace/objspace.c1
-rw-r--r--ext/ripper/eventids2.c1
-rw-r--r--insns.def10
-rw-r--r--node.c9
-rw-r--r--node.h2
-rw-r--r--parse.y28
-rw-r--r--test/ripper/test_parser_events.rb7
-rw-r--r--test/ripper/test_scanner_events.rb2
-rw-r--r--test/ruby/test_ast.rb9
-rw-r--r--test/ruby/test_method.rb19
-rw-r--r--test/ruby/test_syntax.rb1
15 files changed, 101 insertions, 1 deletions
diff --git a/NEWS b/NEWS
index daa1f0812c..6045717bb2 100644
--- a/NEWS
+++ b/NEWS
@@ -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)
diff --git a/ast.c b/ast.c
index c3f28eba0b..2ae738f7aa 100644
--- a/ast.c
+++ b/ast.c
@@ -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:
diff --git a/compile.c b/compile.c
index bece66fe94..233edf21c2 100644
--- a/compile.c
+++ b/compile.c
@@ -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)},
diff --git a/insns.def b/insns.def
index a062a9f119..2690539085 100644
--- a/insns.def
+++ b/insns.def
@@ -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 */
/**********************************************************/
diff --git a/node.c b/node.c
index d81a7319d3..7a962b9dea 100644
--- a/node.c
+++ b/node.c
@@ -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]");
diff --git a/node.h b/node.h
index 0f5a2a923a..4b16e23b3d 100644
--- a/node.h
+++ b/node.h
@@ -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)
diff --git a/parse.y b/parse.y
index 09afa6bedd..cf00b60b72 100644
--- a/parse.y
+++ b/parse.y
@@ -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