Method reference operator

Introduce the new operator for method reference, `.:`.
[Feature #12125] [Feature #13581]
[EXPERIMENTAL]

git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@66667 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
This commit is contained in:
nobu 2018-12-31 15:00:37 +00:00
parent 4a6f763330
commit 67c5747369
15 changed files with 101 additions and 1 deletions

3
NEWS
View File

@ -14,6 +14,9 @@ sufficient information, see the ChangeLog file or Redmine
=== Language changes === Language changes
* Method reference operator, <code>.:</code> is introduced as an
experimental feature. [Feature #12125] [Feature #13581]
=== Core classes updates (outstanding ones only) === Core classes updates (outstanding ones only)
=== Stdlib updates (outstanding ones only) === Stdlib updates (outstanding ones only)

3
ast.c
View File

@ -468,6 +468,9 @@ node_children(rb_ast_t *ast, NODE *node)
NEW_CHILD(ast, node->nd_args)); NEW_CHILD(ast, node->nd_args));
case NODE_VCALL: case NODE_VCALL:
return rb_ary_new_from_args(1, ID2SYM(node->nd_mid)); 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: case NODE_SUPER:
return rb_ary_new_from_node_args(ast, 1, node->nd_args); return rb_ary_new_from_node_args(ast, 1, node->nd_args);
case NODE_ZSUPER: case NODE_ZSUPER:

View File

