From 3e1cf457060308f44d6d55ce08435446fe44b975 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sat, 13 Jun 2026 23:16:54 +0900 Subject: [PATCH] feat: support the `it` block parameter Recognize Ruby 3.4's `it` (e.g. `[1].map { it + 1 }`) by handling the new :itblock node alongside :numblock across type checking, source mapping, diagnostics, hover, and signature help. This requires bumping Prism::Translation::Parser33 to Parser34. Closes #1450 --- lib/steep/diagnostic/ruby.rb | 8 +-- lib/steep/node_helper.rb | 4 +- lib/steep/services/goto_service.rb | 2 +- lib/steep/services/hover_provider/ruby.rb | 2 +- lib/steep/services/signature_help_provider.rb | 2 +- lib/steep/source.rb | 12 ++--- lib/steep/type_construction.rb | 20 ++++++-- lib/steep/typing.rb | 6 +-- sig/shims/prism.rbs | 10 ++++ sig/steep/source.rbs | 2 +- sig/test/type_construction_test.rbs | 4 ++ test/type_construction_test.rb | 50 +++++++++++++++++++ 12 files changed, 100 insertions(+), 22 deletions(-) diff --git a/lib/steep/diagnostic/ruby.rb b/lib/steep/diagnostic/ruby.rb index 9f3f005c1..3de9adbed 100644 --- a/lib/steep/diagnostic/ruby.rb +++ b/lib/steep/diagnostic/ruby.rb @@ -112,7 +112,7 @@ def initialize(node:, params:) send = case node.type when :send, :csend node - when :block, :numblock + when :block, :numblock, :itblock node.children[0] end @@ -163,7 +163,7 @@ def initialize(node:, params:, missing_keywords:) send = case node.type when :send, :csend node - when :block, :numblock + when :block, :numblock, :itblock node.children[0] end @@ -245,7 +245,7 @@ def initialize(node:, type:, method:) loc ||= node.loc.operator if node.loc.respond_to?(:operator) # steep:ignore NoMethod loc ||= node.loc.selector if node.loc.respond_to?(:selector) # steep:ignore NoMethod loc - when :block + when :block, :numblock, :itblock node.children[0].loc.selector end super(node: node, location: loc || node.loc.expression) @@ -1061,7 +1061,7 @@ def initialize(node:, location:, message:) def header_line header = case node&.type - when :send, :csend, :block, :numblock + when :send, :csend, :block, :numblock, :itblock "The method is deprecated" when :const, :casgn "The constant is deprecated" diff --git a/lib/steep/node_helper.rb b/lib/steep/node_helper.rb index 33acac9fa..580902b5e 100644 --- a/lib/steep/node_helper.rb +++ b/lib/steep/node_helper.rb @@ -220,7 +220,7 @@ def test_send_node(node) def private_send?(node) case node.type - when :block, :numblock + when :block, :numblock, :itblock private_send?(node.children[0]) when :send, :csend receiver, = deconstruct_send_node!(node) @@ -243,7 +243,7 @@ def deconstruct_sendish_and_block_nodes(*nodes) when :send, :csend, :super if block_node case block_node.type - when :block, :numblock + when :block, :numblock, :itblock if send_node.equal?(block_node.children[0]) return [send_node, block_node] end diff --git a/lib/steep/services/goto_service.rb b/lib/steep/services/goto_service.rb index 09d06e02c..be11f9f30 100644 --- a/lib/steep/services/goto_service.rb +++ b/lib/steep/services/goto_service.rb @@ -422,7 +422,7 @@ def query_at_implementation(typing, subtyping, line:, column:) when :send location = (_ = node.location) #: Parser::AST::_SelectorLocation if test_ast_location(location.selector, line: line, column: column) - if (parent = parents[0]) && parent.type == :block && parent.children[0] === node + if (parent = parents[0]) && (parent.type == :block || parent.type == :numblock || parent.type == :itblock) && parent.children[0] === node node = parents[0] end diff --git a/lib/steep/services/hover_provider/ruby.rb b/lib/steep/services/hover_provider/ruby.rb index 022a25888..ba9f5573b 100644 --- a/lib/steep/services/hover_provider/ruby.rb +++ b/lib/steep/services/hover_provider/ruby.rb @@ -103,7 +103,7 @@ def content_for(target:, path:, line:, column:) when :send, :csend result_node = case parents[0]&.type - when :block, :numblock + when :block, :numblock, :itblock if node == parents.fetch(0).children[0] parents.fetch(0) else diff --git a/lib/steep/services/signature_help_provider.rb b/lib/steep/services/signature_help_provider.rb index a566af941..4ec349b39 100644 --- a/lib/steep/services/signature_help_provider.rb +++ b/lib/steep/services/signature_help_provider.rb @@ -59,7 +59,7 @@ def run(line:, column:) if begin_loc.end_pos <= pos && pos <= end_loc.begin_pos # Given position is between open/close parens of args of send node - if parent && (parent.type == :block || parent.type == :numblock) && node.equal?(parent.children[0]) + if parent && (parent.type == :block || parent.type == :numblock || parent.type == :itblock) && node.equal?(parent.children[0]) send_node = parent else send_node = node diff --git a/lib/steep/source.rb b/lib/steep/source.rb index bf4fbea4e..d7942e2e4 100644 --- a/lib/steep/source.rb +++ b/lib/steep/source.rb @@ -31,7 +31,7 @@ def string_value(token) end def self.new_parser - Prism::Translation::Parser33.new(Builder.new).tap do |parser| + Prism::Translation::Parser34.new(Builder.new).tap do |parser| parser.diagnostics.all_errors_are_fatal = true parser.diagnostics.ignore_warnings = true end @@ -257,7 +257,7 @@ def self.construct_mapping(node:, annotations:, mapping:, line_range: nil) annot.line or next case node.type - when :def, :module, :class, :block, :numblock, :ensure, :defs, :resbody + when :def, :module, :class, :block, :numblock, :itblock, :ensure, :defs, :resbody location = node.loc location.line <= annot.line && annot.line < location.last_line else @@ -565,13 +565,13 @@ def self.insert_type_node(node, comments) end ] ) - when :numblock - send, size, body = node.children + when :numblock, :itblock + send, arg, body = node.children node = node.updated( nil, [ map_child_node(send) {|child| insert_type_node(child, child_assertions) }, - size, + arg, insert_type_node(body, child_assertions) ] ) @@ -648,7 +648,7 @@ def self.sendish_node?(node) case node.type when :send, :csend node - when :block, :numblock + when :block, :numblock, :itblock send = node.children[0] case send.type when :send, :csend diff --git a/lib/steep/type_construction.rb b/lib/steep/type_construction.rb index 5d6dee06c..4a07b3368 100644 --- a/lib/steep/type_construction.rb +++ b/lib/steep/type_construction.rb @@ -2696,7 +2696,7 @@ def synthesize(node, hint: nil, condition: false) constr.add_typing(node, type: type) end - when :block, :numblock, :send, :csend + when :block, :numblock, :itblock, :send, :csend synthesize_sendish(node, hint: hint, tapp: nil) when :forwarded_args, :forward_arg @@ -2772,6 +2772,20 @@ def synthesize_sendish(node, hint:, tapp:) params = Parser::AST::Node.new(:args, arg_nodes) + if send_node.type == :lambda + # @type var node: Parser::AST::Node & Parser::AST::_BlockNode + type_lambda(node, params_node: params, body_node: body, type_hint: hint) + else + type_send(node, send_node: send_node, block_params: params, block_body: body, unwrap: send_node.type == :csend, tapp: tapp, hint: hint) + end + end + when :itblock + yield_self do + send_node, _name, body = node.children + + arg_nodes = [Parser::AST::Node.new(:procarg0, [:it])] + params = Parser::AST::Node.new(:args, arg_nodes) + if send_node.type == :lambda # @type var node: Parser::AST::Node & Parser::AST::_BlockNode type_lambda(node, params_node: params, body_node: body, type_hint: hint) @@ -3346,7 +3360,7 @@ def type_send_interface(node, interface:, receiver:, receiver_type:, method_name end end - if node.type == :csend || ((node.type == :block || node.type == :numblock) && node.children[0].type == :csend) + if node.type == :csend || ((node.type == :block || node.type == :numblock || node.type == :itblock) && node.children[0].type == :csend) optional_type = AST::Types::Union.build(types: [call.return_type, AST::Builtin.nil_type]) call = call.with_return_type(optional_type) end @@ -3509,7 +3523,7 @@ def type_send(node, send_node:, block_params:, block_body:, unwrap: false, tapp: when AST::Types::Any case node.type - when :block, :numblock + when :block, :numblock, :itblock # @type var node: Parser::AST::Node & Parser::AST::_BlockNode block_annotations = source.annotations(block: node, factory: checker.factory, context: nesting) block_params or raise diff --git a/lib/steep/typing.rb b/lib/steep/typing.rb index d832e6d9a..2ea87cf1f 100644 --- a/lib/steep/typing.rb +++ b/lib/steep/typing.rb @@ -106,7 +106,7 @@ def set_body_context(node, context) set(body_begin_pos..body_end_pos, context) end - when :block, :numblock + when :block, :numblock, :itblock range = block_range(node) set(range, context) @@ -132,7 +132,7 @@ def block_range(node) node.loc.begin.end_pos # steep:ignore NoMethod end end_pos = node.loc.end.begin_pos # steep:ignore NoMethod - when :numblock + when :numblock, :itblock send_node, _ = node.children begin_pos = node.loc.begin.end_pos # steep:ignore NoMethod end_pos = node.loc.end.begin_pos # steep:ignore NoMethod @@ -245,7 +245,7 @@ def block_range(node) node.loc.begin.end_pos # steep:ignore NoMethod end end_pos = node.loc.end.begin_pos # steep:ignore NoMethod - when :numblock + when :numblock, :itblock send_node, _ = node.children begin_pos = node.loc.begin.end_pos # steep:ignore NoMethod end_pos = node.loc.end.begin_pos # steep:ignore NoMethod diff --git a/sig/shims/prism.rbs b/sig/shims/prism.rbs index 8de99a348..01a1f6ef7 100644 --- a/sig/shims/prism.rbs +++ b/sig/shims/prism.rbs @@ -14,5 +14,15 @@ module Prism attr_reader diagnostics: untyped end + + class Parser34 < Parser + def initialize: (Parser::Builders::Default builder) -> void + + def parse: (Parser::Source::Buffer) -> Parser::AST::Node + + def parse_with_comments: (Parser::Source::Buffer) -> [Parser::AST::Node, Array[Parser::Source::Comment]] + + attr_reader diagnostics: untyped + end end end diff --git a/sig/steep/source.rbs b/sig/steep/source.rbs index 496b3ad08..00c410a36 100644 --- a/sig/steep/source.rbs +++ b/sig/steep/source.rbs @@ -29,7 +29,7 @@ module Steep def string_value: (untyped token) -> untyped end - def self.new_parser: () -> Prism::Translation::Parser33 + def self.new_parser: () -> Prism::Translation::Parser34 def self.parse: (String source_code, path: Pathname, factory: AST::Types::Factory) -> Source diff --git a/sig/test/type_construction_test.rbs b/sig/test/type_construction_test.rbs index 1eab29e6a..c95e1a51a 100644 --- a/sig/test/type_construction_test.rbs +++ b/sig/test/type_construction_test.rbs @@ -727,6 +727,10 @@ class TypeConstructionTest < Minitest::Test def test_ruby3_numbered_parameter4: () -> untyped + def test_ruby3_it_parameter1: () -> untyped + + def test_ruby3_it_parameter2: () -> untyped + def test_type_check_def_without_decl: () -> untyped def test_break_without_value_to_block: () -> untyped diff --git a/test/type_construction_test.rb b/test/type_construction_test.rb index 8060fe7ce..8d20481d5 100644 --- a/test/type_construction_test.rb +++ b/test/type_construction_test.rb @@ -8382,6 +8382,56 @@ def bar: (foo: Integer) { (Integer) -> void } -> void end end + def test_ruby3_it_parameter1 + with_checker do |checker| + source = parse_ruby(< void } -> void + + def bar: (foo: Integer) { (Integer) -> void } -> void + end +end +RBS + + source = parse_ruby(<