Stop ForwardingClass from raising on a destructured parameter #1710

Closed
opened 2026-11-12 07:55:46 +00:00 by jared · 2 comments
Owner

Problem

Sdlc/Structural/ForwardingClass raises NoMethodError on a public method that has a destructured parameter, such as def build((left, right)). ConstantChain.of_method calls method.arguments.map(&:name), and an MlhsNode has no name. A cop error stops the inspection of that file, so a valid Ruby file can break a rubocop run.

Observed

  • The #1703 review agent found it on 2026-11-12. The orchestrator confirmed it on main code, with the respond_to?(:name) guard in MethodMatch#parameter_names still present.
  • Error: undefined method 'name' for an instance of RuboCop::AST::MlhsNode (NoMethodError)
  • Location: plugins/os-sdlc/lib/os_sdlc/cops/structural/forwarding_class/constant_chain.rb:10, in ConstantChain.of_method, called from ClassMatch#forwarding?.
  • Update 2026-11-12 (session 01fe836c): three sites read name from each parameter: constant_chain.rb:10 (method.arguments.map(&:name)), method_match.rb:24 (@def_node.arguments.filter_map { |argument| argument.name }), and initialization.rb:11 (def_node.arguments.map(&:name)). #1703 (c390a8a5b) deleted the respond_to?(:name) guard in method_match.rb; the same input crashed in constant_chain.rb before that. def initialize((left, right)) crashes at initialization.rb:11. A check in memory with arguments.argument_list.map(&:name) (rubocop-ast 1.50.0 ArgsNode#argument_list, which flattens destructured parameters) at all three sites: no crash; def build((left, right)) that forwards both values gives 1 offense, the same as plain parameters; forwarding only left gives 0.

Reproduce

class Wrapper
  def build((left, right))
    Thing.new(left, right)
  end
end

Run offenses_for(RuboCop::Cop::Sdlc::Structural::ForwardingClass, source) from test/support/cop_helpers.rb on this source. It raises NoMethodError.

Approach

Required work: none beyond the acceptance criteria.

Acceptance criteria

  1. Given a class whose public method has a destructured parameter, when the cop runs, then it raises no error.
  2. Given that class forwards its destructured values to one constructor, when the cop runs, then the result matches the rule the cop applies to plain parameters (ruled 2026-11-12: a destructured parameter follows the plain-parameter rule).
  3. Given a class whose initialize has a destructured parameter, when the cop runs, then it raises no error.

Owner sketch

class RuboCop::Cop::Sdlc::Structural::ForwardingClass::ConstantChain
  def self.of_method(method)
end
class RuboCop::Cop::Sdlc::Structural::ForwardingClass::MethodMatch
  def parameter_names
end
class RuboCop::Cop::Sdlc::Structural::ForwardingClass::Initialization
  def initialize(def_node)
end