@ -4583,9 +4583,11 @@ defined_expr0(rb_iseq_t *iseq, LINK_ANCHOR *const ret,
case NODE_OPCALL: case NODE_OPCALL:
case NODE_VCALL: case NODE_VCALL:
case NODE_FCALL: case NODE_FCALL:
case NODE_METHREF:
case NODE_ATTRASGN:{ case NODE_ATTRASGN:{
const int explicit_receiver = const int explicit_receiver =
(type == NODE_CALL || type == NODE_OPCALL || (type == NODE_CALL || type == NODE_OPCALL ||
type == NODE_METHREF ||
(type == NODE_ATTRASGN && !private_recv_p(node))); (type == NODE_ATTRASGN && !private_recv_p(node)));
if (!lfinish[1] && (node->nd_args || explicit_receiver)) { 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; 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: default:
UNKNOWN_NODE("iseq_compile_each", node, COMPILE_NG); UNKNOWN_NODE("iseq_compile_each", node, COMPILE_NG);
ng: ng:

View File

@ -106,6 +106,7 @@ token_ops = %[\
ANDOP && ANDOP &&
OROP || OROP ||
ANDDOT &. ANDDOT &.
METHREF .:
] ]
class KeywordError < RuntimeError class KeywordError < RuntimeError

View File

@ -471,6 +471,7 @@ count_nodes(int argc, VALUE *argv, VALUE os)
COUNT_NODE(NODE_DSYM); COUNT_NODE(NODE_DSYM);
COUNT_NODE(NODE_ATTRASGN); COUNT_NODE(NODE_ATTRASGN);
COUNT_NODE(NODE_LAMBDA); COUNT_NODE(NODE_LAMBDA);
COUNT_NODE(NODE_METHREF);
#undef COUNT_NODE #undef COUNT_NODE
case NODE_LAST: break; case NODE_LAST: break;
} }

View File

@ -259,6 +259,7 @@ static const struct token_assoc {
{tSTAR, O(op)}, {tSTAR, O(op)},
{tDSTAR, O(op)}, {tDSTAR, O(op)},
{tANDDOT, O(op)}, {tANDDOT, O(op)},
{tMETHREF, O(op)},
{tSTRING_BEG, O(tstring_beg)}, {tSTRING_BEG, O(tstring_beg)},
{tSTRING_CONTENT, O(tstring_content)}, {tSTRING_CONTENT, O(tstring_content)},
{tSTRING_DBEG, O(embexpr_beg)}, {tSTRING_DBEG, O(embexpr_beg)},

View File

@ -702,6 +702,16 @@ checktype
ret = (TYPE(val) == (int)type) ? Qtrue : Qfalse; 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 */ /* deal with control flow 1: class/module */
/**********************************************************/ /**********************************************************/

9
node.c
View File

@ -934,6 +934,15 @@ dump_node(VALUE buf, VALUE indent, int comment, const NODE * node)
F_NODE(nd_args, "arguments"); F_NODE(nd_args, "arguments");
return; 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: case NODE_LAMBDA:
ANN("lambda expression"); ANN("lambda expression");
ANN("format: -> [nd_body]"); ANN("format: -> [nd_body]");

2
node.h
View File

@ -120,6 +120,7 @@ enum node_type {
NODE_DSYM, NODE_DSYM,
NODE_ATTRASGN, NODE_ATTRASGN,
NODE_LAMBDA, NODE_LAMBDA,
NODE_METHREF,
NODE_LAST NODE_LAST
}; };
@ -361,6 +362,7 @@ typedef struct RNode {
#define NEW_PREEXE(b,loc) NEW_SCOPE(b,loc) #define NEW_PREEXE(b,loc) NEW_SCOPE(b,loc)
#define NEW_POSTEXE(b,loc) NEW_NODE(NODE_POSTEXE,0,b,0,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_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_SPECIAL_REQUIRED_KEYWORD ((NODE *)-1)
#define NODE_REQUIRED_KEYWORD_P(node) ((node)->nd_value == NODE_SPECIAL_REQUIRED_KEYWORD) #define NODE_REQUIRED_KEYWORD_P(node) ((node)->nd_value == NODE_SPECIAL_REQUIRED_KEYWORD)

28
parse.y
View File

@ -886,6 +886,7 @@ static void token_info_warn(struct parser_params *p, const char *token, token_in
%token tRSHFT RUBY_TOKEN(RSHFT) ">>" %token tRSHFT RUBY_TOKEN(RSHFT) ">>"
%token <id> tANDDOT RUBY_TOKEN(ANDDOT) "&." %token <id> tANDDOT RUBY_TOKEN(ANDDOT) "&."
%token <id> tCOLON2 RUBY_TOKEN(COLON2) "::" %token <id> tCOLON2 RUBY_TOKEN(COLON2) "::"
%token <id> tMETHREF RUBY_TOKEN(METHREF) ".:"
%token tCOLON3 ":: at EXPR_BEG" %token tCOLON3 ":: at EXPR_BEG"
%token <id> tOP_ASGN /* +=, -= etc. */ %token <id> tOP_ASGN /* +=, -= etc. */
%token tASSOC "=>" %token tASSOC "=>"
@ -2710,6 +2711,13 @@ primary : literal
/*% %*/ /*% %*/
/*% ripper: retry! %*/ /*% ripper: retry! %*/
} }
| primary_value tMETHREF operation2
{
/*%%%*/
$$ = NEW_METHREF($1, $3, &@$);
/*% %*/
/*% ripper: methref!($1, $3) %*/
}
; ;
primary_value : primary primary_value : primary
@ -8060,12 +8068,30 @@ parser_yylex(struct parser_params *p)
case '.': case '.':
SET_LEX_STATE(EXPR_BEG); SET_LEX_STATE(EXPR_BEG);
if ((c = nextc(p)) == '.') { switch (c = nextc(p)) {
case '.':
if ((c = nextc(p)) == '.') { if ((c = nextc(p)) == '.') {
return tDOT3; return tDOT3;
} }
pushback(p, c); pushback(p, c);
return tDOT2; 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); pushback(p, c);
if (c != -1 && ISDIGIT(c)) { if (c != -1 && ISDIGIT(c)) {

View File

@ -434,6 +434,13 @@ class TestRipper::ParserEvents < Test::Unit::TestCase
assert_equal "[call(ref(self),&.,foo,[])]", tree assert_equal "[call(ref(self),&.,foo,[])]", tree
end 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 def test_excessed_comma
thru_excessed_comma = false thru_excessed_comma = false
parse("proc{|x,|}", :on_excessed_comma) {thru_excessed_comma = true} parse("proc{|x,|}", :on_excessed_comma) {thru_excessed_comma = true}

View File

@ -550,6 +550,8 @@ class TestRipper::ScannerEvents < Test::Unit::TestCase
scan('op', ':[]=') scan('op', ':[]=')
assert_equal ['&.'], assert_equal ['&.'],
scan('op', 'a&.f') scan('op', 'a&.f')
assert_equal %w(.:),
scan('op', 'obj.:foo')
assert_equal [], assert_equal [],
scan('op', %q[`make all`]) scan('op', %q[`make all`])
end end

View File

@ -249,4 +249,13 @@ class TestAst < Test::Unit::TestCase
assert_equal(:b, mid) assert_equal(:b, mid)
assert_equal(:SCOPE, defn.type) assert_equal(:SCOPE, defn.type)
end 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 end

View File

@ -1095,4 +1095,23 @@ class TestMethod < Test::Unit::TestCase
(f >> 5).call(2) (f >> 5).call(2)
} }
end 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 end

View File

@ -908,6 +908,7 @@ eom
def test_fluent_dot def test_fluent_dot
assert_valid_syntax("a\n.foo") assert_valid_syntax("a\n.foo")
assert_valid_syntax("a\n&.foo") assert_valid_syntax("a\n&.foo")
assert_valid_syntax("a\n.:foo")
end end
def test_no_warning_logop_literal def test_no_warning_logop_literal