diff --git a/spec/std/enumerable_spec.cr b/spec/std/enumerable_spec.cr index 084fe80dcf96..baffa410110e 100644 --- a/spec/std/enumerable_spec.cr +++ b/spec/std/enumerable_spec.cr @@ -1,4 +1,5 @@ require "spec" +require "../spec_helper" require "spec/helpers/iterate" module SomeInterface; end @@ -1364,6 +1365,19 @@ describe "Enumerable" do it { [1, 2, 3].sum(4.5).should eq(10.5) } it { (1..3).sum { |x| x * 2 }.should eq(12) } it { (1..3).sum(1.5) { |x| x * 2 }.should eq(13.5) } + it { [1, 3_u64].sum(0_i32).should eq(4_u32) } + it { [1, 3].sum(0_u64).should eq(4_u64) } + it { [1, 10000000000_u64].sum(0_u64).should eq(10000000001) } + it "raises if union types are summed", tags: %w[slow] do + exc = assert_error <<-CRYSTAL, + require "prelude" + [1, 10000000000_u64].sum + CRYSTAL + "`Enumerable#sum()` and `#product()` do not support Union " + + "types. Instead, use `Enumerable#sum(initial)` and " + + "`#product(initial)`, respectively, with an initial value " + + "of the intended type of the call." + end it "uses additive_identity from type" do typeof([1, 2, 3].sum).should eq(Int32) @@ -1405,6 +1419,20 @@ describe "Enumerable" do typeof([1.5, 2.5, 3.5].product).should eq(Float64) typeof([1, 2, 3].product(&.to_f)).should eq(Float64) end + + it { [1, 3_u64].product(3_i32).should eq(9_u32) } + it { [1, 3].product(3_u64).should eq(9_u64) } + it { [1, 10000000000_u64].product(3_u64).should eq(30000000000_u64) } + it "raises if union types are multiplied", tags: %w[slow] do + exc = assert_error <<-CRYSTAL, + require "prelude" + [1, 10000000000_u64].product + CRYSTAL + "`Enumerable#sum()` and `#product()` do not support Union " + + "types. Instead, use `Enumerable#sum(initial)` and " + + "`#product(initial)`, respectively, with an initial value " + + "of the intended type of the call." + end end describe "first" do diff --git a/src/enumerable.cr b/src/enumerable.cr index 0993f38bbc4d..f341409dc66c 100644 --- a/src/enumerable.cr +++ b/src/enumerable.cr @@ -1771,7 +1771,7 @@ module Enumerable(T) end private def additive_identity(reflect) - type = reflect.first + type = reflect.type if type.responds_to? :additive_identity type.additive_identity else @@ -1808,7 +1808,10 @@ module Enumerable(T) # Expects all types returned from the block to respond to `#+` method. # # This method calls `.additive_identity` on the yielded type to determine the - # type of the sum value. + # type of the sum value. Hence, it can fail to compile if + # `.additive_identity` fails to determine a safe type, e.g., in case of + # union types. In such cases, use `sum(initial)` with an initial value of + # the expected type of the sum value. # # If the collection is empty, returns `additive_identity`. # @@ -1847,7 +1850,7 @@ module Enumerable(T) # ``` # # This method calls `.multiplicative_identity` on the element type to determine the - # type of the sum value. + # type of the product value. # # If the collection is empty, returns `multiplicative_identity`. # @@ -1855,7 +1858,7 @@ module Enumerable(T) # ([] of Int32).product # => 1 # ``` def product - product Reflect(T).first.multiplicative_identity + product Reflect(T).type.multiplicative_identity end # Multiplies *initial* and all the elements in the collection @@ -1886,8 +1889,11 @@ module Enumerable(T) # # Expects all types returned from the block to respond to `#*` method. # - # This method calls `.multiplicative_identity` on the element type to determine the - # type of the sum value. + # This method calls `.multiplicative_identity` on the element type to + # determine the type of the product value. Hence, it can fail to compile if + # `.multiplicative_identity` fails to determine a safe type, e.g., in case + # of union types. In such cases, use `product(initial)` with an initial + # value of the expected type of the product value. # # If the collection is empty, returns `multiplicative_identity`. # @@ -1895,7 +1901,7 @@ module Enumerable(T) # ([] of Int32).product { |x| x + 1 } # => 1 # ``` def product(& : T -> _) - product(Reflect(typeof(yield Enumerable.element_type(self))).first.multiplicative_identity) do |value| + product(Reflect(typeof(yield Enumerable.element_type(self))).type.multiplicative_identity) do |value| yield value end end @@ -2287,12 +2293,16 @@ module Enumerable(T) # :nodoc: private struct Reflect(X) - # For now it's just a way to implement `Enumerable#sum` in a way that the - # initial value given to it has the type of the first type in the union, - # if the type is a union. - def self.first + # For now, Reflect is used to reject union types in `#sum()` and + # `#product()` methods. + def self.type {% if X.union? %} - {{X.union_types.first}} + {{ + raise("`Enumerable#sum()` and `#product()` do not support Union " + + "types. Instead, use `Enumerable#sum(initial)` and " + + "`#product(initial)`, respectively, with an initial value " + + "of the intended type of the call.") + }} {% else %} X {% end %}