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

Require elements in 1-to-n assignments to match targets exactly #11145

2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ static ?= ## Enable static linking
O := .build
SOURCES := $(shell find src -name '*.cr')
SPEC_SOURCES := $(shell find spec -name '*.cr')
override FLAGS += $(if $(release),--release )$(if $(stats),--stats )$(if $(progress),--progress )$(if $(threads),--threads $(threads) )$(if $(debug),-d )$(if $(static),--static )$(if $(LDFLAGS),--link-flags="$(LDFLAGS)" )$(if $(target),--cross-compile --target $(target) )
override FLAGS += -D preview_multi_assign $(if $(release),--release )$(if $(stats),--stats )$(if $(progress),--progress )$(if $(threads),--threads $(threads) )$(if $(debug),-d )$(if $(static),--static )$(if $(LDFLAGS),--link-flags="$(LDFLAGS)" )$(if $(target),--cross-compile --target $(target) )
SPEC_WARNINGS_OFF := --exclude-warnings spec/std --exclude-warnings spec/compiler
SPEC_FLAGS := $(if $(verbose),-v )$(if $(junit_output),--junit_output $(junit_output) )
CRYSTAL_CONFIG_LIBRARY_PATH := '$$ORIGIN/../lib/crystal'
Expand Down
32 changes: 32 additions & 0 deletions spec/compiler/codegen/multi_assign_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,38 @@ describe "Code gen: multi assign" do
CR
end

context "without preview_multi_assign" do
it "doesn't raise if value size in 1 to n assignment doesn't match target count" do
run(<<-CR).to_i.should eq(4)
require "prelude"

begin
a, b = [1, 2, 3]
4
rescue ex : Exception
raise ex unless ex.message == "Multiple assignment count mismatch"
5
end
CR
end
end

context "preview_multi_assign" do
it "raises if value size in 1 to n assignment doesn't match target count" do
run(<<-CR, flags: %w(preview_multi_assign)).to_i.should eq(5)
require "prelude"

begin
a, b = [1, 2, 3]
4
rescue ex : Exception
raise ex unless ex.message == "Multiple assignment count mismatch"
5
end
CR
end
end

it "supports m to n assignment, with splat on left-hand side (1)" do
run(<<-CR).to_i.should eq(12345)
#{tuple_new}
Expand Down
84 changes: 61 additions & 23 deletions spec/compiler/normalize/multi_assign_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,6 @@ describe "Normalize: multi assign" do
CR
end

it "normalizes 1 to n" do
assert_expand_second "d = 1; a, b, c = d", <<-CR
__temp_1 = d
a = __temp_1[0]
b = __temp_1[1]
c = __temp_1[2]
CR
end

it "normalizes n to n with []" do
assert_expand_third "a = 1; b = 2; a[0], b[1] = 2, 3", <<-CR
__temp_1 = 2
Expand All @@ -30,14 +21,6 @@ describe "Normalize: multi assign" do
CR
end

it "normalizes 1 to n with []" do
assert_expand_third "a = 1; b = 2; a[0], b[1] = 2", <<-CR
__temp_1 = 2
a[0] = __temp_1[0]
b[1] = __temp_1[1]
CR
end

it "normalizes n to n with call" do
assert_expand_third "a = 1; b = 2; a.foo, b.bar = 2, 3", <<-CR
__temp_1 = 2
Expand All @@ -47,12 +30,67 @@ describe "Normalize: multi assign" do
CR
end

it "normalizes 1 to n with call" do
assert_expand_third "a = 1; b = 2; a.foo, b.bar = 2", <<-CR
__temp_1 = 2
a.foo = __temp_1[0]
b.bar = __temp_1[1]
CR
context "without preview_multi_assign" do
it "normalizes 1 to n" do
assert_expand_second "d = 1; a, b, c = d", <<-CR
__temp_1 = d
a = __temp_1[0]
b = __temp_1[1]
c = __temp_1[2]
CR
end

it "normalizes 1 to n with []" do
assert_expand_third "a = 1; b = 2; a[0], b[1] = 2", <<-CR
__temp_1 = 2
a[0] = __temp_1[0]
b[1] = __temp_1[1]
CR
end

it "normalizes 1 to n with call" do
assert_expand_third "a = 1; b = 2; a.foo, b.bar = 2", <<-CR
__temp_1 = 2
a.foo = __temp_1[0]
b.bar = __temp_1[1]
CR
end
end

context "preview_multi_assign" do
it "normalizes 1 to n" do
assert_expand_second "d = 1; a, b, c = d", <<-CR, flags: "preview_multi_assign"
__temp_1 = d
if __temp_1.size != 3
::raise(::IndexError.new("Multiple assignment count mismatch"))
end
a = __temp_1[0]
b = __temp_1[1]
c = __temp_1[2]
CR
end

