diff --git a/doc/implicit_conversion.rdoc b/doc/implicit_conversion.rdoc new file mode 100644 index 0000000000..fae5973f5c --- /dev/null +++ b/doc/implicit_conversion.rdoc @@ -0,0 +1,198 @@ +== Implicit Conversions + +Some Ruby methods accept one or more objects +that can be either: +* Of a given class, and so accepted as is. +* Implicitly convertible to that class, in which case + the called method converts the object. + +For each of the relevant classes, the conversion is done by calling +a specific conversion method: + +* Array: +to_ary+ +* Hash: +to_hash+ +* Integer: +to_int+ +* String: +to_str+ + +=== Array-Convertible Objects + +An Array-convertible object is an object that: +* Has instance method +to_ary+. +* The method accepts no arguments. +* The method returns an object +obj+ for which obj.kind_of?(Array) returns +true+. + +The examples in this section use method Array#replace, +which accepts an Array-convertible argument. + +This class is Array-convertible: + + class ArrayConvertible + def to_ary + [:foo, 'bar', baz = 2] + end + end + a = [] + a.replace(ArrayConvertible.new) # => [:foo, "bar", 2] + +This class is not Array-convertible (no +to_ary+ method): + + class NotArrayConvertible; end + a = [] + # Raises TypeError (no implicit conversion of NotArrayConvertible into Array) + a.replace(NotArrayConvertible.new) + +This class is not Array-convertible (method +to_ary+ takes arguments): + + class NotArrayConvertible + def to_ary(x) + [:foo, 'bar', baz = 2] + end + end + a = [] + # Raises ArgumentError (wrong number of arguments (given 0, expected 1)) + a.replace(NotArrayConvertible.new) + +This class is not Array-convertible (method +to_ary+ returns non-Array): + + class NotArrayConvertible + def to_ary + :foo + end + end + a = [] + # Raises TypeError (can't convert NotArrayConvertible to Array (NotArrayConvertible#to_ary gives Symbol)) + a.replace(NotArrayConvertible.new) + +=== Hash-Convertible Objects + +A Hash-convertible object is an object that: +* Has instance method +to_hash+. +* The method accepts no arguments. +* The method returns an object +obj+ for which obj.kind_of?(Hash) returns +true+. + +The examples in this section use method Hash#merge, +which accepts a Hash-convertible argument. + +This class is Hash-convertible: + + class HashConvertible + def to_hash + {foo: 0, bar: 1, baz: 2} + end + end + h = {} + h.merge(HashConvertible.new) # => {:foo=>0, :bar=>1, :baz=>2} + +This class is not Hash-convertible (no +to_hash+ method): + + class NotHashConvertible; end + h = {} + # Raises TypeError (no implicit conversion of NotHashConvertible into Hash) + h.merge(NotHashConvertible.new) + +This class is not Hash-convertible (method +to_hash+ takes arguments): + + class NotHashConvertible + def to_hash(x) + {foo: 0, bar: 1, baz: 2} + end + end + h = {} + # Raises ArgumentError (wrong number of arguments (given 0, expected 1)) + h.merge(NotHashConvertible.new) + +This class is not Hash-convertible (method +to_hash+ returns non-Hash): + + class NotHashConvertible + def to_hash + :foo + end + end + h = {} + # Raises TypeError (can't convert NotHashConvertible to Hash (ToHashReturnsNonHash#to_hash gives Symbol)) + h.merge(NotHashConvertible.new) + +=== Integer-Convertible Objects + +An Integer-convertible object is an object that: +* Has instance method +to_int+. +* The method accepts no arguments. +* The method returns an object +obj+ for which obj.kind_of?(Integer) returns +true+. + +The examples in this section use method Array.new, +which accepts an Integer-convertible argument. + +This user-defined class is Integer-convertible: + + class IntegerConvertible + def to_int + 3 + end + end + a = Array.new(IntegerConvertible.new).size + a # => 3 + +This class is not Integer-convertible (method +to_int+ takes arguments): + + class NotIntegerConvertible + def to_int(x) + 3 + end + end + # Raises ArgumentError (wrong number of arguments (given 0, expected 1)) + Array.new(NotIntegerConvertible.new) + +This class is not Integer-convertible (method +to_int+ returns non-Integer): + + class NotIntegerConvertible + def to_int + :foo + end + end + # Raises TypeError (can't convert NotIntegerConvertible to Integer (NotIntegerConvertible#to_int gives Symbol)) + Array.new(NotIntegerConvertible.new) + +=== String-Convertible Objects + +A String-convertible object is an object that: +* Has instance method +to_str+. +* The method accepts no arguments. +* The method returns an object +obj+ for which obj.kind_of?(String) returns +true+. + +The examples in this section use method String::new, +which accepts a String-convertible argument. + +This class is String-convertible: + + class StringConvertible + def to_str + 'foo' + end + end + String.new(StringConvertible.new) # => "foo" + +This class is not String-convertible (no +to_str+ method): + + class NotStringConvertible; end + # Raises TypeError (no implicit conversion of NotStringConvertible into String) + String.new(NotStringConvertible.new) + +This class is not String-convertible (method +to_str+ takes arguments): + + class NotStringConvertible + def to_str(x) + 'foo' + end + end + # Raises ArgumentError (wrong number of arguments (given 0, expected 1)) + String.new(NotStringConvertible.new) + +This class is not String-convertible (method +to_str+ returns non-String): + + class NotStringConvertible + def to_str + :foo + end + end + # Raises TypeError (can't convert NotStringConvertible to String (NotStringConvertible#to_str gives Symbol)) + String.new(NotStringConvertible.new) diff --git a/hash.c b/hash.c index e0f827b80f..ab6dd3ba79 100644 --- a/hash.c +++ b/hash.c @@ -1810,22 +1810,74 @@ rb_hash_initialize(int argc, VALUE *argv, VALUE hash) /* * call-seq: - * Hash[ key, value, ... ] -> new_hash - * Hash[ [ [key, value], ... ] ] -> new_hash - * Hash[ object ] -> new_hash + * Hash[] -> new_empty_hash + * Hash[ [*2_element_arrays] ] -> new_hash + * Hash[*objects] -> new_hash + * Hash[hash_convertible_object] -> new_hash * - * Creates a new hash populated with the given objects. + * Returns a new \Hash object populated with the given objects, if any. * - * Similar to the literal { _key_ => _value_, ... }. In the first - * form, keys and values occur in pairs, so there must be an even number of - * arguments. + * The initial default value and default proc are set to nil + * (see {Default Values}[#class-Hash-label-Default+Values]): * - * The second and third form take a single argument which is either an array - * of key-value pairs or an object convertible to a hash. + * h = Hash[] + * h # => {} + * h.class # => Hash + * h.default # => nil + * h.default_proc # => nil * - * Hash["a", 100, "b", 200] #=> {"a"=>100, "b"=>200} - * Hash[ [ ["a", 100], ["b", 200] ] ] #=> {"a"=>100, "b"=>200} - * Hash["a" => 100, "b" => 200] #=> {"a"=>100, "b"=>200} + * When argument [*2_element_arrays] is given, + * each element of the outer array must be a 2-element array; + * returns a new \Hash object wherein each 2-element array forms a key-value entry: + * + * Hash[ [ [:foo, 0], [:bar, 1] ] ] # => {:foo=>0, :bar=>1} + * + * When arguments *objects are given, + * the argument count must be an even number; + * returns a new \Hash object wherein each successive pair of arguments has become a key-value entry: + * + * Hash[] # => {} + * Hash[:foo, 0, :bar, 1] # => {:foo=>0, :bar=>1} + * + * When argument hash_convertible_object is given, + * the argument must be a + * {Hash-convertible object}[doc/implicit_conversion_rdoc.html#label-Hash-Convertible+Objects]; + * converts the object and returns the resulting \Hash object: + * + * class Foo + * def to_hash + * {foo: 0, bar: 1} + * end + * end + * Hash[Foo.new] # => {:foo=>0, :bar=>1} + * + * --- + * + * Raises an exception if the argument count is 1, + * but the argument is not an array of 2-element arrays or a + * {Hash-convertible object}[doc/implicit_conversion_rdoc.html#label-Hash-Convertible+Objects]: + * + * Hash[:foo] # Raises ArgumentError (odd number of arguments + * Hash[ [ [:foo, 0, 1] ] ] # Raises ArgumentError (invalid number of elements (3 for 1..2)) + * + * Raises an exception if the argument count is odd and greater than 1: + * + * Hash[0, 1, 2] # Raises ArgumentError (odd number of arguments for Hash) + * + * Raises an exception if the argument is an array containing an element + * that is not a 2-element array: + * + * Hash[ [ :foo ] ] # Raises ArgumentError (wrong element type Symbol at 0 (expected array)) + * + * Raises an exception if the argument is an array containing an element + * that is an array of size different from 2: + * + * Hash[ [ [0, 1, 2] ] ] # Raises ArgumentError (invalid number of elements (3 for 1..2)) + * + * Raises an exception if any proposed key is not a valid key: + * + * Hash[:foo, 0, BasicObject.new, 1] # Raises NoMethodError (undefined method `hash' for #) + * Hash[ [ [:foo, 0], [BasicObject.new, 1] ] ] # Raises NoMethodError (undefined method `hash' for #) */ static VALUE