Yukihiro M. wrote:
foo(1, foo:3) # (a=1, b={:foo=>3}) or (a=1,b=0,c=[{:foo=>3}])?
Error. There’s no ‘foo’ parameter. Passing a literal hash would
require {}.
foo(1, 2, 8, c:5) # c={:c=>5}) or c=[8,{:c=>5}] or error?
Hm. Either an error or c = [5], depending on whether or not your want
to
declare that, once a positional is used, there’s no going back after the
fact.
args = [1, {:b=>2, foo=>5}]
foo(*args) # (a=1,b={:b=>2, foo=>5}) or (a=1,b=2,c=[{:foo=>5}])?
a = 1, b = {:b=>2, foo=>5}, since you’ve passed a literal hash.
Here’s the latest test case we had for Sydney. Note that Sydney assumes
that
all method arguments automatically become keyword arguments. There’s no
syntax
for explicitly declaring that a given method parameter is a valid
keyword argument.
For the kids following along at home, also see
http://redhanded.hobix.com/inspect/namedParametersArenTTheyAllNamed.html
for
further discussion on the subject.
Regards,
Dan
Tests the keyword argument behavior.
Authored by Dan B. and Evan Webb
require ‘behavior/keyword’
require ‘test/unit’
This is the class we use within the test case below.
class Foo
MYCONST = 5
behavior KeywordBehavior
attr_reader :x, :y, :z
def bar(x, y, z=3)
@x = x
@y = y
@z = z
end
def grab(x, y, *z)
@x = x
@y = y
@z = z
end
def vars
[@x, @y, @z]
end
def t2(x)
name = "blah"
end
end
Added to test a method redefinition in a subclass, as well as the
define_method and instance_method methods.
class Bar < Foo
attr_reader :a, :b, :c
def bar(a, b=2, c=3, d=4)
@a = a
@b = b
@c = c
end
define_method(:baz, instance_method(:bar))
end
TOPLEVEL=5
class TC_Arguments < Test::Unit::TestCase
def setup
@foo = Foo.new
@bar = Bar.new
@arr = [1,2,3]
end
def test_methods_defined
assert_respond_to(@foo, :bar)
assert_respond_to(@bar, :bar)
assert_respond_to(@foo, :grab)
assert_respond_to(@foo, :vars)
assert_respond_to(@foo, :t2)
assert_respond_to(@bar, :baz)
end
# Methods may still take positional arguments, the way they always
have
def test_positional_basic
assert_nothing_raised{ @foo.bar(1,2) }
assert_nothing_raised{ @foo.bar(1,2,3) }
end
def test_positional_basic_subclass
assert_nothing_raised{ @bar.bar(1) }
assert_nothing_raised{ @bar.bar(1,2) }
assert_nothing_raised{ @bar.bar(1,2,3) }
assert_nothing_raised{ @bar.bar(1,2,3,4) }
assert_nothing_raised{ @bar.baz(1,2,3) }
end
# You can use named parameters, using the name of the parameter
defined in
# in the method itself. It must be the name of the parameter,
followed by
# a colon, followed by the value. Spaces between the parameter
name, colon
# and value are not allowed.
def test_named_basic
assert_nothing_raised{ @foo.bar(z:1, x:2, y:3) }
assert_equal(2, @foo.x)
assert_equal(3, @foo.y)
assert_equal(1, @foo.z)
end
def test_named_basic_subclass
assert_nothing_raised{ @bar.bar(d:1, a:2, b:3, c:4) }
assert_equal(2, @bar.a)
assert_equal(3, @bar.b)
assert_equal(4, @bar.c)
assert_equal(1, @bar.d)
end
# Ensure that named parameters in a define_method/instance_method
work.
# In this case, Bar#baz is actually calling Foo#bar.
def test_named_dynamically_defined
assert_nothing_raised{ @bar.baz(z:3, y:2, x:1) }
assert_equal(1, @bar.x)
assert_equal(2, @bar.y)
assert_equal(3, @bar.z)
end
# Default values in a declaration work the same way they always
have. The
# use of named parameters does not change this.
def test_named_basic_default_values
assert_nothing_raised{ @foo.bar(x:1, y:2) }
assert_equal(1, @foo.x)
assert_equal(2, @foo.y)
assert_equal(3, @foo.z) # default
end
# You can mix and match positional and named parameters, though
there are
# limitations. Positional parameters must come first (i.e. on the
left
# side).
def test_mixed_basic
assert_nothing_raised{ @foo.bar(1, z:2, y:3) }
assert_equal(1, @foo.x)
assert_equal(3, @foo.y)
assert_equal(2, @foo.z)
assert_nothing_raised{ @foo.bar(1, 2, z:4) }
assert_equal(1, @foo.x)
assert_equal(2, @foo.y)
assert_equal(4, @foo.z)
end
# You can splat an array, and the values will be assigned in a left
to right
# fashion, as in the current Ruby behavior.
def test_splat
assert_nothing_raised{ @foo.bar(*@arr) }
assert_equal(1, @foo.x)
assert_equal(2, @foo.y)
assert_equal(3, @foo.z)
end
# You can mix and match positional, named and splat arguments.
However,
# splat arguments must come last. This is the same as the current
behavior
# in Ruby.
def test_mixed_splat
a = [1]
assert_nothing_raised{ @foo.bar(*a) }
assert_nothing_raised{ @foo.bar(1, *a) }
assert_nothing_raised{ @foo.bar(1, y:2, *a)}
assert_nothing_raised{ @foo.bar(1, 2, *a) }
assert_nothing_raised{ @foo.bar(x:1, y:2, *a) }
end
# The '*rest' declarations work the same as they always have. In
the case
# of named parameters, the values assigned to the ‘rest’ argument
are
# simply pushed as an array.
def test_def_splat
assert_nothing_raised{ @foo.grab(1, 2, 3, 4) }
assert_equal([1, 2, [3, 4]], @foo.vars)
assert_nothing_raised{ @foo.grab(x:1, y:2, z:4) }
assert_equal([1, 2, [4]], @foo.vars)
assert_nothing_raised{ @foo.grab(1, 2, 3, [1,2,3])
assert_equal([1,2,3,[[1,2,3]]], @foo.vars)
assert_nothing_raised{ @foo.grab(1, 2, 3, z:[1,2,3])
assert_equal([1,2,3,[[1,2,3]]], @foo.vars)
assert_nothing_raised{ @foo.grab(1, 2, 3, z:*[1,2,3])
assert_equal([1,2,3,[1,2,3]], @foo.vars)
end
# If a keyword argument is assigned to a '*rest' parameter, then it
# autovivifies a hash, assigning that hash as the argument to the
# '*rest' parameter. Note double declarations are still illegal.
def test_keyword_in_splat
assert_nothing_raised{ @foo.grab(10, 20, name:"evan", age:99) }
assert_equal([ 10, 20, [{:name => "evan", :age=>99}] ],
@foo.vars)
end
# If nothing is assigned to the '*rest' variable, then it is simply
empty.
# is the same as the current Ruby behavior.
def test_keyword_no_splat
@foo.grab(x:1, y:2)
assert_equal [1,2,[]], @foo.vars
end
# The presence of a '*rest' declaration does not alter required
arguments.
def test_keyword_splat_with_not_enough
assert_raises(ArgumentError) { @foo.grab(x:1) }
end
# Ensure that the presence of a parameter in one method declaration
does
# not interfere with an identically named parameter in another
declaration.
def test_no_local_leakage
assert_nothing_raised { @foo.t2 x:8 }
end
# Ensure that "::" doesn't cause problems
def test_works_with_constants
assert_nothing_raised{ @foo.bar(y:1, x:2, z:Foo::MYCONST) }
assert_nothing_raised{ @foo.bar(y: 1, x: 2, z: ::TOPLEVEL) }
end
def test_expected_argument_errors
assert_raises(ArgumentError){ @foo.bar } # x & y missing
assert_raises(ArgumentError){ @foo.bar(1) } # y missing
assert_raises(ArgumentError){ @foo.bar(x:1) } # y missing
assert_raises(ArgumentError){ @foo.bar(y:1) } # x missing
assert_raises(ArgumentError){ @foo.bar(x:1, z:2) } # y missing
assert_raises(ArgumentError){ @foo.bar([1,2,3]) } # y missing
assert_raises(ArgumentError){ @foo.bar(*[1]) } # y missing
assert_raises(ArgumentError){ @foo.bar(x:1,y:1,a:3) } # no 'a'
parameter
assert_raises(ArgumentError){ @foo.bar(*[1,2,3,4]) } # too many
args
end
# Ensure that the subclass didn't pick up the named parameters
# from the parent
def test_expected_argument_errors_subclass
assert_raises(ArgumentError){ @bar.bar }
assert_raises(ArgumentError){ @bar.bar(x:1) }
assert_raises(ArgumentError){ @bar.bar(y:1) }
assert_raises(ArgumentError){ @bar.bar(z:1) }
end
def test_expected_syntax_errors
assert_raises(SyntaxError){ @foo.bar(x:1, 2) } # positional must
be first
assert_raises(SyntaxError){ @foo.bar(*a, 1) } # splat must be
last
assert_raises(SyntaxError){ @foo.bar(*a, x:1) } # splat must be
last
end
def teardown
@foo = nil
@arr = nil
end
end