Skip to content

Add StructNewKeywordInit cop

Kev Kloss requested to merge kkloss-add-StructNewKeywordInit-cop into master

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 literal true:
    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

Merge request reports

Loading