diff --git a/lib/steep/type_construction.rb b/lib/steep/type_construction.rb index 0d2e6dc5b..591a998be 100644 --- a/lib/steep/type_construction.rb +++ b/lib/steep/type_construction.rb @@ -2613,6 +2613,7 @@ def synthesize(node, hint: nil, condition: false) end when :alias + module_context.defined_instance_methods << node.children.first.children.first add_typing node, type: AST::Builtin.nil_type when :splat @@ -3465,6 +3466,24 @@ def type_send(node, send_node:, block_params:, block_body:, unwrap: false, tapp: end end + if module_context && receiver.nil? + case method_name + when :attr_reader + arguments.each do |arg| + module_context.defined_instance_methods << arg.children[0] + end + when :attr_writer + arguments.each do |arg| + module_context.defined_instance_methods << "#{arg.children[0]}=".to_sym + end + when :attr_accessor + arguments.each do |arg| + module_context.defined_instance_methods << arg.children[0] + module_context.defined_instance_methods << "#{arg.children[0]}=".to_sym + end + end + end + if unwrap recv_type = unwrap(recv_type) end diff --git a/test/type_construction_test.rb b/test/type_construction_test.rb index 63b0bd28b..e74b95a13 100644 --- a/test/type_construction_test.rb +++ b/test/type_construction_test.rb @@ -10821,4 +10821,155 @@ def test_type_hint__proc_union_proc_instance end end end + + def test_method_definition_missing_for_methods + with_checker(<<~RBS) do |checker| + class A + def foo: () -> void + def bar: () -> void + end + RBS + source = parse_ruby(<<~RUBY) + class A + def foo + end + end + RUBY + + with_standard_construction(checker, source) do |construction, typing| + construction.synthesize(source.node) + + assert_equal 1, typing.errors.size + + typing.errors[0].tap do |error| + assert_instance_of Diagnostic::Ruby::MethodDefinitionMissing, error + assert_equal :bar, error.missing_method + end + end + end + end + + def test_method_definition_missing_for_attr_readers + with_checker(<<~RBS) do |checker| + class A + attr_reader foo: Integer + attr_reader bar: Integer + end + RBS + source = parse_ruby(<<~RUBY) + class A + attr_reader :foo + end + RUBY + + with_standard_construction(checker, source) do |construction, typing| + construction.synthesize(source.node) + + assert_equal 1, typing.errors.size + + typing.errors[0].tap do |error| + assert_instance_of Diagnostic::Ruby::MethodDefinitionMissing, error + assert_equal :bar, error.missing_method + end + end + end + end + + def test_method_definition_missing_for_attr_writers + with_checker(<<~RBS) do |checker| + class A + attr_writer foo: Integer + attr_writer bar: Integer + end + RBS + source = parse_ruby(<<~RUBY) + class A + attr_writer :foo + end + RUBY + + with_standard_construction(checker, source) do |construction, typing| + construction.synthesize(source.node) + + assert_equal 2, typing.errors.size + + typing.errors[0].tap do |error| + assert_instance_of Diagnostic::Ruby::NoMethod, error + assert_equal :attr_writer, error.method # why? + end + + typing.errors[1].tap do |error| + assert_instance_of Diagnostic::Ruby::MethodDefinitionMissing, error + assert_equal :bar=, error.missing_method + end + end + end + end + + def test_method_definition_missing_for_attr_accessors + with_checker(<<~RBS) do |checker| + class A + attr_accessor foo: Integer + attr_accessor bar: Integer + end + RBS + source = parse_ruby(<<~RUBY) + class A + attr_accessor :foo + end + RUBY + + with_standard_construction(checker, source) do |construction, typing| + construction.synthesize(source.node) + + assert_equal 3, typing.errors.size + + typing.errors[0].tap do |error| + assert_instance_of Diagnostic::Ruby::NoMethod, error + assert_equal :attr_accessor, error.method # why? + end + + typing.errors[1].tap do |error| + assert_instance_of Diagnostic::Ruby::MethodDefinitionMissing, error + assert_equal :bar, error.missing_method + end + + typing.errors[2].tap do |error| + assert_instance_of Diagnostic::Ruby::MethodDefinitionMissing, error + assert_equal :bar=, error.missing_method + end + end + end + end + + def test_method_definition_missing_for_aliases + with_checker(<<~RBS) do |checker| + class A + def ==: (untyped) -> bool + alias eql? == + alias equal? == + end + RBS + source = parse_ruby(<<~RUBY) + class A + def ==(other) + true + end + + alias eql? == + end + RUBY + + with_standard_construction(checker, source) do |construction, typing| + construction.synthesize(source.node) + + assert_equal 1, typing.errors.size + + typing.errors[0].tap do |error| + assert_instance_of Diagnostic::Ruby::MethodDefinitionMissing, error + assert_equal :equal?, error.missing_method + end + end + end + end end