diff --git a/spec/compiler/semantic/previous_def_spec.cr b/spec/compiler/semantic/previous_def_spec.cr index a629b4dbaaf8..b10fcb5f4419 100644 --- a/spec/compiler/semantic/previous_def_spec.cr +++ b/spec/compiler/semantic/previous_def_spec.cr @@ -41,7 +41,7 @@ describe "Semantic: previous_def" do )) { int32 } end - it "types previous def with arguments" do + it "types previous def with explicit arguments" do assert_type(%( def foo(x) x @@ -55,7 +55,7 @@ describe "Semantic: previous_def" do )) { float64 } end - it "types previous def with arguments but without parenthesis" do + it "types previous def with forwarded arguments, def has parameters" do assert_type(%( def foo(x) x @@ -69,6 +69,76 @@ describe "Semantic: previous_def" do )) { int32 } end + it "types previous def with forwarded arguments, def has bare splat parameter (#8895)" do + assert_type(%( + def foo(*, x) + x + end + + def foo(*, x) + previous_def + end + + foo(x: 1) + )) { int32 } + end + + it "types previous def with named arguments, def has bare splat parameter (#8895)" do + assert_type(%( + def foo(*, x) + x + end + + def foo(*, x) + previous_def x: x || 'a' + end + + foo(x: 1) + )) { union_of int32, char } + end + + it "types previous def with named arguments, def has bare splat parameter (2) (#8895)" do + assert_type(%( + def foo(x) + x + end + + def foo(x) + previous_def x: x || 'a' + end + + foo(1) + )) { union_of int32, char } + end + + it "types previous def with forwarded arguments, different internal names (#8895)" do + assert_type(%( + def foo(*, x a) + a + end + + def foo(*, x b) + previous_def + end + + foo(x: 1) + )) { int32 } + end + + it "types previous def with named arguments, def has double splat parameter (#8895)" do + assert_type(%( + def foo(**opts) + opts + end + + def foo(**opts) + previous_def + end + + foo(x: 1, y: 'a') + )) { named_tuple_of({"x": int32, "y": char}) } + end + it "types previous def with restrictions" do assert_type(%( def foo(x : Int32) diff --git a/spec/compiler/semantic/super_spec.cr b/spec/compiler/semantic/super_spec.cr index 6f579d44a344..deefc7f60d10 100644 --- a/spec/compiler/semantic/super_spec.cr +++ b/spec/compiler/semantic/super_spec.cr @@ -2,21 +2,154 @@ require "../../spec_helper" describe "Semantic: super" do it "types super without arguments" do - assert_type("class Foo; def foo; 1; end; end; class Bar < Foo; def foo; super; end; end; Bar.new.foo") { int32 } + assert_type(" + class Foo + def foo + 1 + end + end + + class Bar < Foo + def foo + super + end + end + + Bar.new.foo + ") { int32 } end it "types super without arguments and instance variable" do - result = assert_type("class Foo; def foo; @x = 1; end; end; class Bar < Foo; def foo; super; end; end; bar = Bar.new; bar.foo; bar") do - types["Bar"] - end + result = assert_type(" + class Foo + def foo + @x = 1 + end + end + + class Bar < Foo + def foo + super + end + end + + bar = Bar.new + bar.foo + bar + ") { types["Bar"] } + mod, type = result.program, result.node.type.as(NonGenericClassType) superclass = type.superclass.as(NonGenericClassType) superclass.instance_vars["@x"].type.should eq(mod.nilable(mod.int32)) end - it "types super without arguments but parent has arguments" do - assert_type("class Foo; def foo(x); x; end; end; class Bar < Foo; def foo(x); super; end; end; Bar.new.foo(1)") { int32 } + it "types super with forwarded arguments, parent has parameters" do + assert_type(" + class Foo + def foo(x) + x + end + end + + class Bar < Foo + def foo(x) + super + end + end + + Bar.new.foo(1) + ") { int32 } + end + + it "types super with forwarded arguments, def has bare splat parameter (#8895)" do + assert_type(" + class Foo + def foo(*, x) + x + end + end + + class Bar < Foo + def foo(*, x) + super + end + end + + Bar.new.foo(x: 1) + ") { int32 } + end + + it "types super with named arguments, def has bare splat parameter (#8895)" do + assert_type(" + class Foo + def foo(*, x) + x + end + end + + class Bar < Foo + def foo(*, x) + super x: x || 'a' + end + end + + Bar.new.foo(x: 1) + ") { union_of int32, char } + end + + it "types super with named arguments, def has bare splat parameter (2) (#8895)" do + assert_type(" + class Foo + def foo(x) + x + end + end + + class Bar < Foo + def foo(x) + super x: x || 'a' + end + end + + Bar.new.foo(1) + ") { union_of int32, char } + end + + it "types super with forwarded arguments, different internal names (#8895)" do + assert_type(%( + class Foo + def foo(*, x a) + a + end + end + + class Bar < Foo + def foo(*, x b) + super + end + end + + Bar.new.foo(x: 1) + )) { int32 } + end + + it "types super with forwarded arguments, def has double splat parameter (#8895)" do + assert_type(" + class Foo + def foo(**opts) + opts + end + end + + class Bar < Foo + def foo(**opts) + super + end + end + + Bar.new.foo(x: 1, y: 'a') + ") { named_tuple_of({"x": int32, "y": char}) } end it "types super when container method is defined in parent class" do diff --git a/src/compiler/crystal/semantic/normalizer.cr b/src/compiler/crystal/semantic/normalizer.cr index 523cc670e070..f2652b326c3f 100644 --- a/src/compiler/crystal/semantic/normalizer.cr +++ b/src/compiler/crystal/semantic/normalizer.cr @@ -62,27 +62,34 @@ module Crystal end def transform(node : Call) - # Copy enclosing def's args to super/previous_def without parenthesis + # Copy enclosing def's parameters to super/previous_def without parenthesis case node when .super?, .previous_def? - if node.args.empty? && !node.has_parentheses? + named_args = node.named_args + if node.args.empty? && (!named_args || named_args.empty?) && !node.has_parentheses? if current_def = @current_def splat_index = current_def.splat_index current_def.args.each_with_index do |arg, i| - arg = Var.new(arg.name) - if splat_index && i > splat_index # Past the splat index we must pass arguments as named arguments named_args = node.named_args ||= Array(NamedArgument).new - named_args.push NamedArgument.new(arg.name, arg) + named_args.push NamedArgument.new(arg.external_name, Var.new(arg.name)) elsif i == splat_index - # At the splat index we must use a splat - node.args.push Splat.new(arg) + # At the splat index we must use a splat, except the bare splat + # parameter will be skipped + unless arg.external_name.empty? + node.args.push Splat.new(Var.new(arg.name)) + end else # Otherwise it's just a regular argument - node.args.push arg + node.args.push Var.new(arg.name) end end + + # Copy also the double splat + if arg = current_def.double_splat + node.args.push DoubleSplat.new(Var.new(arg.name)) + end end node.has_parentheses = true end