Skip to content

Commit

Permalink
Fix Slice.literal for multiple calls with identical signature (crys…
Browse files Browse the repository at this point in the history
  • Loading branch information
HertzDevil authored Sep 25, 2024
1 parent 6a81bb0 commit cde543a
Show file tree
Hide file tree
Showing 3 changed files with 69 additions and 47 deletions.
7 changes: 7 additions & 0 deletions spec/primitives/slice_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -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
4 changes: 3 additions & 1 deletion src/compiler/crystal/program.cr
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand Down
105 changes: 59 additions & 46 deletions src/compiler/crystal/semantic/main_visitor.cr
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -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)

Expand Down

0 comments on commit cde543a

Please sign in to comment.