Safe navigation operator

* compile.c (iseq_peephole_optimize): peephole optimization for
  branchnil jumps.
* compile.c (iseq_compile_each): generate save navigation operator
  code.
* insns.def (branchnil): new opcode to pop the tos and branch if
  it is nil.
* parse.y (NEW_QCALL, call_op, parser_yylex): parse token '.?'.
  [Feature #11537]

git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@52214 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
This commit is contained in:
nobu 2015-10-22 06:30:12 +00:00
parent 5a599dde0c
commit a356fe1c35
10 changed files with 164 additions and 41 deletions

View File

@ -1,3 +1,17 @@
Thu Oct 22 15:30:08 2015 Nobuyoshi Nakada <nobu@ruby-lang.org>
* compile.c (iseq_peephole_optimize): peephole optimization for
branchnil jumps.
* compile.c (iseq_compile_each): generate save navigation operator
code.
* insns.def (branchnil): new opcode to pop the tos and branch if
it is nil.
* parse.y (NEW_QCALL, call_op, parser_yylex): parse token '.?'.
[Feature #11537]
Thu Oct 22 13:16:19 2015 Guilherme Reis Campos <guilhermekbsa@gmail.com> Thu Oct 22 13:16:19 2015 Guilherme Reis Campos <guilhermekbsa@gmail.com>
* dir.c (ruby_brace_expand): glob brace expansion edge case fix. * dir.c (ruby_brace_expand): glob brace expansion edge case fix.

11
NEWS
View File

@ -18,6 +18,17 @@ with all sufficient information, see the ChangeLog file.
* besides, --enable/--disable=frozen-string-literal options also have * besides, --enable/--disable=frozen-string-literal options also have
been introduced. been introduced.
* safe navigation operator:
* new method call syntax, `object.?foo', method #foo is called on
`object' if it is not nil.
this is similar to `try!' in ActiveSupport, except for:
* method name is syntactically required
obj.try! {} # valid
obj.? {} # syntax error
* attribute assignment is valid
obj.?attr += 1
=== Core classes updates (outstanding ones only) === Core classes updates (outstanding ones only)
* ARGF * ARGF

View File

@ -1942,6 +1942,7 @@ iseq_peephole_optimize(rb_iseq_t *iseq, LINK_ELEMENT *list, const int do_tailcal
} }
if (iobj->insn_id == BIN(branchif) || if (iobj->insn_id == BIN(branchif) ||
iobj->insn_id == BIN(branchnil) ||
iobj->insn_id == BIN(branchunless)) { iobj->insn_id == BIN(branchunless)) {
/* /*
* if L1 * if L1
@ -1955,6 +1956,31 @@ iseq_peephole_optimize(rb_iseq_t *iseq, LINK_ELEMENT *list, const int do_tailcal
if (nobj->insn_id == BIN(jump)) { if (nobj->insn_id == BIN(jump)) {
OPERAND_AT(iobj, 0) = OPERAND_AT(nobj, 0); OPERAND_AT(iobj, 0) = OPERAND_AT(nobj, 0);
} }
if (nobj->insn_id == BIN(dup)) {
/*
* dup
* if L1
* ...
* L1:
* dup
* if L2
* =>
* dup
* if L2
* ...
* L1:
* dup
* if L2
*/
INSN *pobj = (INSN *)iobj->link.prev;
nobj = (INSN *)nobj->link.next;
/* basic blocks, with no labels in the middle */
if ((pobj && pobj->insn_id == BIN(dup)) &&
(nobj && nobj->insn_id == iobj->insn_id)) {
OPERAND_AT(iobj, 0) = OPERAND_AT(nobj, 0);
}
}
} }
if (do_tailcallopt && iobj->insn_id == BIN(leave)) { if (do_tailcallopt && iobj->insn_id == BIN(leave)) {
@ -4319,6 +4345,7 @@ iseq_compile_each(rb_iseq_t *iseq, LINK_ANCHOR *ret, NODE * node, int poped)
VALUE asgnflag; VALUE asgnflag;
LABEL *lfin = NEW_LABEL(line); LABEL *lfin = NEW_LABEL(line);
LABEL *lcfin = NEW_LABEL(line); LABEL *lcfin = NEW_LABEL(line);
LABEL *lskip = 0;
/* /*
class C; attr_accessor :c; end class C; attr_accessor :c; end
r = C.new r = C.new
@ -4362,6 +4389,11 @@ iseq_compile_each(rb_iseq_t *iseq, LINK_ANCHOR *ret, NODE * node, int poped)
*/ */
asgnflag = COMPILE_RECV(ret, "NODE_OP_ASGN2#recv", node); asgnflag = COMPILE_RECV(ret, "NODE_OP_ASGN2#recv", node);
if (node->nd_next->nd_aid) {
lskip = NEW_LABEL(line);
ADD_INSN(ret, line, dup);
ADD_INSNL(ret, line, branchnil, lskip);
}
ADD_INSN(ret, line, dup); ADD_INSN(ret, line, dup);
ADD_SEND(ret, line, vid, INT2FIX(0)); ADD_SEND(ret, line, vid, INT2FIX(0));
@ -4385,6 +4417,9 @@ iseq_compile_each(rb_iseq_t *iseq, LINK_ANCHOR *ret, NODE * node, int poped)
ADD_LABEL(ret, lfin); ADD_LABEL(ret, lfin);
ADD_INSN(ret, line, pop); ADD_INSN(ret, line, pop);
if (lskip) {
ADD_LABEL(ret, lskip);
}
if (poped) { if (poped) {
/* we can apply more optimize */ /* we can apply more optimize */
ADD_INSN(ret, line, pop); ADD_INSN(ret, line, pop);
@ -4392,14 +4427,16 @@ iseq_compile_each(rb_iseq_t *iseq, LINK_ANCHOR *ret, NODE * node, int poped)
} }
else { else {
COMPILE(ret, "NODE_OP_ASGN2 val", node->nd_value); COMPILE(ret, "NODE_OP_ASGN2 val", node->nd_value);
ADD_SEND(ret, line, node->nd_next->nd_mid, ADD_SEND(ret, line, atype, INT2FIX(1));
INT2FIX(1));
if (!poped) { if (!poped) {
ADD_INSN(ret, line, swap); ADD_INSN(ret, line, swap);
ADD_INSN1(ret, line, topn, INT2FIX(1)); ADD_INSN1(ret, line, topn, INT2FIX(1));
} }
ADD_SEND_WITH_FLAG(ret, line, aid, INT2FIX(1), INT2FIX(asgnflag)); ADD_SEND_WITH_FLAG(ret, line, aid, INT2FIX(1), INT2FIX(asgnflag));
ADD_INSN(ret, line, pop); ADD_INSN(ret, line, pop);
if (lskip) {
ADD_LABEL(ret, lskip);
}
} }
break; break;
} }
@ -4548,6 +4585,7 @@ iseq_compile_each(rb_iseq_t *iseq, LINK_ANCHOR *ret, NODE * node, int poped)
} }
break; break;
} }
case NODE_QCALL:
case NODE_FCALL: case NODE_FCALL:
case NODE_VCALL:{ /* VCALL: variable or call */ case NODE_VCALL:{ /* VCALL: variable or call */
/* /*
@ -4557,6 +4595,7 @@ iseq_compile_each(rb_iseq_t *iseq, LINK_ANCHOR *ret, NODE * node, int poped)
*/ */
DECL_ANCHOR(recv); DECL_ANCHOR(recv);
DECL_ANCHOR(args); DECL_ANCHOR(args);
LABEL *lskip = 0;
ID mid = node->nd_mid; ID mid = node->nd_mid;
VALUE argc; VALUE argc;
unsigned int flag = 0; unsigned int flag = 0;
@ -4631,8 +4670,13 @@ iseq_compile_each(rb_iseq_t *iseq, LINK_ANCHOR *ret, NODE * node, int poped)
} }
#endif #endif
/* receiver */ /* receiver */
if (type == NODE_CALL) { if (type == NODE_CALL || type == NODE_QCALL) {
COMPILE(recv, "recv", node->nd_recv); COMPILE(recv, "recv", node->nd_recv);
if (type == NODE_QCALL) {
lskip = NEW_LABEL(line);
ADD_INSN(recv, line, dup);
ADD_INSNL(recv, line, branchnil, lskip);
}
} }
else if (type == NODE_FCALL || type == NODE_VCALL) { else if (type == NODE_FCALL || type == NODE_VCALL) {
ADD_CALL_RECEIVER(recv, line); ADD_CALL_RECEIVER(recv, line);
@ -4662,6 +4706,9 @@ iseq_compile_each(rb_iseq_t *iseq, LINK_ANCHOR *ret, NODE * node, int poped)
ADD_SEND_R(ret, line, mid, argc, parent_block, INT2FIX(flag), keywords); ADD_SEND_R(ret, line, mid, argc, parent_block, INT2FIX(flag), keywords);
if (lskip) {
ADD_LABEL(ret, lskip);
}
if (poped) { if (poped) {
ADD_INSN(ret, line, pop); ADD_INSN(ret, line, pop);
} }

View File

@ -27,6 +27,10 @@ This sends the +my_method+ message to +my_object+. Any object can be a
receiver but depending on the method's visibility sending a message may raise a receiver but depending on the method's visibility sending a message may raise a
NoMethodError. NoMethodError.
You may use <code>.?</code> to designate a receiver, then +my_method+ is not
invoked and the result is +nil+ when the receiver is +nil+. In that case, the
argument of +my_method+ are not evaluated.
You may also use <code>::</code> to designate a receiver, but this is rarely You may also use <code>::</code> to designate a receiver, but this is rarely
used due to the potential for confusion with <code>::</code> for namespaces. used due to the potential for confusion with <code>::</code> for namespaces.

View File

@ -1132,6 +1132,23 @@ branchunless
} }
} }
/**
@c jump
@e if val is nil, set PC to (PC + dst).
@j val nil PC (PC + dst)
*/
DEFINE_INSN
branchnil
(OFFSET dst)
(VALUE val)
()
{
if (NIL_P(val)) {
RUBY_VM_CHECK_INTS(th);
JUMP(dst);
}
}
/**********************************************************/ /**********************************************************/
/* for optimize */ /* for optimize */

5
node.c
View File

@ -357,7 +357,10 @@ dump_node(VALUE buf, VALUE indent, int comment, NODE *node)
ANN(" where [attr]: [nd_next->nd_vid]"); ANN(" where [attr]: [nd_next->nd_vid]");
ANN("example: struct.field += foo"); ANN("example: struct.field += foo");
F_NODE(nd_recv, "receiver"); F_NODE(nd_recv, "receiver");
F_ID(nd_next->nd_vid, "attr"); F_CUSTOM1(nd_next->nd_vid, "attr") {
if (node->nd_next->nd_aid) A("? ");
A_ID(node->nd_next->nd_vid);
}
F_CUSTOM1(nd_next->nd_mid, "operator") { F_CUSTOM1(nd_next->nd_mid, "operator") {
switch (node->nd_next->nd_mid) { switch (node->nd_next->nd_mid) {
case 0: A("0 (||)"); break; case 0: A("0 (||)"); break;

6
node.h
View File

@ -96,6 +96,8 @@ enum node_type {
#define NODE_FCALL NODE_FCALL #define NODE_FCALL NODE_FCALL
NODE_VCALL, NODE_VCALL,
#define NODE_VCALL NODE_VCALL #define NODE_VCALL NODE_VCALL
NODE_QCALL,
#define NODE_QCALL NODE_QCALL
NODE_SUPER, NODE_SUPER,
#define NODE_SUPER NODE_SUPER #define NODE_SUPER NODE_SUPER
NODE_ZSUPER, NODE_ZSUPER,
@ -394,8 +396,8 @@ typedef struct RNode {
#define NEW_CVASGN(v,val) NEW_NODE(NODE_CVASGN,v,val,0) #define NEW_CVASGN(v,val) NEW_NODE(NODE_CVASGN,v,val,0)
#define NEW_CVDECL(v,val) NEW_NODE(NODE_CVDECL,v,val,0) #define NEW_CVDECL(v,val) NEW_NODE(NODE_CVDECL,v,val,0)
#define NEW_OP_ASGN1(p,id,a) NEW_NODE(NODE_OP_ASGN1,p,id,a) #define NEW_OP_ASGN1(p,id,a) NEW_NODE(NODE_OP_ASGN1,p,id,a)
#define NEW_OP_ASGN2(r,i,o,val) NEW_NODE(NODE_OP_ASGN2,r,val,NEW_OP_ASGN22(i,o)) #define NEW_OP_ASGN2(r,t,i,o,val) NEW_NODE(NODE_OP_ASGN2,r,val,NEW_OP_ASGN22(i,o,t))
#define NEW_OP_ASGN22(i,o) NEW_NODE(NODE_OP_ASGN2,i,o,0) #define NEW_OP_ASGN22(i,o,t) NEW_NODE(NODE_OP_ASGN2,i,o,t)
#define NEW_OP_ASGN_OR(i,val) NEW_NODE(NODE_OP_ASGN_OR,i,val,0) #define NEW_OP_ASGN_OR(i,val) NEW_NODE(NODE_OP_ASGN_OR,i,val,0)
#define NEW_OP_ASGN_AND(i,val) NEW_NODE(NODE_OP_ASGN_AND,i,val,0) #define NEW_OP_ASGN_AND(i,val) NEW_NODE(NODE_OP_ASGN_AND,i,val,0)
#define NEW_OP_CDECL(v,op,val) NEW_NODE(NODE_OP_CDECL,v,val,op) #define NEW_OP_CDECL(v,op,val) NEW_NODE(NODE_OP_CDECL,v,val,op)

81
parse.y
View File

@ -371,6 +371,9 @@ static int parser_yyerror(struct parser_params*, const char*);
#define ruby_coverage (parser->coverage) #define ruby_coverage (parser->coverage)
#endif #endif
#define NODE_CALL_Q(q) (((q) == tDOTQ) ? NODE_QCALL : NODE_CALL)
#define NEW_QCALL(q,r,m,a) NEW_NODE(NODE_CALL_Q(q),r,m,a)
static int yylex(YYSTYPE*, struct parser_params*); static int yylex(YYSTYPE*, struct parser_params*);
#ifndef RIPPER #ifndef RIPPER
@ -457,8 +460,8 @@ static NODE *node_assign_gen(struct parser_params*,NODE*,NODE*);
#define node_assign(node1, node2) node_assign_gen(parser, (node1), (node2)) #define node_assign(node1, node2) node_assign_gen(parser, (node1), (node2))
static NODE *new_op_assign_gen(struct parser_params *parser, NODE *lhs, ID op, NODE *rhs); static NODE *new_op_assign_gen(struct parser_params *parser, NODE *lhs, ID op, NODE *rhs);
static NODE *new_attr_op_assign_gen(struct parser_params *parser, NODE *lhs, ID attr, ID op, NODE *rhs); static NODE *new_attr_op_assign_gen(struct parser_params *parser, NODE *lhs, ID atype, ID attr, ID op, NODE *rhs);
#define new_attr_op_assign(lhs, type, attr, op, rhs) new_attr_op_assign_gen(parser, (lhs), (attr), (op), (rhs)) #define new_attr_op_assign(lhs, type, attr, op, rhs) new_attr_op_assign_gen(parser, (lhs), (type), (attr), (op), (rhs))
static NODE *new_const_op_assign_gen(struct parser_params *parser, NODE *lhs, ID op, NODE *rhs); static NODE *new_const_op_assign_gen(struct parser_params *parser, NODE *lhs, ID op, NODE *rhs);
#define new_const_op_assign(lhs, op, rhs) new_const_op_assign_gen(parser, (lhs), (op), (rhs)) #define new_const_op_assign(lhs, op, rhs) new_const_op_assign_gen(parser, (lhs), (op), (rhs))
@ -844,7 +847,7 @@ static void token_info_pop(struct parser_params*, const char *token, size_t len)
%type <node> mlhs mlhs_head mlhs_basic mlhs_item mlhs_node mlhs_post mlhs_inner %type <node> mlhs mlhs_head mlhs_basic mlhs_item mlhs_node mlhs_post mlhs_inner
%type <id> fsym keyword_variable user_variable sym symbol operation operation2 operation3 %type <id> fsym keyword_variable user_variable sym symbol operation operation2 operation3
%type <id> cname fname op f_rest_arg f_block_arg opt_f_block_arg f_norm_arg f_bad_arg %type <id> cname fname op f_rest_arg f_block_arg opt_f_block_arg f_norm_arg f_bad_arg
%type <id> f_kwrest f_label f_arg_asgn %type <id> f_kwrest f_label f_arg_asgn call_op
/*%%%*/ /*%%%*/
/*% /*%
%type <val> program reswords then do dot_or_colon %type <val> program reswords then do dot_or_colon
@ -869,6 +872,7 @@ static void token_info_pop(struct parser_params*, const char *token, size_t len)
%token tASET RUBY_TOKEN(ASET) "[]=" %token tASET RUBY_TOKEN(ASET) "[]="
%token tLSHFT RUBY_TOKEN(LSHFT) "<<" %token tLSHFT RUBY_TOKEN(LSHFT) "<<"
%token tRSHFT RUBY_TOKEN(RSHFT) ">>" %token tRSHFT RUBY_TOKEN(RSHFT) ">>"
%token tDOTQ RUBY_TOKEN(DOTQ) ".?"
%token tCOLON2 "::" %token tCOLON2 "::"
%token tCOLON3 ":: at EXPR_BEG" %token tCOLON3 ":: at EXPR_BEG"
%token <id> tOP_ASGN /* +=, -= etc. */ %token <id> tOP_ASGN /* +=, -= etc. */
@ -1260,15 +1264,15 @@ stmt : keyword_alias fitem {lex_state = EXPR_FNAME;} fitem
$$ = dispatch3(opassign, $$, $5, $6); $$ = dispatch3(opassign, $$, $5, $6);
%*/ %*/
} }
| primary_value '.' tIDENTIFIER tOP_ASGN command_call | primary_value call_op tIDENTIFIER tOP_ASGN command_call
{ {
value_expr($5); value_expr($5);
$$ = new_attr_op_assign($1, '.', $3, $4, $5); $$ = new_attr_op_assign($1, $2, $3, $4, $5);
} }
| primary_value '.' tCONSTANT tOP_ASGN command_call | primary_value call_op tCONSTANT tOP_ASGN command_call
{ {
value_expr($5); value_expr($5);
$$ = new_attr_op_assign($1, '.', $3, $4, $5); $$ = new_attr_op_assign($1, $2, $3, $4, $5);
} }
| primary_value tCOLON2 tCONSTANT tOP_ASGN command_call | primary_value tCOLON2 tCONSTANT tOP_ASGN command_call
{ {
@ -1456,24 +1460,24 @@ command : fcall command_args %prec tLOWEST
$$ = method_add_block($$, $3); $$ = method_add_block($$, $3);
%*/ %*/
} }
| primary_value '.' operation2 command_args %prec tLOWEST | primary_value call_op operation2 command_args %prec tLOWEST
{ {
/*%%%*/ /*%%%*/
$$ = NEW_CALL($1, $3, $4); $$ = NEW_QCALL($2, $1, $3, $4);
fixpos($$, $1); fixpos($$, $1);
/*% /*%
$$ = dispatch4(command_call, $1, ripper_id2sym('.'), $3, $4); $$ = dispatch4(command_call, $1, ripper_id2sym($2), $3, $4);
%*/ %*/
} }
| primary_value '.' operation2 command_args cmd_brace_block | primary_value call_op operation2 command_args cmd_brace_block
{ {
/*%%%*/ /*%%%*/
block_dup_check($4,$5); block_dup_check($4,$5);
$5->nd_iter = NEW_CALL($1, $3, $4); $5->nd_iter = NEW_QCALL($2, $1, $3, $4);
$$ = $5; $$ = $5;
fixpos($$, $1); fixpos($$, $1);
/*% /*%
$$ = dispatch4(command_call, $1, ripper_id2sym('.'), $3, $4); $$ = dispatch4(command_call, $1, ripper_id2sym($2), $3, $4);
$$ = method_add_block($$, $5); $$ = method_add_block($$, $5);
%*/ %*/
} }
@ -1713,12 +1717,12 @@ mlhs_node : user_variable
$$ = dispatch2(aref_field, $1, escape_Qundef($3)); $$ = dispatch2(aref_field, $1, escape_Qundef($3));
%*/ %*/
} }
| primary_value '.' tIDENTIFIER | primary_value call_op tIDENTIFIER
{ {
/*%%%*/ /*%%%*/
$$ = attrset($1, $3); $$ = attrset($1, $3);
/*% /*%
$$ = dispatch3(field, $1, ripper_id2sym('.'), $3); $$ = dispatch3(field, $1, ripper_id2sym($2), $3);
%*/ %*/
} }
| primary_value tCOLON2 tIDENTIFIER | primary_value tCOLON2 tIDENTIFIER
@ -1729,12 +1733,12 @@ mlhs_node : user_variable
$$ = dispatch2(const_path_field, $1, $3); $$ = dispatch2(const_path_field, $1, $3);
%*/ %*/
} }
| primary_value '.' tCONSTANT | primary_value call_op tCONSTANT
{ {
/*%%%*/ /*%%%*/
$$ = attrset($1, $3); $$ = attrset($1, $3);
/*% /*%
$$ = dispatch3(field, $1, ripper_id2sym('.'), $3); $$ = dispatch3(field, $1, ripper_id2sym($2), $3);
%*/ %*/
} }
| primary_value tCOLON2 tCONSTANT | primary_value tCOLON2 tCONSTANT
@ -1804,12 +1808,12 @@ lhs : user_variable
$$ = dispatch2(aref_field, $1, escape_Qundef($3)); $$ = dispatch2(aref_field, $1, escape_Qundef($3));
%*/ %*/
} }
| primary_value '.' tIDENTIFIER | primary_value call_op tIDENTIFIER
{ {
/*%%%*/ /*%%%*/
$$ = attrset($1, $3); $$ = attrset($1, $3);
/*% /*%
$$ = dispatch3(field, $1, ripper_id2sym('.'), $3); $$ = dispatch3(field, $1, ripper_id2sym($2), $3);
%*/ %*/
} }
| primary_value tCOLON2 tIDENTIFIER | primary_value tCOLON2 tIDENTIFIER
@ -1820,12 +1824,12 @@ lhs : user_variable
$$ = dispatch3(field, $1, ID2SYM(idCOLON2), $3); $$ = dispatch3(field, $1, ID2SYM(idCOLON2), $3);
%*/ %*/
} }
| primary_value '.' tCONSTANT | primary_value call_op tCONSTANT
{ {
/*%%%*/ /*%%%*/
$$ = attrset($1, $3); $$ = attrset($1, $3);
/*% /*%
$$ = dispatch3(field, $1, ripper_id2sym('.'), $3); $$ = dispatch3(field, $1, ripper_id2sym($2), $3);
%*/ %*/
} }
| primary_value tCOLON2 tCONSTANT | primary_value tCOLON2 tCONSTANT
@ -2064,15 +2068,15 @@ arg : lhs '=' arg
$$ = dispatch3(opassign, $1, $5, $6); $$ = dispatch3(opassign, $1, $5, $6);
%*/ %*/
} }
| primary_value '.' tIDENTIFIER tOP_ASGN arg | primary_value call_op tIDENTIFIER tOP_ASGN arg
{ {
value_expr($5); value_expr($5);
$$ = new_attr_op_assign($1, '.', $3, $4, $5); $$ = new_attr_op_assign($1, $2, $3, $4, $5);
} }
| primary_value '.' tCONSTANT tOP_ASGN arg | primary_value call_op tCONSTANT tOP_ASGN arg
{ {
value_expr($5); value_expr($5);
$$ = new_attr_op_assign($1, '.', $3, $4, $5); $$ = new_attr_op_assign($1, $2, $3, $4, $5);
} }
| primary_value tCOLON2 tIDENTIFIER tOP_ASGN arg | primary_value tCOLON2 tIDENTIFIER tOP_ASGN arg
{ {
@ -3648,7 +3652,7 @@ method_call : fcall paren_args
$$ = method_arg(dispatch1(fcall, $1), $2); $$ = method_arg(dispatch1(fcall, $1), $2);
%*/ %*/
} }
| primary_value '.' operation2 | primary_value call_op operation2
{ {
/*%%%*/ /*%%%*/
$<num>$ = ruby_sourceline; $<num>$ = ruby_sourceline;
@ -3657,10 +3661,10 @@ method_call : fcall paren_args
opt_paren_args opt_paren_args
{ {
/*%%%*/ /*%%%*/
$$ = NEW_CALL($1, $3, $5); $$ = NEW_QCALL($2, $1, $3, $5);
nd_set_line($$, $<num>4); nd_set_line($$, $<num>4);
/*% /*%
$$ = dispatch3(call, $1, ripper_id2sym('.'), $3); $$ = dispatch3(call, $1, ripper_id2sym($2), $3);
$$ = method_optarg($$, $5); $$ = method_optarg($$, $5);
%*/ %*/
} }
@ -3688,7 +3692,7 @@ method_call : fcall paren_args
$$ = dispatch3(call, $1, ID2SYM(idCOLON2), $3); $$ = dispatch3(call, $1, ID2SYM(idCOLON2), $3);
%*/ %*/
} }
| primary_value '.' | primary_value call_op
{ {
/*%%%*/ /*%%%*/
$<num>$ = ruby_sourceline; $<num>$ = ruby_sourceline;
@ -3697,10 +3701,10 @@ method_call : fcall paren_args
paren_args paren_args
{ {
/*%%%*/ /*%%%*/
$$ = NEW_CALL($1, idCall, $4); $$ = NEW_QCALL($2, $1, idCall, $4);
nd_set_line($$, $<num>3); nd_set_line($$, $<num>3);
/*% /*%
$$ = dispatch3(call, $1, ripper_id2sym('.'), $$ = dispatch3(call, $1, ripper_id2sym($2),
ID2SYM(idCall)); ID2SYM(idCall));
$$ = method_optarg($$, $4); $$ = method_optarg($$, $4);
%*/ %*/
@ -5103,7 +5107,7 @@ operation3 : tIDENTIFIER
| op | op
; ;
dot_or_colon : '.' dot_or_colon : call_op
/*%c%*/ /*%c%*/
/*%c /*%c
{ $$ = $<val>1; } { $$ = $<val>1; }
@ -5115,6 +5119,10 @@ dot_or_colon : '.'
%*/ %*/
; ;
call_op : '.' {$$ = '.';}
| tDOTQ {$$ = tDOTQ;}
;
opt_terms : /* none */ opt_terms : /* none */
| terms | terms
; ;
@ -8356,6 +8364,10 @@ parser_yylex(struct parser_params *parser)
pushback(c); pushback(c);
return tDOT2; return tDOT2;
} }
if (c == '?') {
lex_state = EXPR_DOT;
return tDOTQ;
}
pushback(c); pushback(c);
if (c != -1 && ISDIGIT(c)) { if (c != -1 && ISDIGIT(c)) {
yyerror("no .<digit> floating literal anymore; put 0 before dot"); yyerror("no .<digit> floating literal anymore; put 0 before dot");
@ -10036,7 +10048,8 @@ new_op_assign_gen(struct parser_params *parser, NODE *lhs, ID op, NODE *rhs)
} }
static NODE * static NODE *
new_attr_op_assign_gen(struct parser_params *parser, NODE *lhs, ID attr, ID op, NODE *rhs) new_attr_op_assign_gen(struct parser_params *parser, NODE *lhs,
ID atype, ID attr, ID op, NODE *rhs)
{ {
NODE *asgn; NODE *asgn;
@ -10046,7 +10059,7 @@ new_attr_op_assign_gen(struct parser_params *parser, NODE *lhs, ID attr, ID op,
else if (op == tANDOP) { else if (op == tANDOP) {
op = 1; op = 1;
} }
asgn = NEW_OP_ASGN2(lhs, attr, op, rhs); asgn = NEW_OP_ASGN2(lhs, (atype == tDOTQ), attr, op, rhs);
fixpos(asgn, lhs); fixpos(asgn, lhs);
return asgn; return asgn;
} }

View File

@ -18,7 +18,7 @@ op_id_offset = 128
token_op_ids = %w[ token_op_ids = %w[
tDOT2 tDOT3 tUPLUS tUMINUS tPOW tDSTAR tCMP tLSHFT tRSHFT tDOT2 tDOT3 tUPLUS tUMINUS tPOW tDSTAR tCMP tLSHFT tRSHFT
tLEQ tGEQ tEQ tEQQ tNEQ tMATCH tNMATCH tAREF tASET tLEQ tGEQ tEQ tEQQ tNEQ tMATCH tNMATCH tAREF tASET
tCOLON2 tCOLON3 tANDOP tOROP tCOLON2 tCOLON3 tANDOP tOROP tDOTQ
] ]
defs = File.join(File.dirname(File.dirname(erb.filename)), "defs/id.def") defs = File.join(File.dirname(File.dirname(erb.filename)), "defs/id.def")

View File

@ -31,4 +31,16 @@ class TestCall < Test::Unit::TestCase
assert_nothing_raised(ArgumentError) {o.foo} assert_nothing_raised(ArgumentError) {o.foo}
assert_raise_with_message(ArgumentError, e.message, bug9622) {o.foo(100)} assert_raise_with_message(ArgumentError, e.message, bug9622) {o.foo(100)}
end end
def test_safe_call
s = Struct.new(:x, :y)
o = s.new("x")
assert_equal("X", o.x.?upcase)
assert_nil(o.y.?upcase)
assert_equal("x", o.x)
o.?x = 6
assert_equal(6, o.x)
o.?x *= 7
assert_equal(42, o.x)
end
end end