Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions lib/steep/diagnostic/ruby.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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"
Expand Down
4 changes: 2 additions & 2 deletions lib/steep/node_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion lib/steep/services/goto_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
2 changes: 1 addition & 1 deletion lib/steep/services/hover_provider/ruby.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion lib/steep/services/signature_help_provider.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
12 changes: 6 additions & 6 deletions lib/steep/source.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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|

@tk0miya tk0miya Jun 13, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Discussion: I bumped the Prism parser from Parser33 to Parser34 to support the it parameter.
But this introduces incompatible behavior.

def it
  1
end
[1, 2, 3].map { it }

With Parser33, the it inside the block is recognized as a method call to #it. With Parser34, the same it is recognized as the block's implicit parameter.

I'm afraid that this brings a breaking change. Would it be better to add a configuration for the Ruby syntax version?

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think it's a problem.
Ruby 3.3 was released 2023. Two years old. And it's already in security maintenance stage. We may want to use even newer version --Parser40.

parser.diagnostics.all_errors_are_fatal = true
parser.diagnostics.ignore_warnings = true
end
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
]
)
Expand Down Expand Up @@ -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
Expand Down
20 changes: 17 additions & 3 deletions lib/steep/type_construction.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
6 changes: 3 additions & 3 deletions lib/steep/typing.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down
10 changes: 10 additions & 0 deletions sig/shims/prism.rbs
Original file line number Diff line number Diff line change
Expand Up @@ -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
2 changes: 1 addition & 1 deletion sig/steep/source.rbs
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
4 changes: 4 additions & 0 deletions sig/test/type_construction_test.rbs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
50 changes: 50 additions & 0 deletions test/type_construction_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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(<<RUBY)
[1].map { it.to_s }
RUBY

with_standard_construction(checker, source) do |construction, typing|
type, _ = construction.synthesize(source.node)

assert_equal parse_type("::Array[::String]"), type

assert_no_error typing
end
end
end

def test_ruby3_it_parameter2
with_checker(<<RBS) do |checker|
module Ruby3
class Foo
def foo: (Integer) { (Integer) -> void } -> void

def bar: (foo: Integer) { (Integer) -> void } -> void
end
end
RBS

source = parse_ruby(<<RUBY)
Ruby3::Foo.new().foo { it }
Ruby3::Foo.new().bar { it }
RUBY

with_standard_construction(checker, source) do |construction, typing|
type, _ = construction.synthesize(source.node)

assert_typing_error(typing, size: 2) do |errors|
assert_any!(errors) do |error|
assert_instance_of Diagnostic::Ruby::InsufficientPositionalArguments, error
assert_equal "foo", error.location.source
end

assert_any!(errors) do |error|
assert_instance_of Diagnostic::Ruby::InsufficientKeywordArguments, error
assert_equal "bar", error.location.source
end
end
end
end
end

def test_type_check_def_without_decl
with_checker(<<RBS) do |checker|
RBS
Expand Down
Loading