Yukihiro Matsumoto wrote:
> Hi,
> 
> In message "Re: Confusion Over Keyword Arguments"
>     on Fri, 3 Mar 2006 00:57:44 +0900, "Berger, Daniel" <Daniel.Berger / qwest.com> writes:
> 
> |That can be made to work, with the understanding that '=' in a method
> |call means 'keyword argument', not 'assignment', since there is no point
> |in doing an assignment in a method call.
> 
> I'm not sure if we _can_.  yacc is a tough guy to fight with.
> 
> |http://djberg96.livejournal.com/50162.html
> 
> In this article, you've proposed *rest to slurp keyword hash.  Indeed
> it is simple, but maybe too simpler.  I don't get how it can co-exist
> with _your_ ideal keyword parameters.  I.e. how the following code
> should work?
> 
>  def foo(a, b=0, *c)
>     ...
>  end
> 
>  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 Berger 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