mirror of
				https://github.com/ruby/ruby.git
				synced 2022-11-09 12:17:21 -05:00 
			
		
		
		
	* vm_core.h (struct rb_iseq_struct), compile.c (iseq_set_arguments, iseq_compile_each), vm_insnhelper.c (vm_callee_setup_arg_complex): implement keyword arguments. See [ruby-core:40290] The feature is promised to be included in 2.0, but the detail spec is still under discussion; this commit is a springboard for further discussion. Please try it and give us feedback. This commit includes fixes for some problems reported by Benoit Daloze <eregontp AT gmail.com> [ruby-core:40518] and Marc-Andre Lafortune <ruby-core-mailing-list AT marc-andre.ca> [ruby-core:41772].
* iseq.c (iseq_free, prepare_iseq_build): bookkeeping. * test/ruby/test_keyword.rb: add tests for keyword arguments. * test/ripper/dummyparser.rb (class DummyParser): temporal fix for ripper test. git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@34136 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
This commit is contained in:
		
							parent
							
								
									1ab3974b0e
								
							
						
					
					
						commit
						a0a2c144b8
					
				
					 7 changed files with 238 additions and 3 deletions
				
			
		
							
								
								
									
										20
									
								
								ChangeLog
									
										
									
									
									
								
							
							
						
						
									
										20
									
								
								ChangeLog
									
										
									
									
									
								
							| 
						 | 
				
			
			@ -1,3 +1,23 @@
 | 
			
		|||
Mon Dec 26 22:15:27 2011  Yusuke Endoh  <mame@tsg.ne.jp>
 | 
			
		||||
 | 
			
		||||
	* vm_core.h (struct rb_iseq_struct), compile.c (iseq_set_arguments,
 | 
			
		||||
	  iseq_compile_each), vm_insnhelper.c (vm_callee_setup_arg_complex):
 | 
			
		||||
	  implement keyword arguments.  See [ruby-core:40290]
 | 
			
		||||
	  The feature is promised to be included in 2.0, but the detail spec
 | 
			
		||||
	  is still under discussion; this commit is a springboard for further
 | 
			
		||||
	  discussion.  Please try it and give us feedback.
 | 
			
		||||
	  This commit includes fixes for some problems reported by Benoit
 | 
			
		||||
	  Daloze <eregontp AT gmail.com> [ruby-core:40518] and Marc-Andre
 | 
			
		||||
	  Lafortune <ruby-core-mailing-list AT marc-andre.ca>
 | 
			
		||||
	  [ruby-core:41772].
 | 
			
		||||
 | 
			
		||||
	* iseq.c (iseq_free, prepare_iseq_build): bookkeeping.
 | 
			
		||||
 | 
			
		||||
	* test/ruby/test_keyword.rb: add tests for keyword arguments.
 | 
			
		||||
 | 
			
		||||
	* test/ripper/dummyparser.rb (class DummyParser): temporal fix for
 | 
			
		||||
	  ripper test.
 | 
			
		||||
 | 
			
		||||
Mon Dec 26 22:00:17 2011  Yusuke Endoh  <mame@tsg.ne.jp>
 | 
			
		||||
 | 
			
		||||
	* node.h, node.c, parse.y: implement a parser part for keyword
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										70
									
								
								compile.c
									
										
									
									
									
								
							
							
						
						
									
										70
									
								
								compile.c
									
										
									
									
									
								
							| 
						 | 
				
			
			@ -1127,6 +1127,36 @@ iseq_set_arguments(rb_iseq_t *iseq, LINK_ANCHOR *optargs, NODE *node_args)
 | 
			
		|||
	    iseq->arg_opts = 0;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (args->kw_args) {
 | 
			
		||||
	    NODE *node = args->kw_args;
 | 
			
		||||
	    VALUE keywords = rb_ary_tmp_new(1);
 | 
			
		||||
	    int i = 0, j;
 | 
			
		||||
 | 
			
		||||
	    iseq->arg_keyword = get_dyna_var_idx_at_raw(iseq, args->kw_rest_arg->nd_vid);
 | 
			
		||||
	    COMPILE(optargs, "kwarg", args->kw_rest_arg);
 | 
			
		||||
	    while (node) {
 | 
			
		||||
		rb_ary_push(keywords, INT2FIX(node->nd_body->nd_vid));
 | 
			
		||||
		COMPILE_POPED(optargs, "kwarg", node); /* nd_type(node) == NODE_KW_ARG */
 | 
			
		||||
		node = node->nd_next;
 | 
			
		||||
		i += 1;
 | 
			
		||||
	    }
 | 
			
		||||
	    if ((args->kw_rest_arg->nd_vid & ID_SCOPE_MASK) == ID_JUNK) {
 | 
			
		||||
		iseq->arg_keywords = i;
 | 
			
		||||
		iseq->arg_keyword_table = ALLOC_N(ID, i);
 | 
			
		||||
		for (j = 0; j < i; j++) {
 | 
			
		||||
		    iseq->arg_keyword_table[j] = FIX2INT(RARRAY_PTR(keywords)[j]);
 | 
			
		||||
		}
 | 
			
		||||
	    }
 | 
			
		||||
	    else {
 | 
			
		||||
		iseq->arg_keywords = 0;
 | 
			
		||||
		iseq->arg_keyword_table = 0;
 | 
			
		||||
	    }
 | 
			
		||||
	    ADD_INSN(optargs, nd_line(args->kw_args), pop);
 | 
			
		||||
	}
 | 
			
		||||
	else {
 | 
			
		||||
	    iseq->arg_keyword = -1;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (args->pre_init) { /* m_init */
 | 
			
		||||
	    COMPILE_POPED(optargs, "init arguments (m)", args->pre_init);
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			@ -1151,11 +1181,15 @@ iseq_set_arguments(rb_iseq_t *iseq, LINK_ANCHOR *optargs, NODE *node_args)
 | 
			
		|||
	}
 | 
			
		||||
 | 
			
		||||
	if (iseq->arg_opts != 0 || iseq->arg_post_len != 0 ||
 | 
			
		||||
	    iseq->arg_rest != -1 || iseq->arg_block != -1) {
 | 
			
		||||
	    iseq->arg_rest != -1 || iseq->arg_block != -1 ||
 | 
			
		||||
	    iseq->arg_keyword != -1) {
 | 
			
		||||
	    iseq->arg_simple = 0;
 | 
			
		||||
 | 
			
		||||
	    /* set arg_size: size of arguments */
 | 
			
		||||
	    if (iseq->arg_block != -1) {
 | 
			
		||||
	    if (iseq->arg_keyword != -1) {
 | 
			
		||||
		iseq->arg_size = iseq->arg_keyword + 1;
 | 
			
		||||
	    }
 | 
			
		||||
	    else if (iseq->arg_block != -1) {
 | 
			
		||||
		iseq->arg_size = iseq->arg_block + 1;
 | 
			
		||||
	    }
 | 
			
		||||
	    else if (iseq->arg_post_len) {
 | 
			
		||||
| 
						 | 
				
			
			@ -4919,6 +4953,38 @@ iseq_compile_each(rb_iseq_t *iseq, LINK_ANCHOR *ret, NODE * node, int poped)
 | 
			
		|||
	}
 | 
			
		||||
	break;
 | 
			
		||||
      }
 | 
			
		||||
      case NODE_KW_ARG:{
 | 
			
		||||
	LABEL *default_label = NEW_LABEL(nd_line(node));
 | 
			
		||||
	LABEL *end_label = NEW_LABEL(nd_line(node));
 | 
			
		||||
	int idx, lv, ls;
 | 
			
		||||
	ID id = node->nd_body->nd_vid;
 | 
			
		||||
 | 
			
		||||
	ADD_INSN(ret, nd_line(node), dup);
 | 
			
		||||
	ADD_INSN1(ret, nd_line(node), putobject, ID2SYM(id));
 | 
			
		||||
	ADD_SEND(ret, nd_line(node), ID2SYM(rb_intern("key?")), INT2FIX(1));
 | 
			
		||||
	ADD_INSNL(ret, nd_line(node), branchunless, default_label);
 | 
			
		||||
	ADD_INSN(ret, nd_line(node), dup);
 | 
			
		||||
	ADD_INSN1(ret, nd_line(node), putobject, ID2SYM(id));
 | 
			
		||||
	ADD_SEND(ret, nd_line(node), ID2SYM(rb_intern("delete")), INT2FIX(1));
 | 
			
		||||
	switch (nd_type(node->nd_body)) {
 | 
			
		||||
	    case NODE_LASGN:
 | 
			
		||||
		idx = iseq->local_iseq->local_size - get_local_var_idx(iseq, id);
 | 
			
		||||
		ADD_INSN1(ret, nd_line(node), setlocal, INT2FIX(idx));
 | 
			
		||||
		break;
 | 
			
		||||
	    case NODE_DASGN:
 | 
			
		||||
	    case NODE_DASGN_CURR:
 | 
			
		||||
		idx = get_dyna_var_idx(iseq, id, &lv, &ls);
 | 
			
		||||
		ADD_INSN2(ret, nd_line(node), setdynamic, INT2FIX(ls - idx), INT2FIX(lv));
 | 
			
		||||
		break;
 | 
			
		||||
	    default:
 | 
			
		||||
		rb_bug("iseq_compile_each (NODE_KW_ARG): unknown node: %s", ruby_node_name(nd_type(node->nd_body)));
 | 
			
		||||
	}
 | 
			
		||||
	ADD_INSNL(ret, nd_line(node), jump, end_label);
 | 
			
		||||
	ADD_LABEL(ret, default_label);
 | 
			
		||||
	COMPILE_POPED(ret, "keyword default argument", node->nd_body);
 | 
			
		||||
	ADD_LABEL(ret, end_label);
 | 
			
		||||
	break;
 | 
			
		||||
      }
 | 
			
		||||
      case NODE_DSYM:{
 | 
			
		||||
	compile_dstr(iseq, ret, node);
 | 
			
		||||
	if (!poped) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										2
									
								
								iseq.c
									
										
									
									
									
								
							
							
						
						
									
										2
									
								
								iseq.c
									
										
									
									
									
								
							| 
						 | 
				
			
			@ -83,6 +83,7 @@ iseq_free(void *ptr)
 | 
			
		|||
	    RUBY_FREE_UNLESS_NULL(iseq->ic_entries);
 | 
			
		||||
	    RUBY_FREE_UNLESS_NULL(iseq->catch_table);
 | 
			
		||||
	    RUBY_FREE_UNLESS_NULL(iseq->arg_opt_table);
 | 
			
		||||
	    RUBY_FREE_UNLESS_NULL(iseq->arg_keyword_table);
 | 
			
		||||
	    compile_data_free(iseq->compile_data);
 | 
			
		||||
	}
 | 
			
		||||
	ruby_xfree(ptr);
 | 
			
		||||
| 
						 | 
				
			
			@ -239,6 +240,7 @@ prepare_iseq_build(rb_iseq_t *iseq,
 | 
			
		|||
    iseq->type = type;
 | 
			
		||||
    iseq->arg_rest = -1;
 | 
			
		||||
    iseq->arg_block = -1;
 | 
			
		||||
    iseq->arg_keyword = -1;
 | 
			
		||||
    iseq->klass = 0;
 | 
			
		||||
 | 
			
		||||
    /*
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -151,7 +151,7 @@ class DummyParser < Ripper
 | 
			
		|||
    "&#{var}"
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def on_params(required, optional, rest, more, block)
 | 
			
		||||
  def on_params(required, optional, rest, more, keyword, keyword_rest, block)
 | 
			
		||||
    args = NodeList.new
 | 
			
		||||
 | 
			
		||||
    required.each do |req|
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										105
									
								
								test/ruby/test_keyword.rb
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										105
									
								
								test/ruby/test_keyword.rb
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,105 @@
 | 
			
		|||
require 'test/unit'
 | 
			
		||||
 | 
			
		||||
class TestKeywordArguments < Test::Unit::TestCase
 | 
			
		||||
  def f1(str: "foo", num: 424242)
 | 
			
		||||
    [str, num]
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def test_f1
 | 
			
		||||
    assert_equal(["foo", 424242], f1)
 | 
			
		||||
    assert_equal(["bar", 424242], f1(str: "bar"))
 | 
			
		||||
    assert_equal(["foo", 111111], f1(num: 111111))
 | 
			
		||||
    assert_equal(["bar", 111111], f1(str: "bar", num: 111111))
 | 
			
		||||
    assert_raise(ArgumentError) { f1(str: "bar", check: true) }
 | 
			
		||||
    assert_raise(ArgumentError) { f1("string") }
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  def f2(x, str: "foo", num: 424242)
 | 
			
		||||
    [x, str, num]
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def test_f2
 | 
			
		||||
    assert_equal([:xyz, "foo", 424242], f2(:xyz))
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  def f3(str: "foo", num: 424242, **h)
 | 
			
		||||
    [str, num, h]
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def test_f3
 | 
			
		||||
    assert_equal(["foo", 424242, {}], f3)
 | 
			
		||||
    assert_equal(["bar", 424242, {}], f3(str: "bar"))
 | 
			
		||||
    assert_equal(["foo", 111111, {}], f3(num: 111111))
 | 
			
		||||
    assert_equal(["bar", 111111, {}], f3(str: "bar", num: 111111))
 | 
			
		||||
    assert_equal(["bar", 424242, {:check=>true}], f3(str: "bar", check: true))
 | 
			
		||||
    assert_raise(ArgumentError) { f3("string") }
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  define_method(:f4) {|str: "foo", num: 424242| [str, num] }
 | 
			
		||||
 | 
			
		||||
  def test_f4
 | 
			
		||||
    assert_equal(["foo", 424242], f4)
 | 
			
		||||
    assert_equal(["bar", 424242], f4(str: "bar"))
 | 
			
		||||
    assert_equal(["foo", 111111], f4(num: 111111))
 | 
			
		||||
    assert_equal(["bar", 111111], f4(str: "bar", num: 111111))
 | 
			
		||||
    assert_raise(ArgumentError) { f4(str: "bar", check: true) }
 | 
			
		||||
    assert_raise(ArgumentError) { f4("string") }
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  define_method(:f5) {|str: "foo", num: 424242, **h| [str, num, h] }
 | 
			
		||||
 | 
			
		||||
  def test_f5
 | 
			
		||||
    assert_equal(["foo", 424242, {}], f5)
 | 
			
		||||
    assert_equal(["bar", 424242, {}], f5(str: "bar"))
 | 
			
		||||
    assert_equal(["foo", 111111, {}], f5(num: 111111))
 | 
			
		||||
    assert_equal(["bar", 111111, {}], f5(str: "bar", num: 111111))
 | 
			
		||||
    assert_equal(["bar", 424242, {:check=>true}], f5(str: "bar", check: true))
 | 
			
		||||
    assert_raise(ArgumentError) { f5("string") }
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  def f6(str: "foo", num: 424242, **h, &blk)
 | 
			
		||||
    [str, num, h, blk]
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def test_f6 # [ruby-core:40518]
 | 
			
		||||
    assert_equal(["foo", 424242, {}, nil], f6)
 | 
			
		||||
    assert_equal(["bar", 424242, {}, nil], f6(str: "bar"))
 | 
			
		||||
    assert_equal(["foo", 111111, {}, nil], f6(num: 111111))
 | 
			
		||||
    assert_equal(["bar", 111111, {}, nil], f6(str: "bar", num: 111111))
 | 
			
		||||
    assert_equal(["bar", 424242, {:check=>true}, nil], f6(str: "bar", check: true))
 | 
			
		||||
    a = f6 {|x| x + 42 }
 | 
			
		||||
    assert_equal(["foo", 424242, {}], a[0, 3])
 | 
			
		||||
    assert_equal(43, a.last.call(1))
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def f7(*r, str: "foo", num: 424242, **h)
 | 
			
		||||
    [r, str, num, h]
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def test_f7 # [ruby-core:41772]
 | 
			
		||||
    assert_equal([[], "foo", 424242, {}], f7)
 | 
			
		||||
    assert_equal([[], "bar", 424242, {}], f7(str: "bar"))
 | 
			
		||||
    assert_equal([[], "foo", 111111, {}], f7(num: 111111))
 | 
			
		||||
    assert_equal([[], "bar", 111111, {}], f7(str: "bar", num: 111111))
 | 
			
		||||
    assert_equal([[1], "foo", 424242, {}], f7(1))
 | 
			
		||||
    assert_equal([[1, 2], "foo", 424242, {}], f7(1, 2))
 | 
			
		||||
    assert_equal([[1, 2, 3], "foo", 424242, {}], f7(1, 2, 3))
 | 
			
		||||
    assert_equal([[1], "bar", 424242, {}], f7(1, str: "bar"))
 | 
			
		||||
    assert_equal([[1, 2], "bar", 424242, {}], f7(1, 2, str: "bar"))
 | 
			
		||||
    assert_equal([[1, 2, 3], "bar", 424242, {}], f7(1, 2, 3, str: "bar"))
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  def test_lambda
 | 
			
		||||
    f = ->(str: "foo", num: 424242) { [str, num] }
 | 
			
		||||
    assert_equal(["foo", 424242], f[])
 | 
			
		||||
    assert_equal(["bar", 424242], f[str: "bar"])
 | 
			
		||||
    assert_equal(["foo", 111111], f[num: 111111])
 | 
			
		||||
    assert_equal(["bar", 111111], f[str: "bar", num: 111111])
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
| 
						 | 
				
			
			@ -220,6 +220,9 @@ struct rb_iseq_struct {
 | 
			
		|||
    int arg_post_start;
 | 
			
		||||
    int arg_size;
 | 
			
		||||
    VALUE *arg_opt_table;
 | 
			
		||||
    int arg_keyword;
 | 
			
		||||
    int arg_keywords;
 | 
			
		||||
    ID *arg_keyword_table;
 | 
			
		||||
 | 
			
		||||
    size_t stack_max; /* for stack overflow check */
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -132,6 +132,15 @@ argument_error(const rb_iseq_t *iseq, int miss_argc, int correct_argc)
 | 
			
		|||
    rb_exc_raise(exc);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
NORETURN(static void unknown_keyword_error(const rb_iseq_t *iseq, VALUE hash));
 | 
			
		||||
static void
 | 
			
		||||
unknown_keyword_error(const rb_iseq_t *iseq, VALUE hash)
 | 
			
		||||
{
 | 
			
		||||
    (void) iseq;
 | 
			
		||||
    (void) hash;
 | 
			
		||||
    rb_raise(rb_eArgError, "unknown keyword");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#define VM_CALLEE_SETUP_ARG(ret, th, iseq, orig_argc, orig_argv, block) \
 | 
			
		||||
    if (LIKELY((iseq)->arg_simple & 0x01)) { \
 | 
			
		||||
	/* simple check */ \
 | 
			
		||||
| 
						 | 
				
			
			@ -153,9 +162,30 @@ vm_callee_setup_arg_complex(rb_thread_t *th, const rb_iseq_t * iseq,
 | 
			
		|||
    int argc = orig_argc;
 | 
			
		||||
    VALUE *argv = orig_argv;
 | 
			
		||||
    rb_num_t opt_pc = 0;
 | 
			
		||||
    VALUE keyword_hash = Qnil;
 | 
			
		||||
 | 
			
		||||
    th->mark_stack_len = argc + iseq->arg_size;
 | 
			
		||||
 | 
			
		||||
    if (iseq->arg_keyword != -1) {
 | 
			
		||||
	int i, j;
 | 
			
		||||
	if (argc > 0) keyword_hash = rb_check_convert_type(argv[argc-1], T_HASH, "Hash", "to_hash");
 | 
			
		||||
	if (!NIL_P(keyword_hash)) {
 | 
			
		||||
	    argc--;
 | 
			
		||||
	    keyword_hash = rb_hash_dup(keyword_hash);
 | 
			
		||||
	    if (iseq->arg_keywords) {
 | 
			
		||||
		for (i = j = 0; i < iseq->arg_keywords; i++) {
 | 
			
		||||
		    if (st_lookup(RHASH_TBL(keyword_hash), ID2SYM(iseq->arg_keyword_table[i]), 0)) j++;
 | 
			
		||||
		}
 | 
			
		||||
		if (RHASH_TBL(keyword_hash)->num_entries > (unsigned int) j) {
 | 
			
		||||
		    unknown_keyword_error(iseq, keyword_hash);
 | 
			
		||||
		}
 | 
			
		||||
	    }
 | 
			
		||||
	}
 | 
			
		||||
	else {
 | 
			
		||||
	    keyword_hash = rb_hash_new();
 | 
			
		||||
	}
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /* mandatory */
 | 
			
		||||
    if (argc < (m + iseq->arg_post_len)) { /* check with post arg */
 | 
			
		||||
	argument_error(iseq, argc, m + iseq->arg_post_len);
 | 
			
		||||
| 
						 | 
				
			
			@ -205,6 +235,11 @@ vm_callee_setup_arg_complex(rb_thread_t *th, const rb_iseq_t * iseq,
 | 
			
		|||
	argc = 0;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /* keyword argument */
 | 
			
		||||
    if (iseq->arg_keyword != -1) {
 | 
			
		||||
	orig_argv[iseq->arg_keyword] = keyword_hash;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /* block arguments */
 | 
			
		||||
    if (block && iseq->arg_block != -1) {
 | 
			
		||||
	VALUE blockval = Qnil;
 | 
			
		||||
| 
						 | 
				
			
			@ -230,6 +265,10 @@ vm_callee_setup_arg_complex(rb_thread_t *th, const rb_iseq_t * iseq,
 | 
			
		|||
	orig_argv[iseq->arg_block] = blockval; /* Proc or nil */
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (iseq->arg_keyword && argc != 0) {
 | 
			
		||||
	argument_error(iseq, orig_argc, m + iseq->arg_post_len);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    th->mark_stack_len = 0;
 | 
			
		||||
    return (int)opt_pc;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue