require "spec" require "spec/helpers/iterate" private class TupleSpecObj getter x : Int32 def initialize(@x) end def clone TupleSpecObj.new(@x) end def_equals @x end describe "Tuple" do it "does size" do {1, 2, 1, 2}.size.should eq(4) end it "checks empty?" do Tuple.new.empty?.should be_true {1}.empty?.should be_false end describe "#[] with non-literal index" do it "gets tuple element" do a = {1, 2.5} i = 0 a[i].should eq(1) i = 1 a[i].should eq(2.5) i = -1 a[i].should eq(2.5) i = -2 a[i].should eq(1) typeof(a[i]).should eq(Int32 | Float64) end it "raises index out of bounds" do a = {1, 2.5} i = 2 expect_raises(IndexError) { a[i] } i = -3 expect_raises(IndexError) { a[i] } end end describe "#[]? with non-literal index" do it "gets tuple element or nil" do a = {1, 2.5} i = 0 a[i]?.should eq(1) i = -1 a[i]?.should eq(2.5) i = 2 a[i]?.should be_nil i = -3 a[i]?.should be_nil typeof(a[i]?).should eq(Int32 | Float64 | Nil) end end describe ".[] with non-literal index" do it "gets tuple metaclass element" do a = Tuple(Int32, Float64) i = 0 a[i].should eq(Int32) i = 1 a[i].should eq(Float64) i = -1 a[i].should eq(Float64) i = -2 a[i].should eq(Int32) end it "raises index out of bounds" do a = Tuple(Int32, Float64) i = 2 expect_raises(IndexError) { a[i] } i = -3 expect_raises(IndexError) { a[i] } end end describe ".[]? with non-literal index" do it "gets tuple metaclass element or nil" do a = Tuple(Int32, Float64) i = 0 a[i]?.should eq(Int32) i = -1 a[i]?.should eq(Float64) i = 2 a[i]?.should be_nil i = -3 a[i]?.should be_nil typeof(a[i]?).should eq(Union(Int32.class, Float64.class, Nil)) end end it "does at" do a = {1, 2} a.at(1).should eq(2) a.at(-1).should eq(2) expect_raises(IndexError) { a.at(2) } expect_raises(IndexError) { a.at(-3) } a.at(2) { 3 }.should eq(3) a.at(-3) { 3 }.should eq(3) end describe "values_at" do it "returns the given indexes" do {"a", "b", "c", "d"}.values_at(1, 0, 2).should eq({"b", "a", "c"}) end it "raises when passed an invalid index" do expect_raises IndexError do {"a"}.values_at(10) end end it "works with mixed types" do {1, "a", 1.0, false}.values_at(0, 1, 2, 3).should eq({1, "a", 1.0, false}) end end it "does ==" do a = {1, 2} b = {3, 4} c = {1, 2, 3} d = {1} e = {1, 2} a.should eq(a) a.should eq(e) a.should_not eq(b) a.should_not eq(c) a.should_not eq(d) end it "does == with different types but same size" do {1, 2}.should eq({1.0, 2.0}) end it "does == with another type" do {1, 2}.should_not eq(1) end it "does compare" do a = {1, 2} b = {3, 4} c = {1, 6} d = {3, 5} e = {0, 8} [a, b, c, d, e].sort.should eq([e, a, c, b, d]) [a, b, c, d, e].min.should eq(e) end it "does compare with different sizes" do a = {2} b = {1, 2, 3} c = {1, 2} d = {1, 1} e = {1, 1, 3} [a, b, c, d, e].sort.should eq([d, e, c, b, a]) [a, b, c, d, e].min.should eq(d) end describe "#to_s" do it "returns string representation" do {1, 2, 3}.to_s.should eq("{1, 2, 3}") end context "when the first element starts with '{'" do it "inserts a space after '{' and before '}' when the first element is a Hash, preventing macro interpolation ({{ ... }})" do tuple = { {1 => 2} } tuple.to_s.should eq("{ {1 => 2} }") end it "inserts a space after '{' and before '}' when the first element is a Tuple, preventing macro interpolation ({{ ... }})" do tuple = { {1, 2, 3} } tuple.to_s.should eq("{ {1, 2, 3} }") end it "inserts a space after '{' and before '}' when the first element is a NamedTuple, preventing macro interpolation ({{ ... }})" do tuple = { {a: 1} } tuple.to_s.should eq("{ {a: 1} }") end end end it "does each" do a = 0 {1, 2, 3}.each do |i| a += i end.should be_nil a.should eq(6) end it "does dup" do r1, r2 = TupleSpecObj.new(10), TupleSpecObj.new(20) t = {r1, r2} u = t.dup u.size.should eq(2) u[0].should be(r1) u[1].should be(r2) end it "does clone" do r1, r2 = TupleSpecObj.new(10), TupleSpecObj.new(20) t = {r1, r2} u = t.clone u.size.should eq(2) u[0].x.should eq(r1.x) u[0].should_not be(r1) u[1].x.should eq(r2.x) u[1].should_not be(r2) end it "does Tuple.new, without type vars" do Tuple.new(1, 2, 3).should eq({1, 2, 3}) Tuple.new([1, 2, 3]).should eq({[1, 2, 3]}) Tuple.new(TupleSpecObj.new(10)).should eq({TupleSpecObj.new(10)}) end it "does Tuple.new, with type vars" do Tuple(Int32, String).new(1, "a").should eq({1, "a"}) Tuple(TupleSpecObj).new(TupleSpecObj.new(10)).should eq({TupleSpecObj.new(10)}) typeof(Tuple.new).new.should eq(Tuple.new) t = Tuple(Int32 | String, Int32 | String).new(1, "a") t.should eq({1, "a"}) t.class.should_not eq(Tuple(Int32, String)) end it "does Tuple.from" do t = Tuple(Int32, Float64).from([1_i32, 2.0_f64]) t.should eq({1_i32, 2.0_f64}) t.class.should eq(Tuple(Int32, Float64)) expect_raises ArgumentError do Tuple(Int32).from([1, 2]) end expect_raises(TypeCastError, /[Cc]ast from String to Int32 failed/) do Tuple(Int32, String).from(["foo", 1]) end end it "does Tuple#from" do t = {Int32, Float64}.from([1_i32, 2.0_f64]) t.should eq({1_i32, 2.0_f64}) t.class.should eq(Tuple(Int32, Float64)) expect_raises ArgumentError do {Int32}.from([1, 2]) end expect_raises(TypeCastError, /[Cc]ast from String to Int32 failed/) do {Int32, String}.from(["foo", 1]) end end it "clones empty tuple" do Tuple.new.clone.should eq(Tuple.new) end it_iterates "#each", [1, 2, 3], {1, 2, 3}.each it "does map" do tuple = {1, 2.5, "a"} tuple2 = tuple.map &.to_s tuple2.is_a?(Tuple).should be_true tuple2.should eq({"1", "2.5", "a"}) end it "does map_with_index" do tuple = {1, 1, 2, 2} tuple2 = tuple.map_with_index { |e, i| e + i } tuple2.should eq({1, 2, 4, 5}) end it "does map_with_index, with offset" do tuple = {1, 1, 2, 2} tuple2 = tuple.map_with_index(10) { |e, i| e + i } tuple2.should eq({11, 12, 14, 15}) end it "does reverse" do {1, 2.5, "a", 'c'}.reverse.should eq({'c', "a", 2.5, 1}) end it_iterates "#reverse_each", [3, 2, 1], {1, 2, 3}.reverse_each it "gets first element" do tuple = {1, 2.5} tuple.first.should eq(1) typeof(tuple.first).should eq(Int32) end it "gets first? element" do tuple = {1, 2.5} tuple.first?.should eq(1) Tuple.new.first?.should be_nil end it "gets last element" do tuple = {1, 2.5, "a"} tuple.last.should eq("a") typeof(tuple.last).should eq(String) end it "gets last? element" do tuple = {1, 2.5, "a"} tuple.last?.should eq("a") Tuple.new.last?.should be_nil end it "does comparison" do tuple1 = {"a", "a", "c"} tuple2 = {"a", "b", "c"} (tuple1 <=> tuple2).should eq(-1) (tuple2 <=> tuple1).should eq(1) end it "does <=> for equality" do tuple1 = {0, 1} tuple2 = {0.0, 1} (tuple1 <=> tuple2).should eq(0) end it "does <=> with the same beginning and different size" do tuple1 = {1, 2, 3} tuple2 = {1, 2} (tuple1 <=> tuple2).should eq(1) end it "does types" do tuple = {1, 'a', "hello"} tuple.class.types.to_s.should eq("{Int32, Char, String}") end it "does ===" do ({1, 2} === {1, 2}).should be_true ({1, 2} === {1, 3}).should be_false ({1, 2, 3} === {1, 2}).should be_false ({/o+/, "bar"} === {"fox", "bar"}).should be_true ({1, 2} === nil).should be_false end describe "#to_a" do describe "without block" do it "basic" do ary = {1, 'a', true}.to_a ary.should eq([1, 'a', true]) ary.size.should eq(3) end it "empty" do ary = Tuple.new.to_a ary.size.should eq(0) end end describe "with block" do it "basic" do {-1, -2, -3}.to_a(&.abs).should eq [1, 2, 3] end it "different type" do {1, 2, true}.to_a(&.to_s).should eq ["1", "2", "true"] end end end # Tuple#to_static_array don't compile on aarch64-darwin and # aarch64-linux-musl due to a codegen error caused by LLVM < 13.0.0. # See https://github.com/crystal-lang/crystal/issues/11358 for details. {% unless compare_versions(Crystal::LLVM_VERSION, "13.0.0") < 0 && flag?(:aarch64) && (flag?(:musl) || flag?(:darwin) || flag?(:android)) %} it "#to_static_array" do ary = {1, 'a', true}.to_static_array ary.should be_a(StaticArray(Int32 | Char | Bool, 3)) ary.should eq(StaticArray[1, 'a', true]) ary.size.should eq(3) ary = Tuple.new.to_static_array ary.should be_a(StaticArray(NoReturn, 0)) ary.size.should eq(0) ary = Tuple(String | Int32).new(1).to_static_array ary.should be_a(StaticArray(String | Int32, 1)) ary.should eq StaticArray[1.as(String | Int32)] end {% end %} end