Skip to content

Commit

Permalink
Fix assignments in array literals (#10009)
Browse files Browse the repository at this point in the history
* Add public Array#unsafe_size=

This enables the compiler to expand array literals which currently
uses Array.build and introduces a new scope.

* Refactor array literal expander without block scope

Unwraps the behaviour of Array.build to avoid introducing an additional
scope which breaks assignments to local variables.

Also includes codegen specs for array-like, hash-like and hash literals
(which are not broken).

* Improve refactoring to use Array constructor only

This avoids having a publicly accessible setter for Array#size

* Revert "Add public Array#unsafe_size="

This reverts commit 3cc56eb.

* wip

* Add custom constructor Array.unsafe_build

This is more efficient than initializing a default value but requires
stdlib support.

* Fix specs

* Buffer pointer for unsafe_build

* Update src/compiler/crystal/semantic/literal_expander.cr

Co-authored-by: Brian J. Cardiff <[email protected]>

Co-authored-by: Brian J. Cardiff <[email protected]>
  • Loading branch information
straight-shoota and bcardiff authored Feb 18, 2021
1 parent 9700277 commit a80ed6d
Show file tree
Hide file tree
Showing 5 changed files with 68 additions and 19 deletions.
12 changes: 12 additions & 0 deletions spec/compiler/codegen/array_literal_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -176,4 +176,16 @@ describe "Code gen: array literal spec" do
custom.value
)).to_i.should eq(6)
end

it "creates typed array" do
run("require \"prelude\"; typeof([1, 2] of Int8)").to_string.should eq("Array(Int8)")
end

it "assignment in array literal works" do
run("require \"prelude\"; [a = 1]; a").to_i.should eq(1)
end

it "assignment in array-like literal works" do
run("require \"prelude\"; Array(Int32){a = 1}; a").to_i.should eq(1)
end
end
8 changes: 8 additions & 0 deletions spec/compiler/codegen/hash_literal_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -210,4 +210,12 @@ describe "Code gen: hash literal spec" do
custom.keys &* custom.values
)).to_i.should eq(90)
end

it "assignment in hash literal works" do
run("require \"prelude\"; {k = 1 => v = 2}; k + v").to_i.should eq(3)
end

it "assignment in hash-like literal works" do
run("require \"prelude\"; Hash(Int32, Int32){k = 1 => v = 2}; k + v").to_i.should eq(3)
end
end
16 changes: 14 additions & 2 deletions spec/compiler/normalize/array_literal_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,22 @@ describe "Normalize: array literal" do
end

it "normalizes non-empty with of" do
assert_expand "[1, 2] of Int", "::Array(Int).build(2) do |__temp_1|\n __temp_1[0] = 1\n __temp_1[1] = 2\n 2\nend"
assert_expand "[1, 2] of Int8", <<-CR
__temp_1 = ::Array(Int8).unsafe_build(2)
__temp_2 = __temp_1.to_unsafe
__temp_2[0] = 1
__temp_2[1] = 2
__temp_1
CR
end

it "normalizes non-empty without of" do
assert_expand "[1, 2]", "::Array(typeof(1, 2)).build(2) do |__temp_1|\n __temp_1[0] = 1\n __temp_1[1] = 2\n 2\nend"
assert_expand "[1, 2]", <<-CR
__temp_1 = ::Array(typeof(1, 2)).unsafe_build(2)
__temp_2 = __temp_1.to_unsafe
__temp_2[0] = 1
__temp_2[1] = 2
__temp_1
CR
end
end
14 changes: 13 additions & 1 deletion spec/compiler/semantic/array_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,27 @@ describe "Semantic: array" do
assert_type("require \"prelude\"; [1, 2.5]") { array_of(union_of int32, float64) }
end

it "types empty typed array literal of int" do
it "types empty typed array literal of int32" do
assert_type("require \"prelude\"; [] of Int32") { array_of(int32) }
end

it "types non-empty typed array literal of int" do
assert_type("require \"prelude\"; [1, 2, 3] of Int32") { array_of(int32) }
end

it "types non-empty typed array literal of int" do
assert_type("require \"prelude\"; [1, 2, 3] of Int8") { array_of(int8) }
end

it "types array literal size correctly" do
assert_type("require \"prelude\"; [1].size") { int32 }
end

it "types array literal" do
assert_type("require \"prelude\"; [1].size") { int32 }
end

it "assignment in array literal works (#3195)" do
assert_type("require \"prelude\"; [a = 1]; a") { int32 }
end
end
37 changes: 21 additions & 16 deletions src/compiler/crystal/semantic/literal_expander.cr
Original file line number Diff line number Diff line change
Expand Up @@ -28,32 +28,37 @@ module Crystal
# end
def expand(node : ArrayLiteral)
if node_of = node.of
if node.elements.size == 0
generic = Generic.new(Path.global("Array"), node_of).at(node)
call = Call.new(generic, "new").at(node)
return call
end

type_var = node_of
else
type_var = TypeOf.new(node.elements.clone)
end

capacity = node.elements.size

buffer = new_temp_var.at(node)
generic = Generic.new(Path.global("Array"), type_var).at(node)

exps = Array(ASTNode).new(node.elements.size + 1)
node.elements.each_with_index do |elem, i|
exps << Call.new(buffer.clone, "[]=", NumberLiteral.new(i).at(node), elem.clone).at(node)
end
exps << NumberLiteral.new(capacity).at(node)
block_body = Expressions.new(exps).at(node)
if capacity.zero?
Call.new(generic, "new").at(node)
else
ary_var = new_temp_var.at(node)

block = Block.new([buffer.clone], block_body).at(node)
ary_instance = Call.new(generic, "unsafe_build", args: [NumberLiteral.new(capacity).at(node)] of ASTNode).at(node)

generic = Generic.new(Path.global("Array"), type_var).at(node)
Call.new(generic, "build", args: [NumberLiteral.new(capacity).at(node)] of ASTNode, block: block).at(node)
buffer = Call.new(ary_var, "to_unsafe")
buffer_var = new_temp_var.at(node)

exps = Array(ASTNode).new(node.elements.size + 3)
exps << Assign.new(ary_var.clone, ary_instance).at(node)
exps << Assign.new(buffer_var, buffer).at(node)

node.elements.each_with_index do |elem, i|
exps << Call.new(buffer_var.clone, "[]=", NumberLiteral.new(i).at(node), elem.clone).at(node)
end

exps << ary_var.clone

Expressions.new(exps).at(node)
end
end

def expand_named(node : ArrayLiteral)
Expand Down

0 comments on commit a80ed6d

Please sign in to comment.