diff --git a/lib/steep/interface/builder.rb b/lib/steep/interface/builder.rb index d9199d51b..364bef532 100644 --- a/lib/steep/interface/builder.rb +++ b/lib/steep/interface/builder.rb @@ -767,6 +767,20 @@ def replace_primitive_method(method_name, method_def, method_type) ) end end + + if method_def.annotations.any? {|annotation| annotation.string == "primitive:nil?" } + return method_type.with( + type: method_type.type.with( + return_type: AST::Types::Logic::ReceiverIsNil.new(location: method_type.type.return_type.location) + ) + ) + elsif method_def.annotations.any? {|annotation| annotation.string == "primitive:not_nil?" } + return method_type.with( + type: method_type.type.with( + return_type: AST::Types::Logic::ReceiverIsNotNil.new(location: method_type.type.return_type.location) + ) + ) + end end method_type diff --git a/lib/steep/type_inference/logic_type_interpreter.rb b/lib/steep/type_inference/logic_type_interpreter.rb index d1d2b0fb9..4b9047cce 100644 --- a/lib/steep/type_inference/logic_type_interpreter.rb +++ b/lib/steep/type_inference/logic_type_interpreter.rb @@ -300,6 +300,29 @@ def evaluate_method_call(env:, type:, receiver:, arguments:) [truthy_result, falsy_result] end + when AST::Types::Logic::ReceiverIsNotNil + if receiver && arguments.size.zero? + receiver_type = typing.type_of(node: receiver) + unwrap = factory.unwrap_optional(receiver_type) + truthy_receiver = unwrap || receiver_type + falsy_receiver = AST::Builtin.nil_type + + truthy_env, falsy_env = refine_node_type( + env: env, + node: receiver, + truthy_type: truthy_receiver, + falsy_type: falsy_receiver + ) + + truthy_result = Result.new(type: TRUE, env: truthy_env, unreachable: false) + truthy_result.unreachable! unless unwrap + + falsy_result = Result.new(type: FALSE, env: falsy_env, unreachable: false) + falsy_result.unreachable! if no_subtyping?(sub_type: AST::Builtin.nil_type, super_type: receiver_type) + + [truthy_result, falsy_result] + end + when AST::Types::Logic::ReceiverIsArg if receiver && (arg = arguments[0]) receiver_type = typing.type_of(node: receiver) diff --git a/test/type_construction_test.rb b/test/type_construction_test.rb index 63b0bd28b..219a169db 100644 --- a/test/type_construction_test.rb +++ b/test/type_construction_test.rb @@ -6152,6 +6152,46 @@ def test_logic_receiver_is_nil end end + def test_logic_receiver_is_nil_via_annotation + with_checker(<<-RBS) do |checker| + class Object + %a{primitive:nil?} + def blank?: () -> bool + end + RBS + source = parse_ruby(<<-RUBY) +a = [1].first +return if a.blank? +a + 1 + RUBY + + with_standard_construction(checker, source) do |construction, typing| + construction.synthesize(source.node) + assert_no_error typing + end + end + end + + def test_logic_receiver_is_not_nil_via_annotation + with_checker(<<-RBS) do |checker| + class Object + %a{primitive:not_nil?} + def present?: () -> bool + end + RBS + source = parse_ruby(<<-RUBY) +a = [1].first +return unless a.present? +a + 1 + RUBY + + with_standard_construction(checker, source) do |construction, typing| + construction.synthesize(source.node) + assert_no_error typing + end + end + end + def test_logic_receiver_is_arg with_checker(<<-RBS) do |checker| RBS