Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support Slice.literal with inferred element type #15529

Merged
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 58 additions & 0 deletions spec/compiler/semantic/primitives_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,64 @@ describe "Semantic: primitives" do
end
CRYSTAL

context "without element type" do
it "types primitive int literal" do
assert_type(<<-CRYSTAL) { generic_class "Slice", int32 }
#{def_slice_literal}
Slice.literal(0, 1, 4, 9)
CRYSTAL

assert_type(<<-CRYSTAL) { generic_class "Slice", uint8 }
#{def_slice_literal}
Slice.literal(1_u8, 2_u8)
CRYSTAL
end

it "types primitive float literal" do
assert_type(<<-CRYSTAL) { generic_class "Slice", float64 }
#{def_slice_literal}
Slice.literal(1.2, 3.4)
CRYSTAL

assert_type(<<-CRYSTAL) { generic_class "Slice", float32 }
#{def_slice_literal}
Slice.literal(5.6_f32)
CRYSTAL
end

it "errors if empty" do
assert_error <<-CRYSTAL, "Cannot create empty slice literal without element type"
#{def_slice_literal}
Slice.literal
CRYSTAL
end

it "errors if multiple element types are found" do
assert_error <<-CRYSTAL, "Too many element types for slice literal without generic argument: Int32, UInt8"
#{def_slice_literal}
Slice.literal(1, 2_u8)
CRYSTAL

assert_error <<-CRYSTAL, "Too many element types for slice literal without generic argument: Float32, Float64"
#{def_slice_literal}
Slice.literal(3.0f32, 4.0)
CRYSTAL
end

it "errors if element is not number literal" do
assert_error <<-CRYSTAL, "Expected NumberLiteral, got StringLiteral"
#{def_slice_literal}
Slice.literal("")
CRYSTAL

assert_error <<-CRYSTAL, "Expected NumberLiteral, got Var"
#{def_slice_literal}
x = 1
Slice.literal(x)
CRYSTAL
end
end

context "with element type" do
it "types primitive int literal" do
assert_type(<<-CRYSTAL) { generic_class "Slice", uint8 }
Expand Down
9 changes: 9 additions & 0 deletions spec/primitives/slice_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,14 @@ describe "Primitives: Slice" do
slice1.should eq(slice2)
end
{% end %}

{% for num, suffix in BUILTIN_NUMBER_SUFFIXES %}
pending_interpreted {{ "creates a read-only Slice of #{num}" }} do
slice = Slice.literal(1_{{ suffix.id }}, 2_{{ suffix.id }}, 3_{{ suffix.id }})
slice.should be_a(Slice({{ num }}))
slice.to_a.should eq([1, 2, 3] of {{ num }})
slice.read_only?.should be_true
end
{% end %}
end
end
14 changes: 14 additions & 0 deletions spec/support/number.cr
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,20 @@ BUILTIN_NUMBER_TYPES_LTE_64 =
[Int8, UInt8, Int16, UInt16, Int32, UInt32, Int64, UInt64, Float32, Float64]
BUILTIN_FLOAT_TYPES =
[Float32, Float64]
BUILTIN_NUMBER_SUFFIXES = {
Int8 => "i8",
Int16 => "i16",
Int32 => "i32",
Int64 => "i64",
Int128 => "i128",
UInt8 => "u8",
UInt16 => "u16",
UInt32 => "u32",
UInt64 => "u64",
UInt128 => "u128",
Float32 => "f32",
Float64 => "f64",
}

macro it_can_convert_between(a_types, b_types)
{% for a_type in a_types %}
Expand Down
96 changes: 56 additions & 40 deletions src/compiler/crystal/semantic/main_visitor.cr
Original file line number Diff line number Diff line change
Expand Up @@ -1574,55 +1574,71 @@ module Crystal
def check_slice_literal_call(node, obj_type)
return false unless obj_type
return false unless obj_type.metaclass?
return false unless node.name == "literal"

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
case instance_type
when GenericClassType # Slice
return false unless instance_type == @program.slice

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)
element_type = nil

node.args.each do |arg|
arg.raise "Expected NumberLiteral, got #{arg.class_desc}" unless arg.is_a?(NumberLiteral)
arg.accept self
element_type ||= arg.type
unless element_type == arg.type
arg.raise "Too many element types for slice literal without generic argument: #{element_type}, #{arg.type}"
end
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
unless element_type
node.raise "Cannot create empty slice literal without element type"
end
when GenericClassInstanceType # Slice(T)
return false unless instance_type.generic_type == @program.slice

element_type = instance_type.type_vars["T"].type

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
else
return false
end

false
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

# 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

true
end

# Rewrite:
Expand Down
11 changes: 8 additions & 3 deletions src/primitives.cr
Original file line number Diff line number Diff line change
Expand Up @@ -337,14 +337,19 @@ struct Slice(T)
# Constructs a read-only `Slice` constant from the given *args*. The slice
# contents are stored in the program's read-only data section.
#
# `T` must be one of the `Number::Primitive` types and cannot be a union. It
# also cannot be inferred. The *args* must all be number literals that fit
# into `T`'s range, as if they are autocasted into `T`.
# If `T` is specified, it must be one of the `Number::Primitive` types and
# cannot be a union. The *args* must all be number literals that fit into
# `T`'s range, as if they are autocasted into `T`.
#
# If `T` is not specified, it is inferred from *args*, which must all be
# number literals of the same type, and cannot be empty.
#
# ```
# x = Slice(UInt8).literal(0, 1, 4, 9, 16, 25)
# x # => Slice[0, 1, 4, 9, 16, 25]
# x.read_only? # => true
#
# Slice.literal(1_u8, 2_u8, 3_u8) # => Bytes[1, 2, 3]
# ```
@[Experimental("Slice literals are still under development. Join the discussion at [#2886](https://github.com/crystal-lang/crystal/issues/2886).")]
@[Primitive(:slice_literal)]
Expand Down