Temporary tag for this failure. Updated CI spec coming.
[rbx.git] / kernel / core / module.rb
blob15ec19fc644440a287772a81ea86a1083b25c9c4
1 # depends on: class.rb proc.rb autoload.rb
3 ##
4 # Some terminology notes:
6 # [Encloser] The Class or Module inside which this one is defined or, in the
7 #            event we are at top-level, Object.
9 # [Direct superclass] Whatever is next in the chain of superclass invocations.
10 #                     This may be either an included Module, a Class or nil.
12 # [Superclass] The real semantic superclass and thus only applies to Class
13 #              objects.
15 class Module
17   ivar_as_index :__ivars__ => 0, :method_table => 1, :method_cache => 2, :name => 3, :constants => 4, :encloser => 5, :superclass => 6
19   def method_table    ; @method_table ; end
20   def method_cache    ; @method_cache ; end
21   def constants_table ; @constants    ; end
22   def encloser        ; @encloser  ; end
23   
24   def constants_table=(c) ; @constants = c    ; end
25   def method_table=(m)    ; @method_table = m ; end
27   def self.nesting
28     mod  = MethodContext.current.sender.receiver
29     unless mod.kind_of? Module
30       mod = MethodContext.current.sender.method_module
31     end
32     nesting = []
33     while mod != Object && mod.kind_of?(Module)
34       nesting << mod
35       mod = mod.encloser
36     end
37     nesting
38   end
40   def initialize(&block)
41     _eval_under(self, &block) if block
42   end
44   #--
45   # HACK: This should work after after the bootstrap is loaded,
46   # but it seems to blow things up, so it's only used after
47   # core is loaded. I think it's because the bootstrap Class#new
48   # doesn't use a privileged send.
49   #++
51   def __method_added__cv(name)
52     if name == :initialize
53       private :initialize
54     end
56     method_added(name) if self.respond_to? :method_added
57   end
59   def verify_class_variable_name(name)
60     name = name.kind_of?(Symbol) ? name.to_s : StringValue(name)
61     unless name[0..1] == '@@' and name[2].toupper.between?(?A, ?Z) or name[2] == ?_
62       raise NameError, "#{name} is not an allowed class variable name"
63     end
64     name.to_sym
65   end
66   private :verify_class_variable_name
68   def class_variables_table
69     @class_variables ||= Hash.new
70   end
71   private :class_variables_table
73   def class_variable_set(name, val)
74     name = verify_class_variable_name name
76     current = direct_superclass
77     while current
78       if current.__kind_of__ MetaClass
79         vars = current.attached_instance.send :class_variables_table
80       elsif current.__kind_of__ IncludedModule
81         vars = current.module.send :class_variables_table
82       else
83         vars = current.send :class_variables_table
84       end
85       return vars[name] = val if vars.key? name
86       current = current.direct_superclass
87     end
89     if self.__kind_of__ MetaClass
90       table = self.attached_instance.send :class_variables_table
91     else
92       table = class_variables_table
93     end
94     table[name] = val
95   end
97   def class_variable_get(name)
98     name = verify_class_variable_name name
100     current = self
101     while current
102       if current.__kind_of__ MetaClass
103         vars = current.attached_instance.send :class_variables_table
104       elsif current.__kind_of__ IncludedModule
105         vars = current.module.send :class_variables_table
106       else
107         vars = current.send :class_variables_table
108       end
109       return vars[name] if vars.key? name
110       current = current.direct_superclass
111     end
113     # Try to print something useful for anonymous modules and metaclasses
114     module_name = self.name || self.inspect
115     raise NameError, "uninitialized class variable #{name} in #{module_name}"
116   end
118   def class_variable_defined?(name)
119     name = verify_class_variable_name name
121     current = self
122     while current
123       if current.__kind_of__ IncludedModule
124         vars = current.module.send :class_variables_table
125       else
126         vars = current.send :class_variables_table
127       end
128       return true if vars.key? name
129       current = current.direct_superclass
130     end
131     return false
132   end
134   def class_variables(symbols = false)
135     names = []
136     ancestors.map do |mod|
137       names.concat mod.send(:class_variables_table).keys
138     end
139     names = names.map { |name| name.to_s } unless symbols
140     names
141   end
143   def name
144     @name ? @name.to_s : ""
145   end
147   def to_s
148     @name ? @name.to_s : super
149   end
151   alias_method :inspect, :to_s
153   def find_method_in_hierarchy(sym)
154     mod = self
156     while mod
157       if method = mod.method_table[sym.to_sym]
158         return method
159       end
161       mod = mod.direct_superclass
162     end
164     # Always also search Object (and everything included in Object).
165     # This lets a module alias methods on Kernel.
166     if instance_of?(Module) and self != Kernel
167       return Object.find_method_in_hierarchy(sym)
168     end
169   end
171   def ancestors
172     if self.class == MetaClass
173       out = []
174     else
175       out = [self]
176     end
177     sup = direct_superclass()
178     while sup
179       if sup.class == IncludedModule
180         out << sup.module
181       elsif sup.class != MetaClass
182         out << sup
183       end
184       sup = sup.direct_superclass()
185     end
186     return out
187   end
189   def superclass_chain
190     out = []
191     mod = direct_superclass()
192     while mod
193       out << mod
194       mod = mod.direct_superclass()
195     end
197     return out
198   end
200   # Create a wrapper to a function in a C-linked library that
201   # exists somewhere in the system. If a specific library is
202   # not given, the function is assumed to exist in the running
203   # process, the Rubinius executable. The process contains many
204   # linked libraries in addition to Rubinius' codebase, libc of
205   # course the most prominent on the system side. The wrapper
206   # method is added to the Module as a singleton method or a
207   # "class method."
208   #
209   # The function is specified like a declaration: the first
210   # argument is the type symbol for the return type (see FFI
211   # documentation for types), the second argument is the name
212   # of the function and the third argument is an Array of the
213   # types of the function's arguments. Currently at most 6
214   # arguments can be given.
215   #
216   #   # If you want to wrap this function:
217   #   int foobar(double arg_one, const char* some_string);
218   #
219   #   # The arguments to #attach_foreign look like this:
220   #   :int, 'foobar', [:double, :string]
221   #
222   # If the function is from an external library such as, say,
223   # libpcre, libcurl etc. you can give the name or path of
224   # the library. The fourth argument is an option hash and
225   # the library name should be given in the +:from+ key of
226   # the hash. The name may (and for portable code, should)
227   # omit the file extension. If the extension is present,
228   # it must be the correct one for the runtime platform.
229   # The library is searched for in the system library paths
230   # but if necessary, the full absolute or relative path can
231   # be given.
232   #
233   # By default, the new method's name is the same as the
234   # function it wraps but in some cases it is desirable to
235   # change this. You can specify the method name in the +:as+
236   # key of the option hash.
237   def attach_foreign(ret_type, name, arg_types, opts = {})
238     lib = opts[:from]
240     if lib and !lib.chomp! ".#{Rubinius::LIBSUFFIX}"
241       lib.chomp! ".#{Rubinius::ALT_LIBSUFFIX}" rescue nil     # .defined? is broken anyway
242     end
244     func = FFI.create_function lib, name.to_s, arg_types, ret_type
245     metaclass.method_table[(opts[:as] || name).to_sym] = func
246   end
248   def find_class_method_in_hierarchy(sym)
249     self.metaclass.find_method_in_hierarchy(sym)
250   end
252   def alias_method_cv(new_name, current_name)
253     new_name = normalize_name(new_name)
254     current_name = normalize_name(current_name)
255     meth = find_method_in_hierarchy(current_name)
256     # We valid +meth+ because all hell can break loose if method_table has unexpected
257     # objects in it.
258     if meth
259       if meth.kind_of? Tuple
260         meth = meth.dup
262         if !meth[0].kind_of?(Symbol) or
263              not (meth[1].kind_of?(CompiledMethod) or meth[1].kind_of?(AccessVarMethod))
264           raise TypeError, "Invalid object found in method_table while attempting to alias '#{current_name}'"
265         end
267       else
268         # REFACTOR: pull up a common superclass and test against that
269         unless meth.kind_of?(CompiledMethod) or meth.kind_of?(AccessVarMethod) or meth.kind_of?(DelegatedMethod) then
270           raise TypeError, "Invalid object found in method_table while attempting to alias '#{current_name}' #{meth.inspect}"
271         end
272       end
273       method_table[new_name] = meth
274       Rubinius::VM.reset_method_cache(new_name)
275     else
276       if self.kind_of? MetaClass
277         raise NameError, "Unable to find '#{current_name}' for object #{self.attached_instance.inspect}"
278       else
279         thing = self.kind_of?(Class) ? "class" : "module"
280         raise NameError, "undefined method `#{current_name}' for #{thing} `#{self.name}'"
281       end
282     end
283   end
285   def remote_alias(new_name, mod, current_name)
286     cm = mod.find_method_in_hierarchy(current_name)
287     unless cm
288       raise NameError, "Unable to find method '#{current_name}' under #{mod}"
289     end
291     if cm.kind_of? Tuple
292       meth = cm[1]
293     else
294       meth = cm
295     end
297     if meth.primitive and meth.primitive > 0
298       raise NameError, "Unable to remote alias primitive method '#{current_name}'"
299     end
301     method_table[new_name] = cm
302     Rubinius::VM.reset_method_cache(new_name)
304     return new_name
305   end
307   def undef_method(*names)
308     names.each do |name|
309       name = normalize_name(name)
310       # Will raise a NameError if the method doesn't exist.
311       instance_method(name)
312       method_table[name] = false
313       Rubinius::VM.reset_method_cache(name)
315       method_undefined(name) if respond_to? :method_undefined
316     end
318     nil
319   end
321   def remove_method(*names)
322     names.each do |name|
323       name = normalize_name(name)
324       # Will raise a NameError if the method doesn't exist.
325       instance_method(name)
326       unless self.method_table[name]
327         raise NameError, "method `#{name}' not defined in #{self.name}"
328       end
329       method_table.delete name
330       Rubinius::VM.reset_method_cache(name)
332       method_removed(name) if respond_to? :method_removed
333     end
335     nil
336   end
338   def public_method_defined?(sym)
339     sym = StringValue(sym) unless sym.is_a? Symbol
340     m = find_method_in_hierarchy sym
341     m &&= Tuple[:public, m] unless m.is_a? Tuple
342     m ? m.first == :public : false
343   end
345   def private_method_defined?(sym)
346     sym = StringValue(sym) unless sym.is_a? Symbol
347     m = find_method_in_hierarchy sym
348     m &&= Tuple[:public, m] unless m.is_a? Tuple
349     m ? m.first == :private : false
350   end
352   def protected_method_defined?(sym)
353     sym = StringValue(sym) unless sym.is_a? Symbol
354     m = find_method_in_hierarchy sym
355     m &&= Tuple[:public, m] unless m.is_a? Tuple
356     m ? m.first == :protected : false
357   end
359   def method_defined?(sym)
360     sym = normalize_name(sym)
361     m = find_method_in_hierarchy sym
362     m &&= Tuple[:public, m] unless m.is_a? Tuple
363     m ? [:public,:protected].include?(m.first) : false
364   end
366   ##
367   # Returns an UnboundMethod corresponding to the given name. The name will be
368   # searched for in this Module as well as any included Modules or
369   # superclasses. The UnboundMethod is populated with the method name and the
370   # Module that the method was located in.
371   #
372   # Raises a TypeError if the given name.to_sym fails and a NameError if the
373   # name cannot be located.
375   def instance_method(name)
376     name = Type.coerce_to name, Symbol, :to_sym
378     mod, cmethod = __find_method(name)
380     # We want to show the real module
381     mod = mod.module if mod.class == IncludedModule
382     return UnboundMethod.new(mod, cmethod, self) if cmethod
384     raise NameError, "Undefined method `#{name}' for #{self}"
385   end
387   def instance_methods(all=true)
388     filter_methods(:public_names, all) | filter_methods(:protected_names, all)
389   end
391   def public_instance_methods(all=true)
392     filter_methods(:public_names, all)
393   end
395   def private_instance_methods(all=true)
396     filter_methods(:private_names, all)
397   end
399   def protected_instance_methods(all=true)
400     filter_methods(:protected_names, all)
401   end
403   def filter_methods(filter, all)
404     names = method_table.__send__(filter)
405     unless all or self.is_a?(MetaClass) or self.is_a?(IncludedModule)
406       return names.map { |name| name.to_s }
407     end
409     excludes = method_table.map { |name, meth| meth == false ? name : nil }
410     undefed = excludes.compact
412     sup = direct_superclass
414     while sup
415       names |= sup.method_table.__send__(filter)
417       excludes = method_table.map { |name, meth| meth == false ? name : nil }
418       undefed += excludes.compact
420       sup = sup.direct_superclass
421     end
423     (names - undefed).map { |name| name.to_s }
424   end
425   # private :filter_methods
427   def define_method(name, meth = nil, &prc)
428     meth ||= prc
430     if meth.kind_of?(Proc)
431       block_env = meth.block
432       cm = DelegatedMethod.build(:call_on_instance, block_env, true)
433     elsif meth.kind_of?(Method)
434       cm = DelegatedMethod.build(:call, meth, false)
435     elsif meth.kind_of?(UnboundMethod)
436       cm = DelegatedMethod.build(:call_on_instance, meth, true)
437     else
438       raise TypeError, "wrong argument type #{meth.class} (expected Proc/Method)"
439     end
441     self.method_table[name.to_sym] = cm
442     Rubinius::VM.reset_method_cache(name.to_sym)
443     meth
444   end
446   def extend_object(obj)
447     append_features obj.metaclass
448   end
450   #--
451   # Don't call this include, otherwise it will shadow the bootstrap version
452   # while core loads (a violation of the core/bootstrap boundry)
453   #++
455   def include_cv(*modules)
456     modules.reverse_each do |mod|
457       if !mod.kind_of?(Module) or mod.kind_of?(Class)
458         raise TypeError, "wrong argument type #{mod.class} (expected Module)"
459       end
461       next if ancestors.include? mod
463       mod.send(:append_features, self)
464       mod.send(:included, self)
465     end
466   end
469   # Called when this Module is being included in another Module.
470   # This may be overridden for custom behaviour, but the default
471   # is to add constants, instance methods and module variables
472   # of this Module and all Modules that this one includes to the
473   # includer Module, which is passed in as the parameter +other+.
474   #
475   # See also #include.
476   #
477   def append_features_cv(other)
478     hierarchy = other.ancestors
480     superclass_chain.reverse_each do |ancestor|
481       if ancestor.instance_of? IncludedModule and not hierarchy.include? ancestor.module
482         IncludedModule.new(ancestor.module).attach_to other
483       end
484     end
486     IncludedModule.new(self).attach_to other
487   end
489   def include?(mod)
490     if !mod.kind_of?(Module) or mod.kind_of?(Class)
491       raise TypeError, "wrong argument type #{mod.class} (expected Module)"
492     end
493     ancestors.include? mod
494   end
496   def included_modules
497     out = []
498     sup = direct_superclass
500     while sup
501       if sup.class == IncludedModule
502         out << sup.module
503       end
505       sup = sup.direct_superclass
506     end
508     out
509   end
511   def set_visibility(meth, vis, where = nil)
512     name = normalize_name(meth)
513     vis = vis.to_sym
515     if entry = method_table[name] then
516       if entry.kind_of?(Tuple) then
517         entry = entry.dup
518         entry[0] = vis
519       else
520         entry = Tuple[vis, entry.dup]
521       end
522       method_table[name] = entry
523     elsif find_method_in_hierarchy(name) then
524       method_table[name] = Tuple[vis, nil]
525     else
526       raise NoMethodError, "Unknown #{where}method '#{name}' to make #{vis.to_s} (#{self})"
527     end
529     Rubinius::VM.reset_method_cache name
531     return name
532   end
534   def set_class_visibility(meth, vis)
535     metaclass.set_visibility meth, vis, "class "
536   end
538   #--
539   # As with include_cv above, don't call this private.
540   #++
542   def private_cv(*args)
543     if args.empty?
544       MethodContext.current.sender.method_scope = :private
545       return
546     end
548     args.each { |meth| set_visibility(meth, :private) }
549   end
551   def protected(*args)
552     if args.empty?
553       MethodContext.current.sender.method_scope = :protected
554       return
555     end
557     args.each { |meth| set_visibility(meth, :protected) }
558   end
560   def public(*args)
561     if args.empty?
562       MethodContext.current.sender.method_scope = nil
563       return
564     end
566     args.each { |meth| set_visibility(meth, :public) }
567   end
569   def private_class_method(*args)
570     args.each do |meth|
571       set_class_visibility(meth, :private)
572     end
573     self
574   end
576   def public_class_method(*args)
577     args.each do |meth|
578       set_class_visibility(meth, :public)
579     end
580     self
581   end
583   def module_exec(*args, &prc)
584     instance_exec(*args, &prc)
585   end
586   alias_method :class_exec, :module_exec
588   def module_function_cv(*args)
589     if args.empty?
590       ctx = MethodContext.current.sender
591       block_env = ctx.env if ctx.kind_of?(BlockContext)
592       # Set the method_scope in the home context if this is an eval
593       ctx = block_env.home_block if block_env and block_env.from_eval?
594       ctx.method_scope = :module
595     else
596       mc = self.metaclass
597       args.each do |meth|
598         method_name = normalize_name meth
599         method = find_method_in_hierarchy(method_name)
600         mc.method_table[method_name] = method.dup
601         mc.set_visibility method_name, :public
602         set_visibility method_name, :private
603       end
604     end
606     return self
607   end
609   def constants
610     constants = self.constants_table.keys
611     current = self.direct_superclass
613     while current != nil && current != Object
614       constants += current.constants_table.keys
615       current = current.direct_superclass
616     end
618     constants.map { |c| c.to_s }
619   end
621   def const_defined?(name)
622     name = normalize_const_name(name)
624     current = self
625     while current
626       return true if current.constants_table.has_key?(name)
627       current = current.direct_superclass
628     end
630     return false
631   end
633   # Check if a full constant path is defined, e.g. SomeModule::Something
634   def const_path_defined?(name)
635     # Start at Object if this is a fully-qualified path
636     if name[0,2] == "::" then
637       start = Object
638       pieces = name[2,(name.length - 2)].split("::")
639     else
640       start = self
641       pieces = name.split("::")
642     end
644     defined = false
645     current = start
646     while current and not defined
647       const = current
648       defined = pieces.all? do |piece|
649         if const.is_a?(Module) and const.constants_table.key?(piece)
650           const = const.constants_table[piece]
651           true
652         end
653       end
654       current = current.direct_superclass
655     end
656     return defined
657   end
659   def const_set(name, value)
660     if value.is_a? Module
661       value.set_name_if_necessary(name, self)
662     end
663     constants_table[normalize_const_name(name)] = value
665     return value
666   end
668   ##
669   # \_\_const_set__ is emitted by the compiler for const assignment in
670   # userland.
672   def __const_set__(name, value)
673     if constants_table[normalize_const_name(name)]
674       warn "already initialized constant #{name}"
675     end
676     return const_set(name, value)
677   end
679   ##
680   # Return the named constant enclosed in this Module.
681   #
682   # Included Modules and, for Class objects, superclasses are also searched.
683   # Modules will in addition look in Object. The name is attempted to convert
684   # using #to_str. If the constant is not found, #const_missing is called
685   # with the name.
687   def const_get(name)
688     recursive_const_get(name)
689   end
691   def const_lookup(name)
692     mod = self
694     parts = String(name).split '::'
695     parts.each do |part| mod = mod.const_get part end
697     mod
698   end
700   def const_missing(name)
701     raise NameError, "Missing or uninitialized constant: #{name}"
702   end
704   def attr_reader_cv(*names)
705     names.each do |name|
706       method_symbol = reader_method_symbol(name)
707       access_method = AccessVarMethod.get_ivar(attribute_symbol(name), normalize_name(name))
708       method_table[method_symbol] = access_method
709     end
711     return nil
712   end
714   def attr_writer_cv(*names)
715     names.each do |name|
716       method_symbol = writer_method_symbol(name)
717       access_method = AccessVarMethod.set_ivar(attribute_symbol(name), normalize_name(name))
718       method_table[method_symbol] = access_method
719     end
721     return nil
722   end
724   def attr_accessor_cv(*names)
725     names.each do |name|
726       attr(name,true)
727     end
729     return nil
730   end
732   def attr(name,writeable=false)
733     attr_reader(name)
734     attr_writer(name) if writeable
735     return nil
736   end
738   def <(other)
739     unless other.kind_of? Module
740       raise TypeError, "compared with non class/module"
741     end
742     return false if self.equal? other
743     ancestors.index(other) && true
744   end
746   def <=(other)
747     return true if self.equal? other
748     lt = self < other
749     return false if lt.nil? && other < self
750     lt
751   end
753   def >(other)
754     unless other.kind_of? Module
755       raise TypeError, "compared with non class/module"
756     end
757     other < self
758   end
760   def >=(other)
761     unless other.kind_of? Module
762       raise TypeError, "compared with non class/module"
763     end
764     return true if self.equal? other
765     gt = self > other
766     return false if gt.nil? && other > self
767     gt
768   end
770   def <=>(other)
771     return 0 if self.equal? other
772     return nil unless other.kind_of? Module
773     lt = self < other
774     if lt.nil?
775       other < self ? 1 : nil
776     else
777       lt ? -1 : 1
778     end
779   end
781   def ===(inst)
782     return true if inst.kind_of? self
783     # TODO: check if inst is extended by self
784     # inst.metaclass < self & true rescue false
785     false
786   end
788   def set_name_if_necessary(name, mod)
789     return unless @name.nil?
790     parts = [name.to_s]
791     while mod and mod != Object
792       parts.unshift mod.name
793       mod = mod.encloser
794     end
795     @name = parts.join("::").to_sym
796   end
798   #--
799   # Move the core versions in place now that everything is loaded.
800   #++
802   def self.after_loaded # :nodoc:
803     alias_method :__method_added__, :__method_added__cv
804     alias_method :alias_method, :alias_method_cv
805     alias_method :module_function, :module_function_cv
806     alias_method :include, :include_cv
807     alias_method :private, :private_cv
808     alias_method :append_features, :append_features_cv
809     alias_method :attr_reader, :attr_reader_cv
810     alias_method :attr_writer, :attr_writer_cv
811     alias_method :attr_accessor, :attr_accessor_cv
813     alias_method :attach_function, :attach_function_cv
815     private :alias_method
816   end
818   def autoload(name, path)
819     name = normalize_const_name(name)
820     raise ArgumentError, "empty file name" if path.empty?
821     trigger = Autoload.new(name, self, path)
822     constants_table[name] = trigger
823     return nil
824   end
826   def autoload?(name)
827     name = name.to_sym
828     return unless constants_table.key?(name)
829     trigger = constants_table[name]
830     return unless trigger.kind_of?(Autoload)
831     trigger.original_path
832   end
834   def remove_const(name)
835     sym = name.to_sym
836     const_missing(name) unless constants_table.has_key?(sym)
837     constants_table.delete(sym)
838   end
840   private :remove_const
842   def extended(name)
843   end
845   private :extended
847   def method_added(name)
848   end
850   private :method_added
852   # See #const_get for documentation.
853   def recursive_const_get(name, missing=true)
854     name = normalize_const_name(name)
856     current, constant = self, nil
858     while current
859       constant = current.constants_table[name]
860       constant = constant.call if constant.kind_of?(Autoload)
861       return constant if constant
862       current = current.direct_superclass
863     end
865     if self.kind_of?(Module) and not self.kind_of?(Class)
866       constant = Object.constants_table[name]
867       constant = constant.call if constant.kind_of?(Autoload)
868       return constant if constant
869     end
871     return nil unless missing
873     const_missing(name)
874   end
876   private :recursive_const_get
878   def normalize_name(name)
879     sym_name = nil
880     if name.respond_to?(:to_sym)
881       warn 'do not use Fixnums as Symbols' if name.kind_of?(Fixnum)
882       sym_name = name.to_sym
883     elsif name.respond_to?(:to_str)
884       sym_name = StringValue(name).to_sym
885     end
886     raise TypeError, "#{name} is not a symbol" unless sym_name
888     sym_name
889   end
891   private :normalize_name
893   def normalize_const_name(name)
894     name = normalize_name(name)
895     raise NameError, "wrong constant name #{name}" unless valid_const_name?(name)
896     name
897   end
899   private :normalize_const_name
901   #--
902   # Modified to fit definition at:
903   # http://docs.huihoo.com/ruby/ruby-man-1.4/syntax.html#variable
904   #++
906   def valid_const_name?(name)
907     name.to_s =~ /^((::)?[A-Z]\w*)+$/ ? true : false
908   end
910   private :valid_const_name?
912   def attribute_symbol(name)
913     "@#{normalize_name(name)}".to_sym
914   end
916   private :attribute_symbol
918   def reader_method_symbol(name)
919     normalize_name(name)
920   end
922   private :reader_method_symbol
924   def writer_method_symbol(name)
925     "#{normalize_name(name)}=".to_sym
926   end
928   private :writer_method_symbol