it "normalizes 1 to n with []" do
assert_expand_third "a = 1; b = 2; a[0], b[1] = 2", <<-CR, flags: "preview_multi_assign"
__temp_1 = 2
if __temp_1.size != 2
::raise(::IndexError.new("Multiple assignment count mismatch"))
end
a[0] = __temp_1[0]
b[1] = __temp_1[1]
CR
end

it "normalizes 1 to n with call" do
assert_expand_third "a = 1; b = 2; a.foo, b.bar = 2", <<-CR, flags: "preview_multi_assign"
__temp_1 = 2
if __temp_1.size != 2
::raise(::IndexError.new("Multiple assignment count mismatch"))
end
a.foo = __temp_1[0]
b.bar = __temp_1[1]
CR
end
end

it "normalizes m to n, with splat on left-hand side, splat is empty" do
Expand Down
64 changes: 64 additions & 0 deletions spec/compiler/semantic/multi_assign_spec.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
require "../../spec_helper"

describe "Semantic: multi assign" do
context "without preview_multi_assign" do
it "doesn't error if assigning tuple to fewer targets" do
assert_type(%(
require "prelude"

x = {1, 2, ""}
a, b = x
{a, b}
)) { tuple_of [int32, int32] }
end
end

context "preview_multi_assign" do
it "errors if assigning tuple to fewer targets" do
assert_error %(
require "prelude"

x = {1, 2, ""}
a, b = x
), "cannot assign Tuple(Int32, Int32, String) to 2 targets", flags: "preview_multi_assign"
end

pending "errors if assigning tuple to more targets" do
assert_error %(
require "prelude"

x = {1}
a, b = x
), "cannot assign Tuple(Int32) to 2 targets", flags: "preview_multi_assign"
end

it "errors if assigning union of tuples to fewer targets" do
assert_error %(
require "prelude"

x = true ? {1, 2, 3} : {4, 5, 6, 7}
a, b = x
), "cannot assign (Tuple(Int32, Int32, Int32) | Tuple(Int32, Int32, Int32, Int32)) to 2 targets", flags: "preview_multi_assign"
end

it "doesn't error if some type in union matches target count" do
assert_type(%(
require "prelude"

x = true ? {1, "", 3} : {4, 5}
a, b = x
{a, b}
), flags: "preview_multi_assign") { tuple_of [int32, union_of(int32, string)] }
end

it "doesn't error if some type in union has no constant size" do
assert_type(%(
require "prelude"

x = true ? {1, "", 3} : [4, 5]
a, b = x
{a, b}
), flags: "preview_multi_assign") { tuple_of [int32, union_of(int32, string)] }
end
end
end
22 changes: 12 additions & 10 deletions spec/spec_helper.cr
Original file line number Diff line number Diff line change
Expand Up @@ -85,28 +85,30 @@ def assert_normalize(from, to, flags = nil, *, file = __FILE__, line = __LINE__)
to_nodes
end

def assert_expand(from : String, to, *, file = __FILE__, line = __LINE__)
assert_expand Parser.parse(from), to, file: file, line: line
def assert_expand(from : String, to, *, flags = nil, file = __FILE__, line = __LINE__)
assert_expand Parser.parse(from), to, flags: flags, file: file, line: line
end

def assert_expand(from_nodes : ASTNode, to, *, file = __FILE__, line = __LINE__)
to_nodes = LiteralExpander.new(new_program).expand(from_nodes)
def assert_expand(from_nodes : ASTNode, to, *, flags = nil, file = __FILE__, line = __LINE__)
program = new_program
program.flags.concat(flags.split) if flags
to_nodes = LiteralExpander.new(program).expand(from_nodes)
to_nodes.to_s.strip.should eq(to.strip), file: file, line: line
end

def assert_expand_second(from : String, to, *, file = __FILE__, line = __LINE__)
def assert_expand_second(from : String, to, *, flags = nil, file = __FILE__, line = __LINE__)
node = (Parser.parse(from).as(Expressions))[1]
assert_expand node, to, file: file, line: line
assert_expand node, to, flags: flags, file: file, line: line
end

def assert_expand_third(from : String, to, *, file = __FILE__, line = __LINE__)
def assert_expand_third(from : String, to, *, flags = nil, file = __FILE__, line = __LINE__)
node = (Parser.parse(from).as(Expressions))[2]
assert_expand node, to, file: file, line: line
assert_expand node, to, flags: flags, file: file, line: line
end

def assert_error(str, message = nil, *, inject_primitives = false, file = __FILE__, line = __LINE__)
def assert_error(str, message = nil, *, inject_primitives = false, flags = nil, file = __FILE__, line = __LINE__)
expect_raises TypeException, message, file, line do
semantic str, inject_primitives: inject_primitives
semantic str, inject_primitives: inject_primitives, flags: flags
end
end

Expand Down
18 changes: 9 additions & 9 deletions spec/std/kernel_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,20 @@ require "./spec_helper"