Origin

  • Trigger: #1703 survivor loop review, 2026-11-12
  • Improvised this session: none
  • Chain: cop raises on a destructured parameter ← ConstantChain.of_method maps name over every argument ← DESIGN (lib/os_sdlc/cops/structural/forwarding_class/constant_chain.rb:10)
  • Root candidate: none found; searched forwarding, ForwardingClass, destructur, mlhs, ConstantChain (nearest: #2358, the same MlhsNode fault in two other cops)
  • Where: lib/os_sdlc/cops/structural/forwarding_class/constant_chain.rb
  • Parent: #1703
  • Session: 2677aa00-1973-4df6-b9c5-e317088f3ea3
  • Transcript: /home/jared/.claude/projects/-home-jared-dev-cc-os--claude-worktrees-ticket-1708/2677aa00-1973-4df6-b9c5-e317088f3ea3.jsonl

VERDICT: CREATE (ticket-skeptic, 2026-11-12): crash in a shipped cop on valid Ruby; #2358 covers other cops.

## Problem `Sdlc/Structural/ForwardingClass` raises `NoMethodError` on a public method that has a destructured parameter, such as `def build((left, right))`. `ConstantChain.of_method` calls `method.arguments.map(&:name)`, and an `MlhsNode` has no `name`. A cop error stops the inspection of that file, so a valid Ruby file can break a rubocop run. ## Observed - The #1703 review agent found it on 2026-11-12. The orchestrator confirmed it on main code, with the `respond_to?(:name)` guard in `MethodMatch#parameter_names` still present. - Error: `undefined method 'name' for an instance of RuboCop::AST::MlhsNode (NoMethodError)` - Location: `plugins/os-sdlc/lib/os_sdlc/cops/structural/forwarding_class/constant_chain.rb:10`, in `ConstantChain.of_method`, called from `ClassMatch#forwarding?`. - Update 2026-11-12 (session 01fe836c): three sites read `name` from each parameter: `constant_chain.rb:10` (`method.arguments.map(&:name)`), `method_match.rb:24` (`@def_node.arguments.filter_map { |argument| argument.name }`), and `initialization.rb:11` (`def_node.arguments.map(&:name)`). #1703 (c390a8a5b) deleted the `respond_to?(:name)` guard in `method_match.rb`; the same input crashed in `constant_chain.rb` before that. `def initialize((left, right))` crashes at `initialization.rb:11`. A check in memory with `arguments.argument_list.map(&:name)` (rubocop-ast 1.50.0 `ArgsNode#argument_list`, which flattens destructured parameters) at all three sites: no crash; `def build((left, right))` that forwards both values gives 1 offense, the same as plain parameters; forwarding only `left` gives 0. ## Reproduce ```ruby class Wrapper def build((left, right)) Thing.new(left, right) end end ``` Run `offenses_for(RuboCop::Cop::Sdlc::Structural::ForwardingClass, source)` from `test/support/cop_helpers.rb` on this source. It raises `NoMethodError`. ## Approach Required work: none beyond the acceptance criteria. ## Acceptance criteria 1. Given a class whose public method has a destructured parameter, when the cop runs, then it raises no error. 2. Given that class forwards its destructured values to one constructor, when the cop runs, then the result matches the rule the cop applies to plain parameters (ruled 2026-11-12: a destructured parameter follows the plain-parameter rule). 3. Given a class whose `initialize` has a destructured parameter, when the cop runs, then it raises no error. ## Owner sketch ```ruby class RuboCop::Cop::Sdlc::Structural::ForwardingClass::ConstantChain def self.of_method(method) end class RuboCop::Cop::Sdlc::Structural::ForwardingClass::MethodMatch def parameter_names end class RuboCop::Cop::Sdlc::Structural::ForwardingClass::Initialization def initialize(def_node) end ``` ## Origin - Trigger: #1703 survivor loop review, 2026-11-12 - Improvised this session: none - Chain: cop raises on a destructured parameter ← `ConstantChain.of_method` maps `name` over every argument ← DESIGN (`lib/os_sdlc/cops/structural/forwarding_class/constant_chain.rb:10`) - Root candidate: none found; searched forwarding, ForwardingClass, destructur, mlhs, ConstantChain (nearest: #2358, the same MlhsNode fault in two other cops) - Where: lib/os_sdlc/cops/structural/forwarding_class/constant_chain.rb - Parent: #1703 - Session: 2677aa00-1973-4df6-b9c5-e317088f3ea3 - Transcript: /home/jared/.claude/projects/-home-jared-dev-cc-os--claude-worktrees-ticket-1708/2677aa00-1973-4df6-b9c5-e317088f3ea3.jsonl VERDICT: CREATE (ticket-skeptic, 2026-11-12): crash in a shipped cop on valid Ruby; #2358 covers other cops.
Author
Owner

Work started: os-sdlc pipeline on branch ticket-1710 (session 01fe836c).

Work started: os-sdlc pipeline on branch ticket-1710 (session 01fe836c).
Author
Owner

Resolution

Done: ForwardingClass no longer raises on a destructured parameter: the three name reads (ConstantChain.of_method, MethodMatch#parameter_names, Initialization) use arguments.argument_list. A destructured parameter follows the plain-parameter rule.

Evidence: Implementation 285 (11 dispatches, 7 min 25 s; one test-reviewer fail round): 2b93f2c91 (lib) and a238876ed (test + History row) on branch ticket-1710. New test file 4 runs, 0 failures; suite gate and survivor gate green.

Follow-ups: none

## Resolution **Done:** ForwardingClass no longer raises on a destructured parameter: the three name reads (ConstantChain.of_method, MethodMatch#parameter_names, Initialization) use arguments.argument_list. A destructured parameter follows the plain-parameter rule. **Evidence:** Implementation 285 (11 dispatches, 7 min 25 s; one test-reviewer fail round): 2b93f2c91 (lib) and a238876ed (test + History row) on branch ticket-1710. New test file 4 runs, 0 failures; suite gate and survivor gate green. **Follow-ups:** none <!-- os-backlog kind: resolution -->
jared closed this issue 2026-11-12 19:35:02 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
jared/cc-os#1710
No description provided.