From d3a08ede3f87ed07a09de4a1e9bf178f01216a3e Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Wed, 10 Feb 2021 05:05:51 +0800 Subject: [PATCH 1/3] Support Tuple#[](Range) with compile-time range literals --- spec/compiler/codegen/tuple_spec.cr | 172 +++++++++ spec/compiler/semantic/tuple_spec.cr | 332 +++++++++++++++--- src/compiler/crystal/codegen/primitives.cr | 22 +- src/compiler/crystal/semantic/ast.cr | 4 +- src/compiler/crystal/semantic/call.cr | 55 ++- src/compiler/crystal/semantic/main_visitor.cr | 16 +- src/compiler/crystal/types.cr | 4 +- src/tuple.cr | 35 ++ 8 files changed, 570 insertions(+), 70 deletions(-) diff --git a/spec/compiler/codegen/tuple_spec.cr b/spec/compiler/codegen/tuple_spec.cr index c83bae267512..5b70e68bf70c 100644 --- a/spec/compiler/codegen/tuple_spec.cr +++ b/spec/compiler/codegen/tuple_spec.cr @@ -25,6 +25,169 @@ describe "Code gen: tuple" do run("{'a', 42}[2]? || 84").to_i.should eq(84) end + it "codegens tuple metaclass [0]" do + run("Tuple(Int32, Char)[0].is_a?(Int32.class)").to_b.should be_true + end + + it "codegens tuple metaclass [1]" do + run("Tuple(Int32, Char)[1].is_a?(Char.class)").to_b.should be_true + end + + it "codegens tuple metaclass [2]?" do + run("Tuple(Int32, Char)[2]?.nil?").to_b.should be_true + end + + it "codegens tuple [0..0]" do + run(" + #{range_new} + + val = {1, true}[0..0] + val.is_a?(Tuple(Int32)) && val[0] == 1 + ").to_b.should be_true + end + + it "codegens tuple [0..1]" do + run(" + #{range_new} + + val = {1, true}[0..1] + val.is_a?(Tuple(Int32, Bool)) && val[0] == 1 && val[1] == true + ").to_b.should be_true + end + + it "codegens tuple [0..2]" do + run(" + #{range_new} + + val = {1, true}[0..2] + val.is_a?(Tuple(Int32, Bool)) && val[0] == 1&& val[1] == true + ").to_b.should be_true + end + + it "codegens tuple [1..1]" do + run(" + #{range_new} + + val = {1, true}[1..1] + val.is_a?(Tuple(Bool)) && val[0] == true + ").to_b.should be_true + end + + it "codegens tuple [1..0]" do + run(" + #{range_new} + + def empty(*args) + args + end + + {1, true}[1..0].is_a?(typeof(empty)) + ").to_b.should be_true + end + + it "codegens tuple [2..2]" do + run(" + #{range_new} + + def empty(*args) + args + end + + {1, true}[2..2].is_a?(typeof(empty)) + ").to_b.should be_true + end + + it "codegens tuple [0..0]?" do + run(" + #{range_new} + + val = {1, true}[0..0]? + val.is_a?(Tuple(Int32)) && val[0] == 1 + ").to_b.should be_true + end + + it "codegens tuple [0..1]?" do + run(" + #{range_new} + + val = {1, true}[0..1]? + val.is_a?(Tuple(Int32, Bool)) && val[0] == 1 && val[1] == true + ").to_b.should be_true + end + + it "codegens tuple [0..2]?" do + run(" + #{range_new} + + val = {1, true}[0..2]? + val.is_a?(Tuple(Int32, Bool)) && val[0] == 1&& val[1] == true + ").to_b.should be_true + end + + it "codegens tuple [1..1]?" do + run(" + #{range_new} + + val = {1, true}[1..1]? + val.is_a?(Tuple(Bool)) && val[0] == true + ").to_b.should be_true + end + + it "codegens tuple [1..0]?" do + run(" + #{range_new} + + def empty(*args) + args + end + + {1, true}[1..0]?.is_a?(typeof(empty)) + ").to_b.should be_true + end + + it "codegens tuple [2..2]?" do + run(" + #{range_new} + + def empty(*args) + args + end + + {1, true}[2..2]?.is_a?(typeof(empty)) + ").to_b.should be_true + end + + it "codegens tuple [3..2]?" do + run("#{range_new}; {1, true}[3..2]?.nil?").to_b.should be_true + end + + it "codegens tuple [-3..2]?" do + run("#{range_new}; {1, true}[-3..2]?.nil?").to_b.should be_true + end + + it "codegens tuple metaclass [0..0]" do + run("#{range_new}; Tuple(Int32, Char)[0..0].is_a?(Tuple(Int32).class)").to_b.should be_true + end + + it "codegens tuple metaclass [0..1]" do + run("#{range_new}; Tuple(Int32, Char)[0..1].is_a?(Tuple(Int32, Char).class)").to_b.should be_true + end + + it "codegens tuple metaclass [1..0]" do + run(" + #{range_new} + + def empty(*args) + args.class + end + + Tuple(Int32, Char)[1..0].is_a?(typeof(empty))").to_b.should be_true + end + + it "codegens tuple metaclass [3..2]?" do + run("#{range_new}; Tuple(Int32, Char)[3..2]?.nil?").to_b.should be_true + end + it "passed tuple to def" do run(" def foo(t) @@ -365,3 +528,12 @@ describe "Code gen: tuple" do )) end end + +private def range_new + %( + struct Range(B, E) + def initialize(@begin : B, @end : E, @exclusive : Bool = false) + end + end + ) +end diff --git a/spec/compiler/semantic/tuple_spec.cr b/spec/compiler/semantic/tuple_spec.cr index 59c0df3f3e47..cd7097c439b5 100644 --- a/spec/compiler/semantic/tuple_spec.cr +++ b/spec/compiler/semantic/tuple_spec.cr @@ -13,76 +13,293 @@ describe "Semantic: tuples" do assert_type("{1}; {1, 2}") { tuple_of([int32, int32] of TypeVar) } end - it "types tuple [0]" do - assert_type("{1, 'a'}[0]") { int32 } - end + describe "#[](NumberLiteral)" do + it "types, inbound index" do + assert_type("{1, 'a'}[0]") { int32 } + assert_type("{1, 'a'}[1]") { char } - it "types tuple [1]" do - assert_type("{1, 'a'}[1]") { char } - end + assert_type("{1, 'a'}[-1]") { char } + assert_type("{1, 'a'}[-2]") { int32 } + end - it "types tuple [-1]" do - assert_type("{1, 'a'}[-1]") { char } - end + it "types, inbound index, nilable" do + assert_type("{1, 'a'}[0]?") { int32 } + assert_type("{1, 'a'}[1]?") { char } - it "types tuple [-2]" do - assert_type("{1, 'a'}[-2]") { int32 } - end + assert_type("{1, 'a'}[-1]?") { char } + assert_type("{1, 'a'}[-2]?") { int32 } + end - it "types tuple [0]?" do - assert_type("{1, 'a'}[0]?") { int32 } - end + it "types, out of bound, nilable" do + assert_type("{1, 'a'}[2]?") { nil_type } + assert_type("{1, 'a'}[-3]?") { nil_type } - it "types tuple [1]?" do - assert_type("{1, 'a'}[1]?") { char } - end + assert_type(%( + def tuple(*args) + args + end - it "types tuple [2]?" do - assert_type("{1, 'a'}[2]?") { nil_type } - end + tuple()[0]? + )) { nil_type } + end - it "types tuple [-1]?" do - assert_type("{1, 'a'}[-1]?") { char } - end + it "types, metaclass index" do + assert_type("{1, 'a'}.class[0]") { int32.metaclass } + assert_type("{1, 'a'}.class[1]") { char.metaclass } - it "types tuple [-2]?" do - assert_type("{1, 'a'}[-2]?") { int32 } - end + assert_type("{1, 'a'}.class[-1]") { char.metaclass } + assert_type("{1, 'a'}.class[-2]") { int32.metaclass } + end - it "types tuple [-3]?" do - assert_type("{1, 'a'}[-3]?") { nil_type } - end + it "gives error when indexing out of range" do + assert_error "{1, 'a'}[2]", + "index out of bounds for Tuple(Int32, Char) (2 not in -2..1)" + end - it "types tuple metaclass [0]" do - assert_type("{1, 'a'}.class[0]") { int32.metaclass } - end + it "gives error when indexing out of range on empty tuple" do + assert_error %( + def tuple(*args) + args + end - it "types tuple metaclass [1]" do - assert_type("{1, 'a'}.class[1]") { char.metaclass } + tuple()[0] + ), + "index '0' out of bounds for empty tuple" + end end - it "types tuple metaclass [-1]" do - assert_type("{1, 'a'}.class[-1]") { char.metaclass } - end + describe "#[](RangeLiteral)" do + it "types, inbound begin" do + assert_type(%(#{range_new}; {1, 'a'}[0..-3])) { tuple_of([] of Type) } + assert_type(%(#{range_new}; {1, 'a'}[0..-2])) { tuple_of([int32]) } + assert_type(%(#{range_new}; {1, 'a'}[0..-1])) { tuple_of([int32, char]) } + assert_type(%(#{range_new}; {1, 'a'}[0..0])) { tuple_of([int32]) } + assert_type(%(#{range_new}; {1, 'a'}[0..1])) { tuple_of([int32, char]) } + assert_type(%(#{range_new}; {1, 'a'}[0..2])) { tuple_of([int32, char]) } + + assert_type(%(#{range_new}; {1, 'a'}[1..-3])) { tuple_of([] of Type) } + assert_type(%(#{range_new}; {1, 'a'}[1..-2])) { tuple_of([] of Type) } + assert_type(%(#{range_new}; {1, 'a'}[1..-1])) { tuple_of([char]) } + assert_type(%(#{range_new}; {1, 'a'}[1..0])) { tuple_of([] of Type) } + assert_type(%(#{range_new}; {1, 'a'}[1..1])) { tuple_of([char]) } + assert_type(%(#{range_new}; {1, 'a'}[1..2])) { tuple_of([char]) } + + assert_type(%(#{range_new}; {1, 'a'}[2..-3])) { tuple_of([] of Type) } + assert_type(%(#{range_new}; {1, 'a'}[2..-2])) { tuple_of([] of Type) } + assert_type(%(#{range_new}; {1, 'a'}[2..-1])) { tuple_of([] of Type) } + assert_type(%(#{range_new}; {1, 'a'}[2..0])) { tuple_of([] of Type) } + assert_type(%(#{range_new}; {1, 'a'}[2..1])) { tuple_of([] of Type) } + assert_type(%(#{range_new}; {1, 'a'}[2..2])) { tuple_of([] of Type) } + + assert_type(%(#{range_new}; {1, 'a'}[-1..-3])) { tuple_of([] of Type) } + assert_type(%(#{range_new}; {1, 'a'}[-1..-2])) { tuple_of([] of Type) } + assert_type(%(#{range_new}; {1, 'a'}[-1..-1])) { tuple_of([char]) } + assert_type(%(#{range_new}; {1, 'a'}[-1..0])) { tuple_of([] of Type) } + assert_type(%(#{range_new}; {1, 'a'}[-1..1])) { tuple_of([char]) } + assert_type(%(#{range_new}; {1, 'a'}[-1..2])) { tuple_of([char]) } + + assert_type(%(#{range_new}; {1, 'a'}[-2..-3])) { tuple_of([] of Type) } + assert_type(%(#{range_new}; {1, 'a'}[-2..-2])) { tuple_of([int32]) } + assert_type(%(#{range_new}; {1, 'a'}[-2..-1])) { tuple_of([int32, char]) } + assert_type(%(#{range_new}; {1, 'a'}[-2..0])) { tuple_of([int32]) } + assert_type(%(#{range_new}; {1, 'a'}[-2..1])) { tuple_of([int32, char]) } + assert_type(%(#{range_new}; {1, 'a'}[-2..2])) { tuple_of([int32, char]) } + + assert_type(%( + #{range_new} + + def tuple(*args) + args + end - it "types tuple metaclass [-2]" do - assert_type("{1, 'a'}.class[-2]") { int32.metaclass } - end + tuple()[0..0] + )) { tuple_of([] of Type) } + end - it "gives error when indexing out of range" do - assert_error "{1, 'a'}[2]", - "index out of bounds for Tuple(Int32, Char) (2 not in -2..1)" - end + it "types, inbound begin, end-less" do + assert_type(%(#{range_new}; {1, 'a'}[0..])) { tuple_of([int32, char]) } + assert_type(%(#{range_new}; {1, 'a'}[1..])) { tuple_of([char]) } + assert_type(%(#{range_new}; {1, 'a'}[2..])) { tuple_of([] of Type) } + assert_type(%(#{range_new}; {1, 'a'}[-1..])) { tuple_of([char]) } + assert_type(%(#{range_new}; {1, 'a'}[-2..])) { tuple_of([int32, char]) } - it "gives error when indexing out of range on empty tuple" do - assert_error %( - def tuple(*args) - args - end + assert_type(%( + #{range_new} - tuple()[0] - ), - "index '0' out of bounds for empty tuple" + def tuple(*args) + args + end + + tuple()[0..] + )) { tuple_of([] of Type) } + end + + it "types, begin-less" do + assert_type(%(#{range_new}; {1, 'a'}[..0])) { tuple_of([int32]) } + assert_type(%(#{range_new}; {1, 'a'}[..1])) { tuple_of([int32, char]) } + assert_type(%(#{range_new}; {1, 'a'}[..2])) { tuple_of([int32, char]) } + assert_type(%(#{range_new}; {1, 'a'}[..-3])) { tuple_of([] of Type) } + assert_type(%(#{range_new}; {1, 'a'}[..-2])) { tuple_of([int32]) } + assert_type(%(#{range_new}; {1, 'a'}[..-1])) { tuple_of([int32, char]) } + + assert_type(%( + #{range_new} + + def tuple(*args) + args + end + + tuple()[..0] + )) { tuple_of([] of Type) } + end + + it "types, begin-less, end-less" do + assert_type(%(#{range_new}; {1, 'a'}[..])) { tuple_of([int32, char]) } + + assert_type(%( + #{range_new} + + def tuple(*args) + args + end + + tuple()[..] + )) { tuple_of([] of Type) } + end + + it "types, exclusive range" do + assert_type(%(#{range_new}; {1, 'a'}[0...-2])) { tuple_of([] of Type) } + assert_type(%(#{range_new}; {1, 'a'}[0...-1])) { tuple_of([int32]) } + assert_type(%(#{range_new}; {1, 'a'}[0...0])) { tuple_of([] of Type) } + assert_type(%(#{range_new}; {1, 'a'}[0...1])) { tuple_of([int32]) } + assert_type(%(#{range_new}; {1, 'a'}[0...2])) { tuple_of([int32, char]) } + assert_type(%(#{range_new}; {1, 'a'}[0...3])) { tuple_of([int32, char]) } + + assert_type(%(#{range_new}; {1, 'a'}[1...-2])) { tuple_of([] of Type) } + assert_type(%(#{range_new}; {1, 'a'}[1...-1])) { tuple_of([] of Type) } + assert_type(%(#{range_new}; {1, 'a'}[1...0])) { tuple_of([] of Type) } + assert_type(%(#{range_new}; {1, 'a'}[1...1])) { tuple_of([] of Type) } + assert_type(%(#{range_new}; {1, 'a'}[1...2])) { tuple_of([char]) } + assert_type(%(#{range_new}; {1, 'a'}[1...3])) { tuple_of([char]) } + + assert_type(%(#{range_new}; {1, 'a'}[2...-2])) { tuple_of([] of Type) } + assert_type(%(#{range_new}; {1, 'a'}[2...-1])) { tuple_of([] of Type) } + assert_type(%(#{range_new}; {1, 'a'}[2...0])) { tuple_of([] of Type) } + assert_type(%(#{range_new}; {1, 'a'}[2...1])) { tuple_of([] of Type) } + assert_type(%(#{range_new}; {1, 'a'}[2...2])) { tuple_of([] of Type) } + assert_type(%(#{range_new}; {1, 'a'}[2...3])) { tuple_of([] of Type) } + + assert_type(%(#{range_new}; {1, 'a'}[-1...-2])) { tuple_of([] of Type) } + assert_type(%(#{range_new}; {1, 'a'}[-1...-1])) { tuple_of([] of Type) } + assert_type(%(#{range_new}; {1, 'a'}[-1...0])) { tuple_of([] of Type) } + assert_type(%(#{range_new}; {1, 'a'}[-1...1])) { tuple_of([] of Type) } + assert_type(%(#{range_new}; {1, 'a'}[-1...2])) { tuple_of([char]) } + assert_type(%(#{range_new}; {1, 'a'}[-1...3])) { tuple_of([char]) } + + assert_type(%(#{range_new}; {1, 'a'}[-2...-2])) { tuple_of([] of Type) } + assert_type(%(#{range_new}; {1, 'a'}[-2...-1])) { tuple_of([int32]) } + assert_type(%(#{range_new}; {1, 'a'}[-2...0])) { tuple_of([] of Type) } + assert_type(%(#{range_new}; {1, 'a'}[-2...1])) { tuple_of([int32]) } + assert_type(%(#{range_new}; {1, 'a'}[-2...2])) { tuple_of([int32, char]) } + assert_type(%(#{range_new}; {1, 'a'}[-2...3])) { tuple_of([int32, char]) } + end + + it "types, inbound begin, nilable" do + assert_type(%(#{range_new}; {1, 'a'}[0..-3]?)) { tuple_of([] of Type) } + assert_type(%(#{range_new}; {1, 'a'}[0..-2]?)) { tuple_of([int32]) } + assert_type(%(#{range_new}; {1, 'a'}[0..-1]?)) { tuple_of([int32, char]) } + assert_type(%(#{range_new}; {1, 'a'}[0..0]?)) { tuple_of([int32]) } + assert_type(%(#{range_new}; {1, 'a'}[0..1]?)) { tuple_of([int32, char]) } + assert_type(%(#{range_new}; {1, 'a'}[0..2]?)) { tuple_of([int32, char]) } + + assert_type(%(#{range_new}; {1, 'a'}[1..-3]?)) { tuple_of([] of Type) } + assert_type(%(#{range_new}; {1, 'a'}[1..-2]?)) { tuple_of([] of Type) } + assert_type(%(#{range_new}; {1, 'a'}[1..-1]?)) { tuple_of([char]) } + assert_type(%(#{range_new}; {1, 'a'}[1..0]?)) { tuple_of([] of Type) } + assert_type(%(#{range_new}; {1, 'a'}[1..1]?)) { tuple_of([char]) } + assert_type(%(#{range_new}; {1, 'a'}[1..2]?)) { tuple_of([char]) } + + assert_type(%(#{range_new}; {1, 'a'}[2..-3]?)) { tuple_of([] of Type) } + assert_type(%(#{range_new}; {1, 'a'}[2..-2]?)) { tuple_of([] of Type) } + assert_type(%(#{range_new}; {1, 'a'}[2..-1]?)) { tuple_of([] of Type) } + assert_type(%(#{range_new}; {1, 'a'}[2..0]?)) { tuple_of([] of Type) } + assert_type(%(#{range_new}; {1, 'a'}[2..1]?)) { tuple_of([] of Type) } + assert_type(%(#{range_new}; {1, 'a'}[2..2]?)) { tuple_of([] of Type) } + + assert_type(%(#{range_new}; {1, 'a'}[-1..-3]?)) { tuple_of([] of Type) } + assert_type(%(#{range_new}; {1, 'a'}[-1..-2]?)) { tuple_of([] of Type) } + assert_type(%(#{range_new}; {1, 'a'}[-1..-1]?)) { tuple_of([char]) } + assert_type(%(#{range_new}; {1, 'a'}[-1..0]?)) { tuple_of([] of Type) } + assert_type(%(#{range_new}; {1, 'a'}[-1..1]?)) { tuple_of([char]) } + assert_type(%(#{range_new}; {1, 'a'}[-1..2]?)) { tuple_of([char]) } + + assert_type(%(#{range_new}; {1, 'a'}[-2..-3]?)) { tuple_of([] of Type) } + assert_type(%(#{range_new}; {1, 'a'}[-2..-2]?)) { tuple_of([int32]) } + assert_type(%(#{range_new}; {1, 'a'}[-2..-1]?)) { tuple_of([int32, char]) } + assert_type(%(#{range_new}; {1, 'a'}[-2..0]?)) { tuple_of([int32]) } + assert_type(%(#{range_new}; {1, 'a'}[-2..1]?)) { tuple_of([int32, char]) } + assert_type(%(#{range_new}; {1, 'a'}[-2..2]?)) { tuple_of([int32, char]) } + + assert_type(%( + #{range_new} + + def tuple(*args) + args + end + + tuple()[0..0]? + )) { tuple_of([] of Type) } + end + + it "types, out of bound begin, nilable" do + assert_type(%(#{range_new}; {1, 'a'}[-3..0]?)) { nil_type } + assert_type(%(#{range_new}; {1, 'a'}[3..2]?)) { nil_type } + + assert_type(%( + #{range_new} + + def tuple(*args) + args + end + + tuple()[1..0]? + )) { nil_type } + end + + it "types, metaclass index" do + assert_type(%(#{range_new}; {1, 'a'}.class[0..1])) { tuple_of([int32, char]).metaclass } + assert_type(%(#{range_new}; {1, 'a'}.class[1..2])) { tuple_of([char]).metaclass } + assert_type(%(#{range_new}; {1, 'a'}.class[1..-2])) { tuple_of([] of Type).metaclass } + assert_type(%(#{range_new}; {1, 'a'}.class[-2..-1])) { tuple_of([int32, char]).metaclass } + assert_type(%(#{range_new}; {1, 'a'}.class[-1..0])) { tuple_of([] of Type).metaclass } + end + + it "gives error when begin index is out of range" do + assert_error %( + #{range_new} + + {1, 'a'}[3..0] + ), + "begin index out of bounds for Tuple(Int32, Char) (3 not in -2..2)" + + assert_error %( + #{range_new} + + {1, 'a'}[-3..0] + ), + "begin index out of bounds for Tuple(Int32, Char) (-3 not in -2..2)" + + assert_error %( + #{range_new} + + def tuple(*args) + args + end + + tuple()[1..0] + ), + "begin index out of bounds for Tuple() (1 not in 0..0)" + end end it "can name a tuple type" do @@ -331,3 +548,12 @@ describe "Semantic: tuples" do )) { nil_type } end end + +private def range_new + %( + struct Range(B, E) + def initialize(@begin : B, @end : E, @exclusive : Bool = false) + end + end + ) +end diff --git a/src/compiler/crystal/codegen/primitives.cr b/src/compiler/crystal/codegen/primitives.cr index 44f601b273c1..386d57860257 100644 --- a/src/compiler/crystal/codegen/primitives.cr +++ b/src/compiler/crystal/codegen/primitives.cr @@ -1076,7 +1076,27 @@ class Crystal::CodeGenVisitor codegen_tuple_indexer(context.type, call_args[0], index) end - def codegen_tuple_indexer(type, value, index) + def codegen_tuple_indexer(type, value, index : Range) + case type + when TupleInstanceType + tuple_types = type.tuple_types[index].map &.as(Type) + allocate_tuple(@program.tuple_of(tuple_types).as(TupleInstanceType)) do |tuple_type, i| + ptr = aggregate_index value, index.begin + i + tuple_value = to_lhs ptr, tuple_type + {tuple_type, tuple_value} + end + else + type = type.instance_type + case type + when TupleInstanceType + type_id(@program.tuple_of(type.tuple_types[index].map &.as(Type)).metaclass) + else + raise "BUG: unsupported codegen for tuple_indexer" + end + end + end + + def codegen_tuple_indexer(type, value, index : Int32) case type when TupleInstanceType ptr = aggregate_index value, index diff --git a/src/compiler/crystal/semantic/ast.cr b/src/compiler/crystal/semantic/ast.cr index 8e8f437849f6..ff45fc94caa4 100644 --- a/src/compiler/crystal/semantic/ast.cr +++ b/src/compiler/crystal/semantic/ast.cr @@ -57,9 +57,9 @@ module Crystal # Fictitious node to represent a tuple indexer class TupleIndexer < Primitive - getter index : Int32 + getter index : Int32 | Range(Int32, Int32) - def initialize(@index : Int32) + def initialize(@index) super("tuple_indexer_known_index") end diff --git a/src/compiler/crystal/semantic/call.cr b/src/compiler/crystal/semantic/call.cr index 321e83daf176..7f83635a6158 100644 --- a/src/compiler/crystal/semantic/call.cr +++ b/src/compiler/crystal/semantic/call.cr @@ -500,17 +500,56 @@ class Crystal::Call index = arg.value.to_i index += instance_type.size if index < 0 in_bounds = (0 <= index < instance_type.size) - if nilable || in_bounds - indexer_def = yield instance_type, (in_bounds ? index : -1) - indexer_match = Match.new(indexer_def, arg_types, MatchContext.new(owner, owner)) - return Matches.new([indexer_match] of Match, true) - elsif instance_type.size == 0 - raise "index '#{arg}' out of bounds for empty tuple" + unless in_bounds + unless nilable + raise "index '#{arg}' out of bounds for empty tuple" if instance_type.size == 0 + raise "index out of bounds for #{owner} (#{arg} not in #{-instance_type.size}..#{instance_type.size - 1})" + end + index = -1 + end + elsif arg.is_a?(RangeLiteral) + from = arg.from + if from.is_a?(NumberLiteral) && from.kind == :i32 + from_index = from.value.to_i + from_index += instance_type.size if from_index < 0 + in_bounds = (0 <= from_index <= instance_type.size) + if !in_bounds && !nilable + raise "begin index out of bounds for #{owner} (#{from} not in #{-instance_type.size}..#{instance_type.size})" + end + elsif from.is_a?(Nop) + from_index = 0 + in_bounds = true else - raise "index out of bounds for #{owner} (#{arg} not in #{-instance_type.size}..#{instance_type.size - 1})" + return nil end + + to = arg.to + if to.is_a?(NumberLiteral) && to.kind == :i32 + to_index = to.value.to_i + to_index += instance_type.size if to_index < 0 + to_index = (to_index - (arg.exclusive? ? 1 : 0)).clamp(-1, instance_type.size - 1) + elsif to.is_a?(Nop) + to_index = instance_type.size - 1 + else + return nil + end + + if in_bounds + if from_index <= to_index + index = (from_index..to_index) + else + index = (0...0) + end + else + index = -1 + end + else + return nil end - nil + + indexer_def = yield instance_type, index + indexer_match = Match.new(indexer_def, arg_types, MatchContext.new(owner, owner)) + Matches.new([indexer_match] of Match, true) end def named_tuple_indexer_helper(args, arg_types, owner, instance_type, nilable) diff --git a/src/compiler/crystal/semantic/main_visitor.cr b/src/compiler/crystal/semantic/main_visitor.cr index 5e27e234a6f9..5cd6f5b1f778 100644 --- a/src/compiler/crystal/semantic/main_visitor.cr +++ b/src/compiler/crystal/semantic/main_visitor.cr @@ -2895,13 +2895,21 @@ module Crystal def visit(node : TupleIndexer) scope = @scope if scope.is_a?(TupleInstanceType) - node.type = scope.tuple_types[node.index].as(Type) + if (index = node.index).is_a?(Range) + node.type = @program.tuple_of(scope.tuple_types[index].map &.as(Type)) + else + node.type = scope.tuple_types[index].as(Type) + end elsif scope.is_a?(NamedTupleInstanceType) - node.type = scope.entries[node.index].type + node.type = scope.entries[node.index.as(Int32)].type elsif scope && (instance_type = scope.instance_type).is_a?(TupleInstanceType) - node.type = instance_type.tuple_types[node.index].as(Type).metaclass + if (index = node.index).is_a?(Range) + node.type = @program.tuple_of(instance_type.tuple_types[index].map &.as(Type)).metaclass + else + node.type = instance_type.tuple_types[index].as(Type).metaclass + end elsif scope && (instance_type = scope.instance_type).is_a?(NamedTupleInstanceType) - node.type = instance_type.entries[node.index].type.metaclass + node.type = instance_type.entries[node.index.as(Int32)].type.metaclass else node.raise "unsupported TupleIndexer scope" end diff --git a/src/compiler/crystal/types.cr b/src/compiler/crystal/types.cr index 3db6f52478cc..763afb4625d5 100644 --- a/src/compiler/crystal/types.cr +++ b/src/compiler/crystal/types.cr @@ -2332,12 +2332,12 @@ module Crystal end def tuple_indexer(index) - indexers = @tuple_indexers ||= {} of Int32 => Def + indexers = @tuple_indexers ||= {} of (Int32 | Range(Int32, Int32)) => Def tuple_indexer(indexers, index) end def tuple_metaclass_indexer(index) - indexers = @tuple_metaclass_indexers ||= {} of Int32 => Def + indexers = @tuple_metaclass_indexers ||= {} of (Int32 | Range(Int32, Int32)) => Def tuple_indexer(indexers, index) end diff --git a/src/tuple.cr b/src/tuple.cr index b7f494214458..47e33b51b962 100644 --- a/src/tuple.cr +++ b/src/tuple.cr @@ -23,6 +23,15 @@ # a value whose type is the union of all the types in the tuple, and might raise # `IndexError`. # +# Indexing with a range literal known at compile-time is also allowed, and the +# returned value will have the correct sub-tuple type: +# +# ``` +# tuple = {1, "hello", 'x'} # Tuple(Int32, String, Char) +# sub = tuple[0..1] # => {1, "hello"} +# typeof(sub) # => Tuple(Int32, String) +# ``` +# # Tuples are the preferred way to return fixed-size multiple return # values because no memory is needed to be allocated for them: # @@ -175,6 +184,32 @@ struct Tuple at(index) { nil } end + # Returns all elements that are within the given *range*. *range* must be a + # range literal whose value is known at compile-time. + # + # Negative indices count backward from the end of the array (-1 is the last + # element). Additionally, an empty array is returned when the starting index + # for an element range is at the end of the array. + # + # Raises a compile-time error if `range.begin` is out of range. + # + # ``` + # tuple = {1, "hello", 'x'} + # tuple[0..1] # => {1, "hello"} + # tuple[-2..] # => {"hello", 'x'} + # tuple[...1] # => {1} + # tuple[4..] # Error: begin index out of bounds for Tuple(Int32, Char, Array(Int32), String) (5 not in -4..4) + # + # i = 0 + # tuple[i..2] # Error: Tuple#[](Range) can only be called with range literals known at compile-time + # + # i = 0..2 + # tuple[i] # Error: Tuple#[](Range) can only be called with range literals known at compile-time + # ``` + def [](range : Range) + {% raise "Tuple#[](Range) can only be called with range literals known at compile-time" %} + end + # Returns the element at the given *index* or raises IndexError if out of bounds. # # ``` From aac78ca32eae80a19f983108e43394f8d00a0a95 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Wed, 10 Feb 2021 05:55:51 +0800 Subject: [PATCH 2/3] crystal tool format --- src/tuple.cr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tuple.cr b/src/tuple.cr index 47e33b51b962..075ecd836487 100644 --- a/src/tuple.cr +++ b/src/tuple.cr @@ -204,7 +204,7 @@ struct Tuple # tuple[i..2] # Error: Tuple#[](Range) can only be called with range literals known at compile-time # # i = 0..2 - # tuple[i] # Error: Tuple#[](Range) can only be called with range literals known at compile-time + # tuple[i] # Error: Tuple#[](Range) can only be called with range literals known at compile-time # ``` def [](range : Range) {% raise "Tuple#[](Range) can only be called with range literals known at compile-time" %} From 776a9159e98e4d1a40789d56b16a5843ba1a21a5 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Wed, 10 Feb 2021 22:56:14 +0800 Subject: [PATCH 3/3] Apply suggestions --- src/compiler/crystal/semantic/ast.cr | 2 +- src/compiler/crystal/semantic/main_visitor.cr | 10 ++++++---- src/compiler/crystal/types.cr | 6 ++++-- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/compiler/crystal/semantic/ast.cr b/src/compiler/crystal/semantic/ast.cr index ff45fc94caa4..1ce97c89995e 100644 --- a/src/compiler/crystal/semantic/ast.cr +++ b/src/compiler/crystal/semantic/ast.cr @@ -57,7 +57,7 @@ module Crystal # Fictitious node to represent a tuple indexer class TupleIndexer < Primitive - getter index : Int32 | Range(Int32, Int32) + getter index : TupleInstanceType::Index def initialize(@index) super("tuple_indexer_known_index") diff --git a/src/compiler/crystal/semantic/main_visitor.cr b/src/compiler/crystal/semantic/main_visitor.cr index 5cd6f5b1f778..aff11961287f 100644 --- a/src/compiler/crystal/semantic/main_visitor.cr +++ b/src/compiler/crystal/semantic/main_visitor.cr @@ -2895,17 +2895,19 @@ module Crystal def visit(node : TupleIndexer) scope = @scope if scope.is_a?(TupleInstanceType) - if (index = node.index).is_a?(Range) + case index = node.index + in Range node.type = @program.tuple_of(scope.tuple_types[index].map &.as(Type)) - else + in Int32 node.type = scope.tuple_types[index].as(Type) end elsif scope.is_a?(NamedTupleInstanceType) node.type = scope.entries[node.index.as(Int32)].type elsif scope && (instance_type = scope.instance_type).is_a?(TupleInstanceType) - if (index = node.index).is_a?(Range) + case index = node.index + in Range node.type = @program.tuple_of(instance_type.tuple_types[index].map &.as(Type)).metaclass - else + in Int32 node.type = instance_type.tuple_types[index].as(Type).metaclass end elsif scope && (instance_type = scope.instance_type).is_a?(NamedTupleInstanceType) diff --git a/src/compiler/crystal/types.cr b/src/compiler/crystal/types.cr index 763afb4625d5..8ab9090b5bee 100644 --- a/src/compiler/crystal/types.cr +++ b/src/compiler/crystal/types.cr @@ -2321,6 +2321,8 @@ module Crystal # An instantiated tuple type, like Tuple(Char, Int32). class TupleInstanceType < GenericClassInstanceType + alias Index = Int32 | Range(Int32, Int32) + getter tuple_types : Array(Type) def initialize(program, @tuple_types) @@ -2332,12 +2334,12 @@ module Crystal end def tuple_indexer(index) - indexers = @tuple_indexers ||= {} of (Int32 | Range(Int32, Int32)) => Def + indexers = @tuple_indexers ||= {} of Index => Def tuple_indexer(indexers, index) end def tuple_metaclass_indexer(index) - indexers = @tuple_metaclass_indexers ||= {} of (Int32 | Range(Int32, Int32)) => Def + indexers = @tuple_metaclass_indexers ||= {} of Index => Def tuple_indexer(indexers, index) end