describe "exit" do
it "exits normally with status 0" do
status, _ = compile_and_run_source "exit"
status, _, _ = compile_and_run_source "exit"
status.success?.should be_true
end

it "exits with given error code" do
status, _ = compile_and_run_source "exit 42"
status, _, _ = compile_and_run_source "exit 42"
status.success?.should be_false
status.exit_code.should eq(42)
end
end

describe "at_exit" do
it "runs handlers on normal program ending" do
status, output = compile_and_run_source <<-CODE
status, output, _ = compile_and_run_source <<-CODE
at_exit do
puts "handler code"
end
Expand All @@ -27,7 +27,7 @@ describe "at_exit" do
end

it "runs handlers on explicit program ending" do
status, output = compile_and_run_source <<-'CODE'
status, output, _ = compile_and_run_source <<-'CODE'
at_exit do |exit_code|
puts "handler code, exit code: #{exit_code}"
end
Expand All @@ -40,7 +40,7 @@ describe "at_exit" do
end

it "runs handlers in reverse order" do
status, output = compile_and_run_source <<-CODE
status, output, _ = compile_and_run_source <<-CODE
at_exit do
puts "first handler code"
end
Expand All @@ -59,7 +59,7 @@ describe "at_exit" do
end

it "runs all handlers maximum once" do
status, output = compile_and_run_source <<-CODE
status, output, _ = compile_and_run_source <<-CODE
at_exit do
puts "first handler code"
end
Expand All @@ -86,7 +86,7 @@ describe "at_exit" do
end

it "allows handlers to change the exit code with explicit `exit` call" do
status, output = compile_and_run_source <<-'CODE'
status, output, _ = compile_and_run_source <<-'CODE'
at_exit do |exit_code|
puts "first handler code, exit code: #{exit_code}"
end
Expand Down Expand Up @@ -114,7 +114,7 @@ describe "at_exit" do
end

it "allows handlers to change the exit code with explicit `exit` call (2)" do
status, output = compile_and_run_source <<-'CODE'
status, output, _ = compile_and_run_source <<-'CODE'
at_exit do |exit_code|
puts "first handler code, exit code: #{exit_code}"
end
Expand Down Expand Up @@ -210,7 +210,7 @@ describe "at_exit" do
end

it "allows at_exit inside at_exit" do
status, output = compile_and_run_source <<-CODE
status, output, _ = compile_and_run_source <<-CODE
at_exit do
puts "1"
at_exit do
Expand Down
2 changes: 1 addition & 1 deletion spec/std/process_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ describe Process do
end

pending_win32 "chroot raises when unprivileged" do
status, output = compile_and_run_source <<-'CODE'
status, output, _ = compile_and_run_source <<-'CODE'
begin
Process.chroot("/usr")
puts "FAIL"
Expand Down
12 changes: 6 additions & 6 deletions src/compiler/crystal/codegen/primitives.cr
Original file line number Diff line number Diff line change
Expand Up @@ -1136,8 +1136,8 @@ class Crystal::CodeGenVisitor
success_ordering = atomic_ordering_from_symbol_literal(call.args[-2])
failure_ordering = atomic_ordering_from_symbol_literal(call.args[-1])

pointer, cmp, new = call_args
value = builder.cmpxchg(pointer, cmp, new, success_ordering, failure_ordering)
ptr, cmp, new, _, _ = call_args
value = builder.cmpxchg(ptr, cmp, new, success_ordering, failure_ordering)
HertzDevil marked this conversation as resolved.
Show resolved Hide resolved
value_ptr = alloca llvm_type(node.type)
store extract_value(value, 0), gep(value_ptr, 0, 0)
store extract_value(value, 1), gep(value_ptr, 0, 1)
Expand All @@ -1150,8 +1150,8 @@ class Crystal::CodeGenVisitor
ordering = atomic_ordering_from_symbol_literal(call.args[-2])
singlethread = bool_from_bool_literal(call.args[-1])

_, pointer, val = call_args
builder.atomicrmw(op, pointer, val, ordering, singlethread)
_, ptr, val, _, _ = call_args
builder.atomicrmw(op, ptr, val, ordering, singlethread)
end

def codegen_primitive_fence(call, node, target_def, call_args)
Expand All @@ -1168,7 +1168,7 @@ class Crystal::CodeGenVisitor
ordering = atomic_ordering_from_symbol_literal(call.args[-2])
volatile = bool_from_bool_literal(call.args[-1])

ptr = call_args.first
ptr, _, _ = call_args

inst = builder.load(ptr)
inst.ordering = ordering
Expand All @@ -1182,7 +1182,7 @@ class Crystal::CodeGenVisitor
ordering = atomic_ordering_from_symbol_literal(call.args[-2])
volatile = bool_from_bool_literal(call.args[-1])

ptr, value = call_args
ptr, value, _, _ = call_args

inst = builder.store(value, ptr)
inst.ordering = ordering
Expand Down
Loading