Add StructNewKeywordInit cop
What does this MR do and why?
This adds a new cop called StructNewKeywordInit
to enforce that keyword_init
is set to true
in Struct.new
.
We do this because starting in Ruby 3.2, keyword_init
will be true
by default, so we ensure consistent behavior across Ruby 3.1 and 3.2+.
A major side-effects is that we can no longer do MyStruct.new('Dog')
but must do MyStruct.new(name: 'Dog')
. See the Ruby behavior section below.
Relates to #474743 (closed)
Cop demo
✅ Okay
Struct.new(:name, keyword_init: true)
❌ Not okay
- No
keyword_init
:Struct.new(:name)
-
keyword_init
set to anything other than literaltrue
:Struct.new(:name, keyword_init: false)
Ruby behavior
Note that Struct.new(:name).new('Dog')
works in both 3.1 and 3.2+ due to backwards compatibility but an explicit Struct.new(:name, keyword_init: true).new('Dog')
will raise an ArgumentError
. Therefore we should only use keyword arguments in #new
.
3.2
irb(main):001> RUBY_VERSION
=> "3.2.3"
irb(main):002> Struct.new(:name).new(name: 'Dog')
=> #<struct name="Dog">
irb(main):003> Struct.new(:name, keyword_init: true).new(name: 'Dog')
=> #<struct name="Dog">
irb(main):004> Struct.new(:name, keyword_init: false).new(name: 'Dog')
=> #<struct name={:name=>"Dog"}>
3.1
irb(main):001> RUBY_VERSION
=> "3.1.5"
irb(main):002> Struct.new(:name).new(name: 'Dog')
(irb):2: warning: Passing only keyword arguments to Struct#initialize will behave differently from Ruby 3.2. Please use a Hash literal like .new({k: v}) instead of .new(k: v).
=> #<struct name={:name=>"Dog"}>
irb(main):003> Struct.new(:name, keyword_init: true).new(name: 'Dog')
=> #<struct name="Dog">
irb(main):004> Struct.new(:name, keyword_init: false).new(name: 'Dog')
=> #<struct name={:name=>"Dog"}>
How to set up and validate locally
Numbered steps to set up and validate the change are strongly suggested.
Edited by Kev Kloss