Add rb_enumeratorize_with_size_kw and related macros

Currently, there is not a way to create a sized enumerator in C
with a different set of arguments than provided by Ruby, and
correctly handle keyword arguments.  This function allows that.

The need for this is fairly uncommon, but it occurs at least in
Enumerator.produce, which takes arugments from Ruby but calls
rb_enumeratorize_with_size with a different set of arguments.
This commit is contained in:
Jeremy Evans 2019-09-29 21:33:59 -07:00
Родитель 5ddc2ba13e
Коммит 3073404e74
7 изменённых файлов: 85 добавлений и 7 удалений

Просмотреть файл

@ -513,7 +513,7 @@ rb_enumeratorize(VALUE obj, VALUE meth, int argc, const VALUE *argv)
}
static VALUE
lazy_to_enum_i(VALUE self, VALUE meth, int argc, const VALUE *argv, rb_enumerator_size_func *size_fn);
lazy_to_enum_i(VALUE self, VALUE meth, int argc, const VALUE *argv, rb_enumerator_size_func *size_fn, int kw_splat);
VALUE
rb_enumeratorize_with_size(VALUE obj, VALUE meth, int argc, const VALUE *argv, rb_enumerator_size_func *size_fn)
@ -521,12 +521,24 @@ rb_enumeratorize_with_size(VALUE obj, VALUE meth, int argc, const VALUE *argv, r
/* Similar effect as calling obj.to_enum, i.e. dispatching to either
Kernel#to_enum vs Lazy#to_enum */
if (RTEST(rb_obj_is_kind_of(obj, rb_cLazy)))
return lazy_to_enum_i(obj, meth, argc, argv, size_fn);
return lazy_to_enum_i(obj, meth, argc, argv, size_fn, PASS_KW_SPLAT);
else
return enumerator_init(enumerator_allocate(rb_cEnumerator),
obj, meth, argc, argv, size_fn, Qnil, PASS_KW_SPLAT);
}
VALUE
rb_enumeratorize_with_size_kw(VALUE obj, VALUE meth, int argc, const VALUE *argv, rb_enumerator_size_func *size_fn, int kw_splat)
{
/* Similar effect as calling obj.to_enum, i.e. dispatching to either
Kernel#to_enum vs Lazy#to_enum */
if (RTEST(rb_obj_is_kind_of(obj, rb_cLazy)))
return lazy_to_enum_i(obj, meth, argc, argv, size_fn, kw_splat);
else
return enumerator_init(enumerator_allocate(rb_cEnumerator),
obj, meth, argc, argv, size_fn, Qnil, kw_splat);
}
static VALUE
enumerator_block_call(VALUE obj, rb_block_call_func *func, VALUE arg)
{
@ -1868,17 +1880,17 @@ lazy_add_method(VALUE obj, int argc, VALUE *argv, VALUE args, VALUE memo,
static VALUE
enumerable_lazy(VALUE obj)
{
VALUE result = lazy_to_enum_i(obj, sym_each, 0, 0, lazyenum_size);
VALUE result = lazy_to_enum_i(obj, sym_each, 0, 0, lazyenum_size, PASS_KW_SPLAT);
/* Qfalse indicates that the Enumerator::Lazy has no method name */
rb_ivar_set(result, id_method, Qfalse);
return result;
}
static VALUE
lazy_to_enum_i(VALUE obj, VALUE meth, int argc, const VALUE *argv, rb_enumerator_size_func *size_fn)
lazy_to_enum_i(VALUE obj, VALUE meth, int argc, const VALUE *argv, rb_enumerator_size_func *size_fn, int kw_splat)
{
return enumerator_init(enumerator_allocate(rb_cLazy),
obj, meth, argc, argv, size_fn, Qnil, PASS_KW_SPLAT);
obj, meth, argc, argv, size_fn, Qnil, kw_splat);
}
/*
@ -1916,7 +1928,7 @@ lazy_to_enum(int argc, VALUE *argv, VALUE self)
if (RTEST((super_meth = rb_hash_aref(lazy_use_super_method, meth)))) {
meth = super_meth;
}
lazy = lazy_to_enum_i(self, meth, argc, argv, 0);
lazy = lazy_to_enum_i(self, meth, argc, argv, 0, PASS_KW_SPLAT);
if (rb_block_given_p()) {
enumerator_ptr(lazy)->size = rb_block_proc();
}
@ -2919,7 +2931,7 @@ enumerator_s_produce(int argc, VALUE *argv, VALUE klass)
producer = producer_init(producer_allocate(rb_cEnumProducer), init, rb_block_proc());
return rb_enumeratorize_with_size(producer, sym_each, 0, 0, producer_size);
return rb_enumeratorize_with_size_kw(producer, sym_each, 0, 0, producer_size, RB_NO_KEYWORDS);
}
/*

Просмотреть файл

@ -0,0 +1,14 @@
# AUTOGENERATED DEPENDENCIES START
enumerator_kw.o: $(RUBY_EXTCONF_H)
enumerator_kw.o: $(arch_hdrdir)/ruby/config.h
enumerator_kw.o: $(hdrdir)/ruby.h
enumerator_kw.o: $(hdrdir)/ruby/assert.h
enumerator_kw.o: $(hdrdir)/ruby/backward.h
enumerator_kw.o: $(hdrdir)/ruby/defines.h
enumerator_kw.o: $(hdrdir)/ruby/intern.h
enumerator_kw.o: $(hdrdir)/ruby/missing.h
enumerator_kw.o: $(hdrdir)/ruby/ruby.h
enumerator_kw.o: $(hdrdir)/ruby/st.h
enumerator_kw.o: $(hdrdir)/ruby/subst.h
enumerator_kw.o: enumerator_kw.c
# AUTOGENERATED DEPENDENCIES END

Просмотреть файл

@ -0,0 +1,21 @@
#include <ruby.h>
static VALUE
enumerator_kw(int argc, VALUE *argv, VALUE self)
{
VALUE obj, opt, enum_args[4];
enum_args[0] = Qnil;
enum_args[1] = Qnil;
rb_scan_args(argc, argv, "01*:", enum_args, enum_args+1, &opt);
enum_args[3] = self;
enum_args[2] = opt;
RETURN_SIZED_ENUMERATOR_KW(self, 4, enum_args, 0, RB_NO_KEYWORDS);
return rb_yield_values_kw(4, enum_args, RB_NO_KEYWORDS);
}
void
Init_enumerator_kw(void) {
VALUE module = rb_define_module("Bug");
module = rb_define_module_under(module, "EnumeratorKw");
rb_define_method(module, "m", enumerator_kw, -1);
}

Просмотреть файл

@ -0,0 +1 @@
create_makefile("-test-/enumerator_kw")

Просмотреть файл

@ -251,18 +251,29 @@ VALUE rb_enum_values_pack(int, const VALUE*);
VALUE rb_enumeratorize(VALUE, VALUE, int, const VALUE *);
typedef VALUE rb_enumerator_size_func(VALUE, VALUE, VALUE);
VALUE rb_enumeratorize_with_size(VALUE, VALUE, int, const VALUE *, rb_enumerator_size_func *);
VALUE rb_enumeratorize_with_size_kw(VALUE, VALUE, int, const VALUE *, rb_enumerator_size_func *, int);
#ifndef RUBY_EXPORT
#define rb_enumeratorize_with_size(obj, id, argc, argv, size_fn) \
rb_enumeratorize_with_size(obj, id, argc, argv, (rb_enumerator_size_func *)(size_fn))
#define rb_enumeratorize_with_size_kw(obj, id, argc, argv, size_fn, kw_splat) \
rb_enumeratorize_with_size_kw(obj, id, argc, argv, (rb_enumerator_size_func *)(size_fn), kw_splat)
#endif
#define SIZED_ENUMERATOR(obj, argc, argv, size_fn) \
rb_enumeratorize_with_size((obj), ID2SYM(rb_frame_this_func()), \
(argc), (argv), (size_fn))
#define SIZED_ENUMERATOR_KW(obj, argc, argv, size_fn, kw_splat) \
rb_enumeratorize_with_size_kw((obj), ID2SYM(rb_frame_this_func()), \
(argc), (argv), (size_fn), (kw_splat))
#define RETURN_SIZED_ENUMERATOR(obj, argc, argv, size_fn) do { \
if (!rb_block_given_p()) \
return SIZED_ENUMERATOR(obj, argc, argv, size_fn); \
} while (0)
#define RETURN_SIZED_ENUMERATOR_KW(obj, argc, argv, size_fn, kw_splat) do { \
if (!rb_block_given_p()) \
return SIZED_ENUMERATOR_KW(obj, argc, argv, size_fn, kw_splat); \
} while (0)
#define RETURN_ENUMERATOR(obj, argc, argv) RETURN_SIZED_ENUMERATOR(obj, argc, argv, 0)
#define RETURN_ENUMERATOR_KW(obj, argc, argv, kw_splat) RETURN_SIZED_ENUMERATOR_KW(obj, argc, argv, 0, kw_splat)
typedef struct {
VALUE begin;
VALUE end;

Просмотреть файл

@ -0,0 +1,11 @@
require 'test/unit'
require '-test-/enumerator_kw'
class TestEnumeratorKw < Test::Unit::TestCase
def test_enumerator_kw
o = Object.new
o.extend Bug::EnumeratorKw
assert_equal([nil, [], {:a=>1}, o], o.m(a: 1) { |*a| a })
assert_equal([nil, [[], {:a=>1}, o], nil, o], o.m(a: 1).each { |*a| a })
end
end

Просмотреть файл

@ -831,6 +831,14 @@ class TestEnumerator < Test::Unit::TestCase
assert_equal [1, 2, 3], enum.take(3)
assert_equal [1, 2], passed_args
# With initial keyword arguments
passed_args = []
enum = Enumerator.produce(a: 1, b: 1) { |obj| passed_args << obj; obj.shift if obj.respond_to?(:shift)}
assert_instance_of(Enumerator, enum)
assert_equal Float::INFINITY, enum.size
assert_equal [{b: 1}, [1], :a, nil], enum.take(4)
assert_equal [{b: 1}, [1], :a], passed_args
# Raising StopIteration
words = "The quick brown fox jumps over the lazy dog.".scan(/\w+/)
enum = Enumerator.produce { words.shift or raise StopIteration }