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

Fix named arguments in super and previous_def calls #10400

Merged
merged 1 commit into from
Feb 17, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 72 additions & 2 deletions spec/compiler/semantic/previous_def_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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)
Expand Down
145 changes: 139 additions & 6 deletions spec/compiler/semantic/super_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
23 changes: 15 additions & 8 deletions src/compiler/crystal/semantic/normalizer.cr
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down