зеркало из https://github.com/github/ruby.git
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:
Родитель
5a599dde0c
Коммит
a356fe1c35
14
ChangeLog
14
ChangeLog
|
@ -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>
|
||||
|
||||
* dir.c (ruby_brace_expand): glob brace expansion edge case fix.
|
||||
|
|
11
NEWS
11
NEWS
|
@ -18,6 +18,17 @@ with all sufficient information, see the ChangeLog file.
|
|||
* besides, --enable/--disable=frozen-string-literal options also have
|
||||
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)
|
||||
|
||||
* ARGF
|
||||
|
|
53
compile.c
53
compile.c
|
@ -1942,6 +1942,7 @@ iseq_peephole_optimize(rb_iseq_t *iseq, LINK_ELEMENT *list, const int do_tailcal
|
|||
}
|
||||
|
||||
if (iobj->insn_id == BIN(branchif) ||
|
||||
iobj->insn_id == BIN(branchnil) ||
|
||||
iobj->insn_id == BIN(branchunless)) {
|
||||
/*
|
||||
* 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)) {
|
||||
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)) {
|
||||
|
@ -4319,6 +4345,7 @@ iseq_compile_each(rb_iseq_t *iseq, LINK_ANCHOR *ret, NODE * node, int poped)
|
|||
VALUE asgnflag;
|
||||
LABEL *lfin = NEW_LABEL(line);
|
||||
LABEL *lcfin = NEW_LABEL(line);
|
||||
LABEL *lskip = 0;
|
||||
/*
|
||||
class C; attr_accessor :c; end
|
||||
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);
|
||||
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_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_INSN(ret, line, pop);
|
||||
if (lskip) {
|
||||
ADD_LABEL(ret, lskip);
|
||||
}
|
||||
if (poped) {
|
||||
/* we can apply more optimize */
|
||||
ADD_INSN(ret, line, pop);
|
||||
|
@ -4392,14 +4427,16 @@ iseq_compile_each(rb_iseq_t *iseq, LINK_ANCHOR *ret, NODE * node, int poped)
|
|||
}
|
||||
else {
|
||||
COMPILE(ret, "NODE_OP_ASGN2 val", node->nd_value);
|
||||
ADD_SEND(ret, line, node->nd_next->nd_mid,
|
||||
INT2FIX(1));
|
||||
ADD_SEND(ret, line, atype, INT2FIX(1));
|
||||
if (!poped) {
|
||||
ADD_INSN(ret, line, swap);
|
||||
ADD_INSN1(ret, line, topn, INT2FIX(1));
|
||||
}
|
||||
ADD_SEND_WITH_FLAG(ret, line, aid, INT2FIX(1), INT2FIX(asgnflag));
|
||||
ADD_INSN(ret, line, pop);
|
||||
if (lskip) {
|
||||
ADD_LABEL(ret, lskip);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
@ -4548,6 +4585,7 @@ iseq_compile_each(rb_iseq_t *iseq, LINK_ANCHOR *ret, NODE * node, int poped)
|
|||
}
|
||||
break;
|
||||
}
|
||||
case NODE_QCALL:
|
||||
case NODE_FCALL:
|
||||
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(args);
|
||||
LABEL *lskip = 0;
|
||||
ID mid = node->nd_mid;
|
||||
VALUE argc;
|
||||
unsigned int flag = 0;
|
||||
|
@ -4631,8 +4670,13 @@ iseq_compile_each(rb_iseq_t *iseq, LINK_ANCHOR *ret, NODE * node, int poped)
|
|||
}
|
||||
#endif
|
||||
/* receiver */
|
||||
if (type == NODE_CALL) {
|
||||
if (type == NODE_CALL || type == NODE_QCALL) {
|
||||
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) {
|
||||
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);
|
||||
|
||||
if (lskip) {
|
||||
ADD_LABEL(ret, lskip);
|
||||
}
|
||||
if (poped) {
|
||||
ADD_INSN(ret, line, pop);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
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
|
||||
used due to the potential for confusion with <code>::</code> for namespaces.
|
||||
|
||||
|
|
17
insns.def
17
insns.def
|
@ -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 */
|
||||
|
|
5
node.c
5
node.c
|
@ -357,7 +357,10 @@ dump_node(VALUE buf, VALUE indent, int comment, NODE *node)
|
|||
ANN(" where [attr]: [nd_next->nd_vid]");
|
||||
ANN("example: struct.field += foo");
|
||||
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") {
|
||||
switch (node->nd_next->nd_mid) {
|
||||
case 0: A("0 (||)"); break;
|
||||
|
|
6
node.h
6
node.h
|
@ -96,6 +96,8 @@ enum node_type {
|
|||
#define NODE_FCALL NODE_FCALL
|
||||
NODE_VCALL,
|
||||
#define NODE_VCALL NODE_VCALL
|
||||
NODE_QCALL,
|
||||
#define NODE_QCALL NODE_QCALL
|
||||
NODE_SUPER,
|
||||
#define NODE_SUPER NODE_SUPER
|
||||
NODE_ZSUPER,
|
||||
|
@ -394,8 +396,8 @@ typedef struct RNode {
|
|||
#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_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_ASGN22(i,o) NEW_NODE(NODE_OP_ASGN2,i,o,0)
|
||||
#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,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_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)
|
||||
|
|
81
parse.y
81
parse.y
|
@ -371,6 +371,9 @@ static int parser_yyerror(struct parser_params*, const char*);
|
|||
#define ruby_coverage (parser->coverage)
|
||||
#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*);
|
||||
|
||||
#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))
|
||||
|
||||
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);
|
||||
#define new_attr_op_assign(lhs, type, attr, op, rhs) new_attr_op_assign_gen(parser, (lhs), (attr), (op), (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), (type), (attr), (op), (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))
|
||||
|
||||
|
@ -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 <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> 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
|
||||
|
@ -869,6 +872,7 @@ static void token_info_pop(struct parser_params*, const char *token, size_t len)
|
|||
%token tASET RUBY_TOKEN(ASET) "[]="
|
||||
%token tLSHFT RUBY_TOKEN(LSHFT) "<<"
|
||||
%token tRSHFT RUBY_TOKEN(RSHFT) ">>"
|
||||
%token tDOTQ RUBY_TOKEN(DOTQ) ".?"
|
||||
%token tCOLON2 "::"
|
||||
%token tCOLON3 ":: at EXPR_BEG"
|
||||
%token <id> tOP_ASGN /* +=, -= etc. */
|
||||
|
@ -1260,15 +1264,15 @@ stmt : keyword_alias fitem {lex_state = EXPR_FNAME;} fitem
|
|||
$$ = dispatch3(opassign, $$, $5, $6);
|
||||
%*/
|
||||
}
|
||||
| primary_value '.' tIDENTIFIER tOP_ASGN command_call
|
||||
| primary_value call_op tIDENTIFIER tOP_ASGN command_call
|
||||
{
|
||||
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);
|
||||
$$ = 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
|
||||
{
|
||||
|
@ -1456,24 +1460,24 @@ command : fcall command_args %prec tLOWEST
|
|||
$$ = 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);
|
||||
/*%
|
||||
$$ = 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);
|
||||
$5->nd_iter = NEW_CALL($1, $3, $4);
|
||||
$5->nd_iter = NEW_QCALL($2, $1, $3, $4);
|
||||
$$ = $5;
|
||||
fixpos($$, $1);
|
||||
/*%
|
||||
$$ = dispatch4(command_call, $1, ripper_id2sym('.'), $3, $4);
|
||||
$$ = dispatch4(command_call, $1, ripper_id2sym($2), $3, $4);
|
||||
$$ = method_add_block($$, $5);
|
||||
%*/
|
||||
}
|
||||
|
@ -1713,12 +1717,12 @@ mlhs_node : user_variable
|
|||
$$ = dispatch2(aref_field, $1, escape_Qundef($3));
|
||||
%*/
|
||||
}
|
||||
| primary_value '.' tIDENTIFIER
|
||||
| primary_value call_op tIDENTIFIER
|
||||
{
|
||||
/*%%%*/
|
||||
$$ = attrset($1, $3);
|
||||
/*%
|
||||
$$ = dispatch3(field, $1, ripper_id2sym('.'), $3);
|
||||
$$ = dispatch3(field, $1, ripper_id2sym($2), $3);
|
||||
%*/
|
||||
}
|
||||
| primary_value tCOLON2 tIDENTIFIER
|
||||
|
@ -1729,12 +1733,12 @@ mlhs_node : user_variable
|
|||
$$ = dispatch2(const_path_field, $1, $3);
|
||||
%*/
|
||||
}
|
||||
| primary_value '.' tCONSTANT
|
||||
| primary_value call_op tCONSTANT
|
||||
{
|
||||
/*%%%*/
|
||||
$$ = attrset($1, $3);
|
||||
/*%
|
||||
$$ = dispatch3(field, $1, ripper_id2sym('.'), $3);
|
||||
$$ = dispatch3(field, $1, ripper_id2sym($2), $3);
|
||||
%*/
|
||||
}
|
||||
| primary_value tCOLON2 tCONSTANT
|
||||
|
@ -1804,12 +1808,12 @@ lhs : user_variable
|
|||
$$ = dispatch2(aref_field, $1, escape_Qundef($3));
|
||||
%*/
|
||||
}
|
||||
| primary_value '.' tIDENTIFIER
|
||||
| primary_value call_op tIDENTIFIER
|
||||
{
|
||||
/*%%%*/
|
||||
$$ = attrset($1, $3);
|
||||
/*%
|
||||
$$ = dispatch3(field, $1, ripper_id2sym('.'), $3);
|
||||
$$ = dispatch3(field, $1, ripper_id2sym($2), $3);
|
||||
%*/
|
||||
}
|
||||
| primary_value tCOLON2 tIDENTIFIER
|
||||
|
@ -1820,12 +1824,12 @@ lhs : user_variable
|
|||
$$ = dispatch3(field, $1, ID2SYM(idCOLON2), $3);
|
||||
%*/
|
||||
}
|
||||
| primary_value '.' tCONSTANT
|
||||
| primary_value call_op tCONSTANT
|
||||
{
|
||||
/*%%%*/
|
||||
$$ = attrset($1, $3);
|
||||
/*%
|
||||
$$ = dispatch3(field, $1, ripper_id2sym('.'), $3);
|
||||
$$ = dispatch3(field, $1, ripper_id2sym($2), $3);
|
||||
%*/
|
||||
}
|
||||
| primary_value tCOLON2 tCONSTANT
|
||||
|
@ -2064,15 +2068,15 @@ arg : lhs '=' arg
|
|||
$$ = dispatch3(opassign, $1, $5, $6);
|
||||
%*/
|
||||
}
|
||||
| primary_value '.' tIDENTIFIER tOP_ASGN arg
|
||||
| primary_value call_op tIDENTIFIER tOP_ASGN arg
|
||||
{
|
||||
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);
|
||||
$$ = new_attr_op_assign($1, '.', $3, $4, $5);
|
||||
$$ = new_attr_op_assign($1, $2, $3, $4, $5);
|
||||
}
|
||||
| primary_value tCOLON2 tIDENTIFIER tOP_ASGN arg
|
||||
{
|
||||
|
@ -3648,7 +3652,7 @@ method_call : fcall paren_args
|
|||
$$ = method_arg(dispatch1(fcall, $1), $2);
|
||||
%*/
|
||||
}
|
||||
| primary_value '.' operation2
|
||||
| primary_value call_op operation2
|
||||
{
|
||||
/*%%%*/
|
||||
$<num>$ = ruby_sourceline;
|
||||
|
@ -3657,10 +3661,10 @@ method_call : fcall paren_args
|
|||
opt_paren_args
|
||||
{
|
||||
/*%%%*/
|
||||
$$ = NEW_CALL($1, $3, $5);
|
||||
$$ = NEW_QCALL($2, $1, $3, $5);
|
||||
nd_set_line($$, $<num>4);
|
||||
/*%
|
||||
$$ = dispatch3(call, $1, ripper_id2sym('.'), $3);
|
||||
$$ = dispatch3(call, $1, ripper_id2sym($2), $3);
|
||||
$$ = method_optarg($$, $5);
|
||||
%*/
|
||||
}
|
||||
|
@ -3688,7 +3692,7 @@ method_call : fcall paren_args
|
|||
$$ = dispatch3(call, $1, ID2SYM(idCOLON2), $3);
|
||||
%*/
|
||||
}
|
||||
| primary_value '.'
|
||||
| primary_value call_op
|
||||
{
|
||||
/*%%%*/
|
||||
$<num>$ = ruby_sourceline;
|
||||
|
@ -3697,10 +3701,10 @@ method_call : fcall paren_args
|
|||
paren_args
|
||||
{
|
||||
/*%%%*/
|
||||
$$ = NEW_CALL($1, idCall, $4);
|
||||
$$ = NEW_QCALL($2, $1, idCall, $4);
|
||||
nd_set_line($$, $<num>3);
|
||||
/*%
|
||||
$$ = dispatch3(call, $1, ripper_id2sym('.'),
|
||||
$$ = dispatch3(call, $1, ripper_id2sym($2),
|
||||
ID2SYM(idCall));
|
||||
$$ = method_optarg($$, $4);
|
||||
%*/
|
||||
|
@ -5103,7 +5107,7 @@ operation3 : tIDENTIFIER
|
|||
| op
|
||||
;
|
||||
|
||||
dot_or_colon : '.'
|
||||
dot_or_colon : call_op
|
||||
/*%c%*/
|
||||
/*%c
|
||||
{ $$ = $<val>1; }
|
||||
|
@ -5115,6 +5119,10 @@ dot_or_colon : '.'
|
|||
%*/
|
||||
;
|
||||
|
||||
call_op : '.' {$$ = '.';}
|
||||
| tDOTQ {$$ = tDOTQ;}
|
||||
;
|
||||
|
||||
opt_terms : /* none */
|
||||
| terms
|
||||
;
|
||||
|
@ -8356,6 +8364,10 @@ parser_yylex(struct parser_params *parser)
|
|||
pushback(c);
|
||||
return tDOT2;
|
||||
}
|
||||
if (c == '?') {
|
||||
lex_state = EXPR_DOT;
|
||||
return tDOTQ;
|
||||
}
|
||||
pushback(c);
|
||||
if (c != -1 && ISDIGIT(c)) {
|
||||
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 *
|
||||
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;
|
||||
|
||||
|
@ -10046,7 +10059,7 @@ new_attr_op_assign_gen(struct parser_params *parser, NODE *lhs, ID attr, ID op,
|
|||
else if (op == tANDOP) {
|
||||
op = 1;
|
||||
}
|
||||
asgn = NEW_OP_ASGN2(lhs, attr, op, rhs);
|
||||
asgn = NEW_OP_ASGN2(lhs, (atype == tDOTQ), attr, op, rhs);
|
||||
fixpos(asgn, lhs);
|
||||
return asgn;
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ op_id_offset = 128
|
|||
token_op_ids = %w[
|
||||
tDOT2 tDOT3 tUPLUS tUMINUS tPOW tDSTAR tCMP tLSHFT tRSHFT
|
||||
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")
|
||||
|
|
|
@ -31,4 +31,16 @@ class TestCall < Test::Unit::TestCase
|
|||
assert_nothing_raised(ArgumentError) {o.foo}
|
||||
assert_raise_with_message(ArgumentError, e.message, bug9622) {o.foo(100)}
|
||||
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
|
||||
|
|
Загрузка…
Ссылка в новой задаче