diff --git a/spec/primitives/slice_spec.cr b/spec/primitives/slice_spec.cr index 546ae0de5ce1..98bea774df8b 100644 --- a/spec/primitives/slice_spec.cr +++ b/spec/primitives/slice_spec.cr @@ -12,6 +12,13 @@ describe "Primitives: Slice" do slice.to_a.should eq([0, 1, 4, 9, 16, 25] of {{ num }}) slice.read_only?.should be_true end + + # TODO: these should probably return the same pointers + pending_interpreted "creates multiple literals" do + slice1 = Slice({{ num }}).literal(1, 2, 3) + slice2 = Slice({{ num }}).literal(1, 2, 3) + slice1.should eq(slice2) + end {% end %} end end diff --git a/src/compiler/crystal/program.cr b/src/compiler/crystal/program.cr index b1cc99f0dfc6..c262a2d9770a 100644 --- a/src/compiler/crystal/program.cr +++ b/src/compiler/crystal/program.cr @@ -205,6 +205,8 @@ module Crystal types["Regex"] = @regex = NonGenericClassType.new self, self, "Regex", reference types["Range"] = range = @range = GenericClassType.new self, self, "Range", struct_t, ["B", "E"] range.struct = true + types["Slice"] = slice = @slice = GenericClassType.new self, self, "Slice", struct_t, ["T"] + slice.struct = true types["Exception"] = @exception = NonGenericClassType.new self, self, "Exception", reference @@ -528,7 +530,7 @@ module Crystal {% for name in %w(object no_return value number reference void nil bool char int int8 int16 int32 int64 int128 uint8 uint16 uint32 uint64 uint128 float float32 float64 string symbol pointer enumerable indexable - array static_array exception tuple named_tuple proc union enum range regex crystal + array static_array exception tuple named_tuple proc union enum range slice regex crystal packed_annotation thread_local_annotation no_inline_annotation always_inline_annotation naked_annotation returns_twice_annotation raises_annotation primitive_annotation call_convention_annotation diff --git a/src/compiler/crystal/semantic/main_visitor.cr b/src/compiler/crystal/semantic/main_visitor.cr index c33c64e893ff..ea5626c37f94 100644 --- a/src/compiler/crystal/semantic/main_visitor.cr +++ b/src/compiler/crystal/semantic/main_visitor.cr @@ -1313,6 +1313,10 @@ module Crystal if check_special_new_call(node, obj.type?) return false end + + if check_slice_literal_call(node, obj.type?) + return false + end end args.each &.accept(self) @@ -1567,6 +1571,60 @@ module Crystal false end + def check_slice_literal_call(node, obj_type) + return false unless obj_type + return false unless obj_type.metaclass? + + instance_type = obj_type.instance_type.remove_typedef + + if node.name == "literal" + case instance_type + when GenericClassType # Slice + return false unless instance_type == @program.slice + node.raise "TODO: implement slice_literal primitive for Slice without generic arguments" + when GenericClassInstanceType # Slice(T) + return false unless instance_type.generic_type == @program.slice + + element_type = instance_type.type_vars["T"].type + kind = case element_type + when IntegerType + element_type.kind + when FloatType + element_type.kind + else + node.raise "Only slice literals of primitive integer or float types can be created" + end + + node.args.each do |arg| + arg.raise "Expected NumberLiteral, got #{arg.class_desc}" unless arg.is_a?(NumberLiteral) + arg.accept self + arg.raise "Argument out of range for a Slice(#{element_type})" unless arg.representable_in?(element_type) + end + + # create the internal constant `$Slice:n` to hold the slice contents + const_name = "$Slice:#{@program.const_slices.size}" + const_value = Nop.new + const_value.type = @program.static_array_of(element_type, node.args.size) + const = Const.new(@program, @program, const_name, const_value) + @program.types[const_name] = const + @program.const_slices << Program::ConstSliceInfo.new(const_name, kind, node.args) + + # ::Slice.new(pointerof($Slice:n.@buffer), {{ args.size }}, read_only: true) + pointer_node = PointerOf.new(ReadInstanceVar.new(Path.new(const_name).at(node), "@buffer").at(node)).at(node) + size_node = NumberLiteral.new(node.args.size.to_s, :i32).at(node) + read_only_node = NamedArgument.new("read_only", BoolLiteral.new(true).at(node)).at(node) + expanded = Call.new(Path.global("Slice").at(node), "new", [pointer_node, size_node], named_args: [read_only_node]).at(node) + + expanded.accept self + node.bind_to expanded + node.expanded = expanded + return true + end + end + + false + end + # Rewrite: # # LibFoo::Struct.new arg0: value0, argN: value0 @@ -2308,7 +2366,7 @@ module Crystal when "pointer_new" visit_pointer_new node when "slice_literal" - visit_slice_literal node + node.raise "BUG: Slice literal should have been expanded" when "argc" # Already typed when "argv" @@ -2466,51 +2524,6 @@ module Crystal node.type = scope.instance_type end - def visit_slice_literal(node) - call = self.call.not_nil! - - case slice_type = scope.instance_type - when GenericClassType # Slice - call.raise "TODO: implement slice_literal primitive for Slice without generic arguments" - when GenericClassInstanceType # Slice(T) - element_type = slice_type.type_vars["T"].type - kind = case element_type - when IntegerType - element_type.kind - when FloatType - element_type.kind - else - call.raise "Only slice literals of primitive integer or float types can be created" - end - - call.args.each do |arg| - arg.raise "Expected NumberLiteral, got #{arg.class_desc}" unless arg.is_a?(NumberLiteral) - arg.raise "Argument out of range for a Slice(#{element_type})" unless arg.representable_in?(element_type) - end - - # create the internal constant `$Slice:n` to hold the slice contents - const_name = "$Slice:#{@program.const_slices.size}" - const_value = Nop.new - const_value.type = @program.static_array_of(element_type, call.args.size) - const = Const.new(@program, @program, const_name, const_value) - @program.types[const_name] = const - @program.const_slices << Program::ConstSliceInfo.new(const_name, kind, call.args) - - # ::Slice.new(pointerof($Slice:n.@buffer), {{ args.size }}, read_only: true) - pointer_node = PointerOf.new(ReadInstanceVar.new(Path.new(const_name).at(node), "@buffer").at(node)).at(node) - size_node = NumberLiteral.new(call.args.size.to_s, :i32).at(node) - read_only_node = NamedArgument.new("read_only", BoolLiteral.new(true).at(node)).at(node) - extra = Call.new(Path.global("Slice").at(node), "new", [pointer_node, size_node], named_args: [read_only_node]).at(node) - - extra.accept self - node.extra = extra - node.type = slice_type - call.expanded = extra - else - node.raise "BUG: Unknown scope for slice_literal primitive" - end - end - def visit_struct_or_union_set(node) scope = @scope.as(NonGenericClassType)