Skip to content
This repository was archived by the owner on Nov 30, 2024. It is now read-only.

Commit ed84b42

Browse files
committed
Recognize hash as optional arg when optional keyword is present
Given a method foo(a, b = {}, x: false) it should recognize that calling foo(1, 'a' => 1, 'b' => 2) is a valid method call. In case when the method has optional arguments and keyword arguments the ruby interpreter will treat the last Hash as keyword arguments if it contains Symbol keys. In the example above calling foo(1, a: 1) will raise an ArgumentError.
1 parent 29f8cd5 commit ed84b42

File tree

2 files changed

+96
-0
lines changed

2 files changed

+96
-0
lines changed

lib/rspec/support/method_signature_verifier.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,10 @@ def has_kw_args_in?(args)
8585
# contain keyword arguments?
8686
def could_contain_kw_args?(args)
8787
return false if args.count <= min_non_kw_args
88+
return false if args.count <= max_non_kw_args &&
89+
Hash === args.last &&
90+
args.last.keys.none? { |x| x.is_a?(Symbol) }
91+
8892
@allows_any_kw_args || @allowed_kw_args.any?
8993
end
9094

spec/rspec/support/method_signature_verifier_spec.rb

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -366,6 +366,98 @@ def arity_kw(x, y:1, z:2); end
366366
end
367367
end
368368

369+
if RubyFeatures.kw_args_supported?
370+
describe 'a method with optional argument and keyword arguments' do
371+
eval <<-RUBY
372+
def arity_kw(x, y = {}, z:2); end
373+
RUBY
374+
375+
let(:test_method) { method(:arity_kw) }
376+
377+
it 'does not require any of the arguments' do
378+
expect(valid?(nil)).to eq(true)
379+
expect(valid?(nil, nil)).to eq(true)
380+
end
381+
382+
it 'does not allow an invalid keyword arguments' do
383+
expect(valid?(nil, nil, :a => 1)).to eq(false)
384+
expect(valid?(nil, :a => 1)).to eq(false)
385+
end
386+
387+
it 'allows Hash containing strings as last argument' do
388+
expect(valid?(nil, 'a' => 1)).to eq(true)
389+
end
390+
391+
it 'mentions the invalid keyword args in the error', :pending => RSpec::Support::Ruby.jruby? && !RSpec::Support::Ruby.jruby_9000? do
392+
expect(error_for(nil, nil, :a => 0)).to \
393+
eq("Invalid keyword arguments provided: a")
394+
end
395+
396+
it 'describes invalid arity precisely' do
397+
expect(error_for()).to \
398+
eq("Wrong number of arguments. Expected 1 to 2, got 0.")
399+
end
400+
401+
it 'does not blow up when given a BasicObject as the last arg' do
402+
expect(valid?(BasicObject.new)).to eq(true)
403+
end
404+
405+
it 'does not mutate the provided args array' do
406+
args = [nil, nil, { :y => 1 }]
407+
described_class.new(signature, args).valid?
408+
expect(args).to eq([nil, nil, { :y => 1 }])
409+
end
410+
411+
it 'mentions the arity and optional kw args in the description', :pending => RSpec::Support::Ruby.jruby? && !RSpec::Support::Ruby.jruby_9000? do
412+
expect(signature_description).to eq("arity of 1 to 2 and optional keyword args (:z)")
413+
end
414+
415+
it "indicates the optional keyword args" do
416+
expect(signature.optional_kw_args).to contain_exactly(:z)
417+
end
418+
419+
it "indicates it has no required keyword args" do
420+
expect(signature.required_kw_args).to eq([])
421+
end
422+
423+
describe 'with an expectation object' do
424+
it 'matches the exact arity' do
425+
expect(validate_expectation 0).to eq(false)
426+
expect(validate_expectation 1).to eq(true)
427+
expect(validate_expectation 2).to eq(true)
428+
end
429+
430+
it 'matches the exact range' do
431+
expect(validate_expectation 0, 1).to eq(false)
432+
expect(validate_expectation 1, 1).to eq(true)
433+
expect(validate_expectation 1, 2).to eq(true)
434+
expect(validate_expectation 1, 3).to eq(false)
435+
end
436+
437+
it 'does not match unlimited arguments' do
438+
expect(validate_expectation :unlimited_args).to eq(false)
439+
end
440+
441+
it 'matches optional keywords with the correct arity' do
442+
expect(validate_expectation :z).to eq(false)
443+
expect(validate_expectation 1, :z).to eq(true) # Are we OK with that?
444+
expect(validate_expectation 1, 2, :z).to eq(true)
445+
expect(validate_expectation 1, 2, :y).to eq(false)
446+
end
447+
448+
it 'does not match invalid keywords' do
449+
expect(validate_expectation :w).to eq(false)
450+
451+
expect(validate_expectation 2, :w).to eq(false)
452+
end
453+
454+
it 'does not match arbitrary keywords' do
455+
expect(validate_expectation :arbitrary_kw_args).to eq(false)
456+
end
457+
end
458+
end
459+
end
460+
369461
if RubyFeatures.required_kw_args_supported?
370462
describe 'a method with required keyword arguments' do
371463
eval <<-RUBY

0 commit comments

Comments
 (0)