From c9a018d9d4cd82136131c93d93af051f0c9bb0e6 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Tue, 21 Oct 2025 23:01:32 +0800 Subject: [PATCH 1/3] typed def splat parameters --- spec/compiler/normalize/def_spec.cr | 50 +++++++++++++++++++ .../crystal/semantic/default_arguments.cr | 4 +- src/compiler/crystal/semantic/new.cr | 2 +- 3 files changed, 53 insertions(+), 3 deletions(-) diff --git a/spec/compiler/normalize/def_spec.cr b/spec/compiler/normalize/def_spec.cr index 1ad8f2962fc5..7f5f74a87244 100644 --- a/spec/compiler/normalize/def_spec.cr +++ b/spec/compiler/normalize/def_spec.cr @@ -251,5 +251,55 @@ module Crystal end end end + + it "normalizes with filename" do + a_def = parse("def foo(*args, **options); args + options; end", filename: "foo.cr").as(Def) + other_def = a_def.expand_default_arguments(Program.new, 2, ["x", "y"]) + other_def.to_s.should eq <<-CRYSTAL + def foo:x:y(__temp_cd6ae5dd_1, __temp_cd6ae5dd_2, x __temp_cd6ae5dd_3, y __temp_cd6ae5dd_4) + args = {__temp_cd6ae5dd_1, __temp_cd6ae5dd_2} + options = {x: __temp_cd6ae5dd_3, y: __temp_cd6ae5dd_4} + args + options + end + CRYSTAL + + a_def = parse("def foo(*args, **options); args + options; end", filename: "bar.cr").as(Def) + other_def = a_def.expand_default_arguments(Program.new, 2, ["x", "y"]) + other_def.to_s.should eq <<-CRYSTAL + def foo:x:y(__temp_fbcf3d84_1, __temp_fbcf3d84_2, x __temp_fbcf3d84_3, y __temp_fbcf3d84_4) + args = {__temp_fbcf3d84_1, __temp_fbcf3d84_2} + options = {x: __temp_fbcf3d84_3, y: __temp_fbcf3d84_4} + args + options + end + CRYSTAL + end + + it "normalizes `.new` with filename" do + a_def = parse("def new(y, **options); end", filename: "foo.cr").as(Def) + other_def = a_def.expand_new_default_arguments(Program.new, 0, ["x", "y", "z"]) + other_def.to_s.should eq <<-CRYSTAL + def new:x:y:z(x __temp_cd6ae5dd_1, y __temp_cd6ae5dd_2, z __temp_cd6ae5dd_3) + _ = allocate + _.initialize(x: __temp_cd6ae5dd_1, y: __temp_cd6ae5dd_2, z: __temp_cd6ae5dd_3) + if _.responds_to?(:finalize) + ::GC.add_finalizer(_) + end + _ + end + CRYSTAL + + a_def = parse("def new(y, **options); end", filename: "bar.cr").as(Def) + other_def = a_def.expand_new_default_arguments(Program.new, 0, ["x", "y", "z"]) + other_def.to_s.should eq <<-CRYSTAL + def new:x:y:z(x __temp_fbcf3d84_1, y __temp_fbcf3d84_2, z __temp_fbcf3d84_3) + _ = allocate + _.initialize(x: __temp_fbcf3d84_1, y: __temp_fbcf3d84_2, z: __temp_fbcf3d84_3) + if _.responds_to?(:finalize) + ::GC.add_finalizer(_) + end + _ + end + CRYSTAL + end end end diff --git a/src/compiler/crystal/semantic/default_arguments.cr b/src/compiler/crystal/semantic/default_arguments.cr index 82f753178031..a45f013faf6e 100644 --- a/src/compiler/crystal/semantic/default_arguments.cr +++ b/src/compiler/crystal/semantic/default_arguments.cr @@ -65,7 +65,7 @@ class Crystal::Def splat_size = 0 if splat_size < 0 splat_size.times do |index| - splat_name = program.new_temp_var_name + splat_name = program.new_temp_var_name(self) splat_names << splat_name splat_arg = Arg.new(splat_name) @@ -88,7 +88,7 @@ class Crystal::Def str << ':' str << named_arg - temp_name = program.new_temp_var_name + temp_name = program.new_temp_var_name(self) named_args_temp_names << temp_name # If a named argument matches an argument's external name, use the internal name diff --git a/src/compiler/crystal/semantic/new.cr b/src/compiler/crystal/semantic/new.cr index 4cc97beadf02..af52dceedeff 100644 --- a/src/compiler/crystal/semantic/new.cr +++ b/src/compiler/crystal/semantic/new.cr @@ -301,7 +301,7 @@ module Crystal # When **opts is expanded for named arguments, we must use internal # names that won't clash with local variables defined in the method. - temp_name = instance_type.program.new_temp_var_name + temp_name = instance_type.program.new_temp_var_name(self) def_arg = Arg.new(temp_name, external_name: named_arg) if matching_arg = args.find { |arg| arg.external_name == named_arg } From 59f1f3d2e1eab5e0edc005c8122b7d697390067a Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Tue, 21 Oct 2025 23:25:43 +0800 Subject: [PATCH 2/3] call splat arguments --- spec/compiler/semantic/splat_spec.cr | 17 +++++++++++++++++ src/compiler/crystal/semantic/main_visitor.cr | 2 +- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/spec/compiler/semantic/splat_spec.cr b/spec/compiler/semantic/splat_spec.cr index c43ca222d35a..e3944c6a3dc5 100644 --- a/spec/compiler/semantic/splat_spec.cr +++ b/spec/compiler/semantic/splat_spec.cr @@ -808,4 +808,21 @@ describe "Semantic: splat" do a_def.location.should eq Location.new("", line_number: 2, column_number: 3) a_def.body.location.should eq Location.new("", line_number: 3, column_number: 5) end + + it "normalizes with filename" do + result = semantic <<-CRYSTAL + def foo(x, y) + end + + #foo(*{1, 2}) + #foo(*{3, 4}) + CRYSTAL + + result.node.to_s.should end_with <<-CRYSTAL + __temp_cd6ae5dd_1 = {1, 2} + foo(__temp_cd6ae5dd_1[0], __temp_cd6ae5dd_1[1]) + __temp_fbcf3d84_1 = {3, 4} + foo(__temp_fbcf3d84_1[0], __temp_fbcf3d84_1[1])\n + CRYSTAL + end end diff --git a/src/compiler/crystal/semantic/main_visitor.cr b/src/compiler/crystal/semantic/main_visitor.cr index d2200c31acc8..111d051c08e0 100644 --- a/src/compiler/crystal/semantic/main_visitor.cr +++ b/src/compiler/crystal/semantic/main_visitor.cr @@ -1464,7 +1464,7 @@ module Crystal next end - temp_var = @program.new_temp_var.at(arg) + temp_var = @program.new_temp_var(arg).at(arg) assign = Assign.new(temp_var, exp).at(arg) exps << assign case arg From 405e7660898316508d56ade139ff71ca504a4752 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Tue, 21 Oct 2025 23:46:13 +0800 Subject: [PATCH 3/3] array-like literal splat element --- spec/compiler/normalize/array_literal_spec.cr | 21 +++++++++++++++++++ spec/spec_helper.cr | 4 ++-- .../crystal/semantic/literal_expander.cr | 2 +- 3 files changed, 24 insertions(+), 3 deletions(-) diff --git a/spec/compiler/normalize/array_literal_spec.cr b/spec/compiler/normalize/array_literal_spec.cr index 116d4f028915..fb977cfabdf8 100644 --- a/spec/compiler/normalize/array_literal_spec.cr +++ b/spec/compiler/normalize/array_literal_spec.cr @@ -97,4 +97,25 @@ describe "Normalize: array literal" do __temp_3 CRYSTAL end + + # TODO: add md5 to the rest of the variables + it "normalizes with filename" do + assert_expand_named "Foo{x, *y}", <<-CRYSTAL, filename: "foo.cr" + __temp_1 = x + __temp_2 = y + __temp_3 = Foo.new + __temp_3 << __temp_1 + __temp_2.each do |__temp_cd6ae5dd_1| __temp_3 << __temp_cd6ae5dd_1 end + __temp_3 + CRYSTAL + + assert_expand_named "Foo{x, *y}", <<-CRYSTAL, filename: "bar.cr" + __temp_1 = x + __temp_2 = y + __temp_3 = Foo.new + __temp_3 << __temp_1 + __temp_2.each do |__temp_fbcf3d84_1| __temp_3 << __temp_fbcf3d84_1 end + __temp_3 + CRYSTAL + end end diff --git a/spec/spec_helper.cr b/spec/spec_helper.cr index 8d92fb98e8fb..83077702a7ff 100644 --- a/spec/spec_helper.cr +++ b/spec/spec_helper.cr @@ -148,10 +148,10 @@ def assert_expand_third(from : String, to, *, flags = nil, file = __FILE__, line assert_expand node, to, flags: flags, file: file, line: line end -def assert_expand_named(from : String, to, *, generic = nil, flags = nil, file = __FILE__, line = __LINE__) +def assert_expand_named(from : String, to, *, generic = nil, flags = nil, filename = nil, file = __FILE__, line = __LINE__) program = new_program program.flags.concat(flags.split) if flags - from_nodes = Parser.parse(from) + from_nodes = parse(from, filename: filename) generic_type = generic.path if generic case from_nodes when ArrayLiteral diff --git a/src/compiler/crystal/semantic/literal_expander.cr b/src/compiler/crystal/semantic/literal_expander.cr index 0485652a6440..8eaf046ff3d8 100644 --- a/src/compiler/crystal/semantic/literal_expander.cr +++ b/src/compiler/crystal/semantic/literal_expander.cr @@ -200,7 +200,7 @@ module Crystal node.elements.each_with_index do |elem, i| temp_var = elem_temp_vars.try &.[i] if elem.is_a?(Splat) - yield_var = new_temp_var + yield_var = new_temp_var(elem) each_body = Call.new(ary_var.clone, "<<", yield_var.clone).at(node) each_block = Block.new(args: [yield_var], body: each_body).at(node) exps << Call.new((temp_var || elem.exp).clone, "each", block: each_block).at(node)