Skip to content

Fold keywords into an options hash for replaced new and class methods - #34

Merged
rosston merged 1 commit into
mainfrom
fix-options-hash-kwargs
Oct 12, 2026
Merged

rosston merged 1 commit into
mainfrom
fix-options-hash-kwargs

Conversation

@rosston

@rosston rosston commented Oct 12, 2026

Copy link
Copy Markdown
Member

Replacing a class swaps new and its class methods for generic (*args, **kwargs) lambdas, so Ruby always splits brace-less keywords like Klass.new(item: "hello") into kwargs. Mocktail then validated those kwargs against the real method, and when that method takes an options hash (def initialize(opts = {})) rather than keywords, it raised ArgumentError: unknown keyword: :item, even though Ruby 3 passes keywords to a method with no keyword params as a trailing positional hash. Instance-method doubles never had this problem because their lambdas are generated from the real signature.

Add FoldsKeywordsIntoOptionsHash, which applies Ruby's rule: fold non-empty kwargs into a trailing hash when the original method has no :key, :keyreq, :keyrest, or :nokey params. It reads params through GrabsOriginalMethodParameters so Sorbet-wrapped methods report their real signature. Call it before building the Call in HandlesDryNewCall and in the mocked branch of replaced class methods, so validation, call logging, and stub matching all see the same shape instance doubles already record.

test_replace_class asserted that House.room(wrong: :arg) raises for def self.room(room_name), which real Ruby accepts, so it now asserts on a call Ruby also rejects.

Picks up the failing test from #20 and makes it pass.

Replacing a class swaps `new` and its class methods for generic
`(*args, **kwargs)` lambdas, so Ruby always splits brace-less keywords
like `Klass.new(item: "hello")` into kwargs. Mocktail then validated
those kwargs against the real method, and when that method takes an
options hash (`def initialize(opts = {})`) rather than keywords, it
raised `ArgumentError: unknown keyword: :item`, even though Ruby 3
passes keywords to a method with no keyword params as a trailing
positional hash. Instance-method doubles never had this problem
because their lambdas are generated from the real signature.

Add FoldsKeywordsIntoOptionsHash, which applies Ruby's rule: fold
non-empty kwargs into a trailing hash when the original method has no
:key, :keyreq, :keyrest, or :nokey params. It reads params through
GrabsOriginalMethodParameters so Sorbet-wrapped methods report their
real signature. Call it before building the Call in HandlesDryNewCall
and in the mocked branch of replaced class methods, so validation,
call logging, and stub matching all see the same shape instance
doubles already record.

test_replace_class asserted that `House.room(wrong: :arg)` raises for
`def self.room(room_name)`, which real Ruby accepts, so it now asserts
on a call Ruby also rejects.

Picks up the failing test from #20 and makes it pass.
@rosston rosston self-assigned this Oct 12, 2026
@rosston
rosston marked this pull request as ready for review October 12, 2026 14:24
@rosston
rosston merged commit 0a0db4f into main Oct 12, 2026
10 checks passed
@rosston
rosston deleted the fix-options-hash-kwargs branch October 12, 2026 14:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant