From: Yusuke Endoh Date: 2009-11-14T14:30:32+09:00 Subject: [ruby-dev:39685] [Feature #2366] private constant Feature #2366: private constant http://redmine.ruby-lang.org/issues/show/2366 起票者: Yusuke Endoh ステータス: Open, 優先度: Normal 担当者: Yukihiro Matsumoto, カテゴリ: core, Target version: 1.9.x 遠藤です。 今の Ruby には、クラスが公開 API かどうかを伝える手段がドキュメント しかありません。そのため、ERB::Compiler など、ライブラリの中の公開 でない (と思われる) inner class を外から自由に参照できてしまいます。 これを防ぐためには、匿名クラスを用いて定義すれば大分隠蔽できますが、 記述が相当煩雑になってしまいます。また、そのようにしてしまうと、 「非公開というのは承知の上で敢えて使いたい」という要求に答えにくく なります。 そこで、定数に public/private の属性を指定できるようにするのはどう でしょうか。 module SomeMod class PublicInnerCls end class PrivateInnerCls end # PrivateInnerCls を private にする private_constant :PrivateInnerCls end # public な定数は従来どおり参照できる p SomeMod::PublicInnerCls #=> SomeMod::PublicInnerCls # private な定数を外から参照しようとすると例外 p SomeMod::PrivateInnerCls #=> private constant (RuntimeError) # 同じスコープからは参照できる (望むなら自分の足を撃てる) p SomeMod.module_eval { PrivateInnerCls } #=> SomeMod::PrivateInnerCls p(module SomeMod; PrivateInnerCls; end) #=> SomeMod::PrivateInnerCls 要するに、メソッドの public/private と同じ感じです。 細かい仕様は詰めていませんが、proof of concept のパッチを付けます。 1 定数ごとに 2 要素の配列を作ってしまうので、ちゃんとした実装は必要 だと思います。 どんなものでしょうか。 ちなみにこの提案のきっかけは [ruby-dev:39677] です。 diff --git a/variable.c b/variable.c index 779a8e8..db3a81c 100644 --- a/variable.c +++ b/variable.c @@ -1525,6 +1525,33 @@ rb_autoload_p(VALUE mod, ID id) return load && (file = load->nd_lit) ? file : Qnil; } +void +rb_change_const_visibility(VALUE klass, ID id, VALUE ex) +{ + VALUE value, tmp; + int mod_retry = 0; + + tmp = klass; + while (RTEST(tmp)) { + VALUE am = 0; + while (RCLASS_IV_TBL(tmp) && st_lookup(RCLASS_IV_TBL(tmp), (st_data_t)id, &value)) { + if (value == Qundef) { + if (am == tmp) break; + am = tmp; + rb_autoload_load(tmp, id); + continue; + } + if (tmp == rb_cObject && klass != rb_cObject) { + rb_warn("toplevel constant %s referenced by %s::%s", + rb_id2name(id), rb_class2name(klass), rb_id2name(id)); + } + RARRAY_PTR(value)[1] = ex; + return; + } + tmp = RCLASS_SUPER(tmp); + } +} + static VALUE rb_const_get_0(VALUE klass, ID id, int exclude, int recurse) { @@ -1546,7 +1573,10 @@ rb_const_get_0(VALUE klass, ID id, int exclude, int recurse) rb_warn("toplevel constant %s referenced by %s::%s", rb_id2name(id), rb_class2name(klass), rb_id2name(id)); } - return value; + if (!RARRAY_PTR(value)[1]) { + rb_raise(rb_eRuntimeError, "private constant"); + } + return RARRAY_PTR(value)[0]; } if (!recurse && klass != rb_cObject) break; tmp = RCLASS_SUPER(tmp); @@ -1798,11 +1828,13 @@ mod_av_set(VALUE klass, ID id, VALUE val, int isconst) void rb_const_set(VALUE klass, ID id, VALUE val) { + VALUE ary; if (NIL_P(klass)) { rb_raise(rb_eTypeError, "no class/module to define constant %s", rb_id2name(id)); } - mod_av_set(klass, id, val, TRUE); + ary = rb_ary_new3(2, val, Qtrue); + mod_av_set(klass, id, ary, TRUE); } void diff --git a/vm_insnhelper.c b/vm_insnhelper.c index 0660c7d..e3e789e 100644 --- a/vm_insnhelper.c +++ b/vm_insnhelper.c @@ -1141,7 +1141,7 @@ vm_get_ev_const(rb_thread_t *th, const rb_iseq_t *iseq, return 1; } else { - return val; + return RARRAY_PTR(val)[0]; } } } diff --git a/vm_method.c b/vm_method.c index 557583f..40b125a 100644 --- a/vm_method.c +++ b/vm_method.c @@ -1038,6 +1038,30 @@ rb_mod_private_method(int argc, VALUE *argv, VALUE obj) return obj; } +static void +set_const_visibility(VALUE self, int argc, VALUE *argv, VALUE ex) +{ + int i; + extern void rb_change_const_visibility(VALUE klass, ID id, VALUE ex); + secure_visibility(self); + for (i = 0; i < argc; i++) { + rb_change_const_visibility(self, rb_to_id(argv[i]), ex); + } + rb_clear_cache_by_class(self); +} + +static VALUE +rb_mod_public_constant(int argc, VALUE *argv, VALUE obj) +{ + set_const_visibility(obj, argc, argv, Qtrue); +} + +static VALUE +rb_mod_private_constant(int argc, VALUE *argv, VALUE obj) +{ + set_const_visibility(obj, argc, argv, Qfalse); +} + /* * call-seq: * public @@ -1250,6 +1274,8 @@ Init_eval_method(void) rb_define_method(rb_cModule, "protected_method_defined?", rb_mod_protected_method_defined, 1); rb_define_method(rb_cModule, "public_class_method", rb_mod_public_method, -1); rb_define_method(rb_cModule, "private_class_method", rb_mod_private_method, -1); + rb_define_method(rb_cModule, "public_constant", rb_mod_public_constant, -1); + rb_define_method(rb_cModule, "private_constant", rb_mod_private_constant, -1); rb_define_singleton_method(rb_vm_top_self(), "public", top_public, -1); rb_define_singleton_method(rb_vm_top_self(), "private", top_private, -1); -- Yusuke Endoh ---------------------------------------- http://redmine.ruby-lang.org