From 1f0c1ef2c89111464f307255dca63e0b30ed2622 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Thu, 26 Oct 2023 03:17:37 +0900 Subject: [PATCH] emit UnreachableBranch to the "void" condition It would be better to emit UnreachableBranch diagnostics to use "void" variables for if-conditions. --- lib/steep/ast/types/factory.rb | 2 + test/logic_type_interpreter_test.rb | 34 +++++++++++ test/type_check_test.rb | 93 +++++++++++++++++++++++++++++ test/type_factory_test.rb | 9 +++ 4 files changed, 138 insertions(+) diff --git a/lib/steep/ast/types/factory.rb b/lib/steep/ast/types/factory.rb index 330cb09e9..f695afac6 100644 --- a/lib/steep/ast/types/factory.rb +++ b/lib/steep/ast/types/factory.rb @@ -409,6 +409,8 @@ def partition_union(type) ] when AST::Types::Any, AST::Types::Boolean, AST::Types::Top, AST::Types::Logic::Base [type, type] + when AST::Types::Bot, AST::Types::Void + [nil, nil] when AST::Types::Nil [nil, type] when AST::Types::Literal diff --git a/test/logic_type_interpreter_test.rb b/test/logic_type_interpreter_test.rb index 8be7b6df7..98fbef33e 100644 --- a/test/logic_type_interpreter_test.rb +++ b/test/logic_type_interpreter_test.rb @@ -377,6 +377,40 @@ def test_type_case_select end end + def test_call_void + with_checker(<<-RBS) do |checker| + RBS + source = parse_ruby("email = foo; email.void!") + + node = source.node.children[1] + + call = TypeInference::MethodCall::Typed.new( + node: node, + context: TypeInference::MethodCall::TopLevelContext.new, + method_name: :void!, + receiver_type: parse_type("::String"), + actual_method_type: parse_method_type("() -> void"), + method_decls: [], + return_type: AST::Types::Void.new() + ) + + typing = Typing.new(source: source, root_context: nil, cursor: nil) + typing.add_typing(dig(node), AST::Types::Void.new(), nil) + typing.add_typing(dig(node, 0), parse_type("::String"), nil) + + env = type_env + .assign_local_variable(:email, parse_type("::String"), nil) + + interpreter = LogicTypeInterpreter.new(subtyping: checker, typing: typing, config: config) + truthy_result, falsy_result = interpreter.eval(env: env, node: node) + + assert_equal parse_type("bot"), truthy_result.type + assert_equal parse_type("bot"), falsy_result.type + assert_equal true, truthy_result.unreachable + assert_equal true, falsy_result.unreachable + end + end + def test_type_case_select_untyped with_checker do |checker| interpreter = LogicTypeInterpreter.new(subtyping: checker, typing: nil, config: config) diff --git a/test/type_check_test.rb b/test/type_check_test.rb index c1bc92276..c5beccebf 100644 --- a/test/type_check_test.rb +++ b/test/type_check_test.rb @@ -583,6 +583,99 @@ def test_if_unreachable__unless_else ) end + def test_if_unreachable__if_void + run_type_check_test( + signatures: { + "a.rbs" => <<~RBS + class Foo + def void: () -> void + end + RBS + }, + code: { + "a.rb" => <<~RUBY + # Both branches are unreachable + if Foo.new.void + 123 + else + 123 + end + RUBY + }, + expectations: <<~YAML + --- + - file: a.rb + diagnostics: + - range: + start: + line: 2 + character: 0 + end: + line: 2 + character: 2 + severity: ERROR + message: The branch is unreachable + code: Ruby::UnreachableBranch + - range: + start: + line: 4 + character: 0 + end: + line: 4 + character: 4 + severity: ERROR + message: The branch is unreachable + code: Ruby::UnreachableBranch + YAML + ) + end + def test_if_unreachable__if_bot + run_type_check_test( + signatures: { + "a.rbs" => <<~RBS + class Foo + def bot: () -> bot + end + RBS + }, + code: { + "a.rb" => <<~RUBY + # Both branches are unreachable + if Foo.new.bot + 123 + else + 123 + end + RUBY + }, + expectations: <<~YAML + --- + - file: a.rb + diagnostics: + - range: + start: + line: 2 + character: 0 + end: + line: 2 + character: 2 + severity: ERROR + message: The branch is unreachable + code: Ruby::UnreachableBranch + - range: + start: + line: 4 + character: 0 + end: + line: 4 + character: 4 + severity: ERROR + message: The branch is unreachable + code: Ruby::UnreachableBranch + YAML + ) + end + def test_case_unreachable_1 run_type_check_test( signatures: { diff --git a/test/type_factory_test.rb b/test/type_factory_test.rb index 30efb9cf2..3a4cd1140 100644 --- a/test/type_factory_test.rb +++ b/test/type_factory_test.rb @@ -430,4 +430,13 @@ def test_partition_union__bool_union end end end + + def test_partition_union__void + with_factory() do |factory| + factory.partition_union(factory.type(parse_type("void"))).tap do |truthy, falsy| + assert_equal nil, truthy + assert_equal nil, falsy + end + end + end end