require "../../spec_helper" module Crystal describe "Normalize: def" do it "expands a def on request with default arguments" do a_def = parse("def foo(x, y = 1, z = 2); x + y + z; end").as(Def) actual = a_def.expand_default_arguments(Program.new, 1) expected = parse("def foo(x); y = 1; z = 2; foo(x, y, z); end") actual.should eq(expected) end it "expands a def on request with default arguments (2)" do a_def = parse("def foo(x, y = 1, z = 2); x + y + z; end").as(Def) actual = a_def.expand_default_arguments(Program.new, 2) expected = parse("def foo(x, y); z = 2; foo(x, y, z); end") actual.should eq(expected) end it "expands a def on request with default arguments that yields" do a_def = parse("def foo(x, y = 1, z = 2); yield x + y + z; end").as(Def) actual = a_def.expand_default_arguments(Program.new, 1) expected = parse("def foo(x); y = 1; z = 2; yield x + y + z; end") actual.should eq(expected) end it "expands a def on request with default arguments that yields (2)" do a_def = parse("def foo(x, y = 1, z = 2); yield x + y + z; end").as(Def) actual = a_def.expand_default_arguments(Program.new, 2) expected = parse("def foo(x, y); z = 2; yield x + y + z; end") actual.should eq(expected) end it "expands a def on request with default arguments and type restrictions" do a_def = parse("def foo(x, y : Int32 = 1, z : Int64 = 2i64); x + y + z; end").as(Def) actual = a_def.expand_default_arguments(Program.new, 1) expected = parse("def foo(x); y = 1; z = 2i64; x + y + z; end").as(Def) exps = expected.body.as(Expressions).expressions exps[0] = AssignWithRestriction.new(exps[0].as(Assign), Path.new("Int32")) exps[1] = AssignWithRestriction.new(exps[1].as(Assign), Path.new("Int64")) actual.should eq(expected) end it "expands a def on request with default arguments and type restrictions (2)" do a_def = parse("def foo(x, y : Int32 = 1, z : Int64 = 2i64); x + y + z; end").as(Def) actual = a_def.expand_default_arguments(Program.new, 2) expected = parse("def foo(x, y : Int32); z = 2i64; x + y + z; end").as(Def) exps = expected.body.as(Expressions).expressions exps[0] = AssignWithRestriction.new(exps[0].as(Assign), Path.new("Int64")) actual.should eq(expected) end it "expands with splat" do a_def = parse("def foo(*args); args; end").as(Def) actual = a_def.expand_default_arguments(Program.new, 3) expected = parse("def foo(__temp_1, __temp_2, __temp_3)\n args = {__temp_1, __temp_2, __temp_3}\n args\nend") actual.should eq(expected) end it "expands with splat with one arg before" do a_def = parse("def foo(x, *args); args; end").as(Def) actual = a_def.expand_default_arguments(Program.new, 3) expected = parse("def foo(x, __temp_1, __temp_2)\n args = {__temp_1, __temp_2}\n args\nend") actual.should eq(expected) end it "expands with splat and zero" do a_def = parse("def foo(*args); args; end").as(Def) actual = a_def.expand_default_arguments(Program.new, 0) actual.to_s.should eq("def foo\n args = ::Tuple.new\n args\nend") end it "expands with splat and default argument" do a_def = parse("def foo(x = 1, *args); args; end").as(Def) actual = a_def.expand_default_arguments(Program.new, 0) actual.to_s.should eq("def foo\n x = 1\n args = ::Tuple.new\n args\nend") end it "expands with named argument" do a_def = parse("def foo(x = 1, y = 2); x + y; end").as(Def) actual = a_def.expand_default_arguments(Program.new, 0, ["y"]) actual.to_s.should eq("def foo:y(y)\n x = 1\n foo(x, y)\nend") end it "expands with two named argument" do a_def = parse("def foo(x = 1, y = 2); x + y; end").as(Def) actual = a_def.expand_default_arguments(Program.new, 0, ["y", "x"]) actual.to_s.should eq("def foo:y:x(y, x)\n foo(x, y)\nend") end it "expands with two named argument and one not" do a_def = parse("def foo(x, y = 2, z = 3); x + y; end").as(Def) actual = a_def.expand_default_arguments(Program.new, 1, ["z"]) actual.to_s.should eq("def foo:z(x, z)\n y = 2\n foo(x, y, z)\nend") end it "expands with named argument and yield" do a_def = parse("def foo(x = 1, y = 2, &); yield x + y; end").as(Def) actual = a_def.expand_default_arguments(Program.new, 0, ["y"]) actual.to_s.should eq("def foo:y(y, &)\n x = 1\n yield x + y\nend") end # Small optimizations: no need to create a separate def in these cases it "expands with one named arg that is the only one (1)" do a_def = parse("def foo(x = 1); x; end").as(Def) other_def = a_def.expand_default_arguments(Program.new, 0, ["x"]) other_def.should be(a_def) end it "expands with one named arg that is the only one (2)" do a_def = parse("def foo(x, y = 1); x; end").as(Def) other_def = a_def.expand_default_arguments(Program.new, 1, ["y"]) other_def.should be(a_def) end it "expands with more named arg which come in the correct order" do a_def = parse("def foo(x, y = 1, z = 2); x; end").as(Def) other_def = a_def.expand_default_arguments(Program.new, 1, ["y", "z"]) other_def.should be(a_def) end it "expands with magic constant" do a_def = parse("def foo(x, y = __LINE__); x; end").as(Def) other_def = a_def.expand_default_arguments(Program.new, 1) other_def.should be(a_def) end it "expands with magic constant specifying one when all are magic" do a_def = parse("def foo(x, file = __FILE__, line = __LINE__); x; end").as(Def) other_def = a_def.expand_default_arguments(Program.new, 2) other_def.should be(a_def) end it "expands with magic constant specifying one when not all are magic" do a_def = parse("def foo(x, z = 1, line = __LINE__); x; end").as(Def) other_def = a_def.expand_default_arguments(Program.new, 2) other_def.should be(a_def) end it "expands with magic constant with named arg" do a_def = parse("def foo(x, file = __FILE__, line = __LINE__); x; end").as(Def) other_def = a_def.expand_default_arguments(Program.new, 1, ["line"]) other_def.to_s.should eq("def foo:line(x, line, file = __FILE__)\n foo(x, file, line)\nend") end it "expands with magic constant with named arg with yield" do a_def = parse("def foo(x, file = __FILE__, line = __LINE__, &); yield x, file, line; end").as(Def) other_def = a_def.expand_default_arguments(Program.new, 1, ["line"]) other_def.to_s.should eq("def foo:line(x, line, file = __FILE__, &)\n yield x, file, line\nend") end it "expands a def with double splat and no args" do a_def = parse("def foo(**options); options; end").as(Def) other_def = a_def.expand_default_arguments(Program.new, 0) other_def.to_s.should eq("def foo\n options = ::NamedTuple.new\n options\nend") end it "expands a def with double splat and two named args" do a_def = parse("def foo(**options); options; end").as(Def) other_def = a_def.expand_default_arguments(Program.new, 0, ["x", "y"]) other_def.to_s.should eq("def foo:x:y(x __temp_1, y __temp_2)\n options = {x: __temp_1, y: __temp_2}\n options\nend") end it "expands a def with double splat and two named args and regular args" do a_def = parse("def foo(y, **options); y + options; end").as(Def) other_def = a_def.expand_default_arguments(Program.new, 0, ["x", "y", "z"]) other_def.to_s.should eq("def foo:x:y:z(x __temp_1, y, z __temp_3)\n options = {x: __temp_1, z: __temp_3}\n y + options\nend") end it "expands a def with splat and double splat" do a_def = parse("def foo(*args, **options); args + options; end").as(Def) other_def = a_def.expand_default_arguments(Program.new, 2, ["x", "y"]) other_def.to_s.should eq("def foo:x:y(__temp_1, __temp_2, x __temp_3, y __temp_4)\n args = {__temp_1, __temp_2}\n options = {x: __temp_3, y: __temp_4}\n args + options\nend") end it "expands arg with default value after splat" do a_def = parse("def foo(*args, x = 10); args + x; end").as(Def) other_def = a_def.expand_default_arguments(Program.new, 0) other_def.to_s.should eq("def foo\n x = 10\n args = ::Tuple.new\n args + x\nend") end it "expands default value after splat index" do a_def = parse("def foo(x, *y, z = 10); x + y + z; end").as(Def) other_def = a_def.expand_default_arguments(Program.new, 3) other_def.to_s.should eq("def foo(x, __temp_1, __temp_2)\n z = 10\n y = {__temp_1, __temp_2}\n (x + y) + z\nend") end it "uses bare *" do a_def = parse("def foo(x, *, y); x + y; end").as(Def) other_def = a_def.expand_default_arguments(Program.new, 1, ["y"]) other_def.to_s.should eq("def foo:y(x, y)\n x + y\nend") end it "expands a def with external names (1)" do a_def = parse("def foo(x y); y; end").as(Def) actual = a_def.expand_default_arguments(Program.new, 0, ["x"]) actual.should be(a_def) end it "expands a def with external names (2)" do a_def = parse("def foo(x x1, y y1); x1 + y1; end").as(Def) other_def = a_def.expand_default_arguments(Program.new, 0, ["y", "x"]) other_def.to_s.should eq("def foo:y:x(y y1, x x1)\n foo(x1, y1)\nend") end it "expands a def on request with default arguments (external names)" do a_def = parse("def foo(x x1, y y1 = 1, z z1 = 2); x1 + y1 + z1; end").as(Def) actual = a_def.expand_default_arguments(Program.new, 1) expected = parse("def foo(x x1); y1 = 1; z1 = 2; foo(x1, y1, z1); end") actual.should eq(expected) end it "expands a def on request with default arguments that yields (external names)" do a_def = parse("def foo(x x1, y y1 = 1, z z1 = 2); yield x1 + y1 + z1; end").as(Def) actual = a_def.expand_default_arguments(Program.new, 1) expected = parse("def foo(x x1); y1 = 1; z1 = 2; yield x1 + y1 + z1; end") actual.should eq(expected) end it "expands a new def with double splat and two named args and regular args" do a_def = parse("def new(y, **options); y + options; end").as(Def) other_def = a_def.expand_new_default_arguments(Program.new, 0, ["x", "y", "z"]) other_def.to_s.should eq("def new:x:y:z(x __temp_1, y __temp_2, z __temp_3)\n _ = allocate\n _.initialize(x: __temp_1, y: __temp_2, z: __temp_3)\n if _.responds_to?(:finalize)\n ::GC.add_finalizer(_)\n end\n _\nend") end it "expands def with reserved external name (#6559)" do a_def = parse("def foo(abstract __arg0, **options); @abstract = __arg0; end").as(Def) other_def = a_def.expand_default_arguments(Program.new, 0, ["abstract"]) other_def.to_s.should eq("def foo:abstract(abstract __arg0)\n options = ::NamedTuple.new\n @abstract = __arg0\nend") end describe "gives correct body location with" do {"default arg" => "def testing(foo = 5)", "default arg with restriction" => "def testing(foo : Int = 5)", "splat arg" => "def testing(*foo : Array)", "block instance var arg" => "def testing(&@foo : ->)", }.each do |(suffix1, definition)| {"with body" => "zzz = 7\n", "without body" => "", }.each do |(suffix2, body)| it "#{suffix1}, #{suffix2}" do a_def = parse("#{definition}\n#{body}end").as(Def) actual = a_def.expand_default_arguments(Program.new, 1) actual.location.should eq Location.new("", line_number: 1, column_number: 1) actual.body.location.should eq Location.new("", line_number: 2, column_number: 1) end 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