From fc264acb5257c6fd885a4e3b400bb6e7d4b1380c Mon Sep 17 00:00:00 2001 From: Soutaro Matsumoto Date: Wed, 9 Mar 2022 16:55:24 +0900 Subject: [PATCH 1/4] Unfold type alias --- lib/steep/type_construction.rb | 2 ++ test/type_construction_test.rb | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/lib/steep/type_construction.rb b/lib/steep/type_construction.rb index e106fbc92..c5ac5d705 100644 --- a/lib/steep/type_construction.rb +++ b/lib/steep/type_construction.rb @@ -2731,6 +2731,8 @@ def type_lambda(node, params_node:, body_node:, type_hint:) block_annotations = source.annotations(block: node, factory: checker.factory, current_module: current_namespace) params = TypeInference::BlockParams.from_node(params_node, annotations: block_annotations) + type_hint = deep_expand_alias(type_hint) if type_hint + case type_hint when AST::Types::Proc params_hint = type_hint.type.params diff --git a/test/type_construction_test.rb b/test/type_construction_test.rb index a8feea06d..0dac88ca0 100644 --- a/test/type_construction_test.rb +++ b/test/type_construction_test.rb @@ -8420,6 +8420,24 @@ def foo(&block) end end + def test_lambda_with_block_alias + with_checker(<<-RBS) do |checker| +type callback[T] = ^() { (T) -> void } -> T + RBS + + source = parse_ruby(<<-'RUBY') +# @type var foo: callback[Integer] +foo = -> (&block) { block[80]; 123 } + RUBY + + with_standard_construction(checker, source) do |construction, typing| + type, _, _ = construction.synthesize(source.node) + + assert_no_error typing + end + end + end + def test_flat_map with_checker(<<-RBS) do |checker| class FlatMap From ae41d6148c8d0911c64af5a7883627f302398ef5 Mon Sep 17 00:00:00 2001 From: Soutaro Matsumoto Date: Thu, 10 Mar 2022 21:35:03 +0900 Subject: [PATCH 2/4] Fix error when a non-block type hint specified --- lib/steep/type_construction.rb | 2 +- test/type_construction_test.rb | 23 +++++++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/lib/steep/type_construction.rb b/lib/steep/type_construction.rb index c5ac5d705..8f7926e46 100644 --- a/lib/steep/type_construction.rb +++ b/lib/steep/type_construction.rb @@ -2771,7 +2771,7 @@ def type_lambda(node, params_node:, body_node:, type_hint:) end end else - type_hint.block + block_hint end end diff --git a/test/type_construction_test.rb b/test/type_construction_test.rb index 0dac88ca0..22e951d6f 100644 --- a/test/type_construction_test.rb +++ b/test/type_construction_test.rb @@ -8438,6 +8438,29 @@ def test_lambda_with_block_alias end end + def test_lambda_with_block_non_proc + with_checker(<<-RBS) do |checker| + RBS + + source = parse_ruby(<<-'RUBY') +# @type var foo: Integer +foo = -> (&block) do + block +end + RUBY + + with_standard_construction(checker, source) do |construction, typing| + type, _, _ = construction.synthesize(source.node) + + assert_typing_error(typing, size: 1) do |errors| + assert_any!(errors) do |error| + assert_instance_of Diagnostic::Ruby::IncompatibleAssignment, error + end + end + end + end + end + def test_flat_map with_checker(<<-RBS) do |checker| class FlatMap From f4bb92048814a75f327e299f8c08380dcdd52122 Mon Sep 17 00:00:00 2001 From: Soutaro Matsumoto Date: Thu, 10 Mar 2022 21:52:37 +0900 Subject: [PATCH 3/4] Add error when nonproc type is given for block parameter --- lib/steep/diagnostic/ruby.rb | 13 ++++++++++ lib/steep/type_construction.rb | 45 ++++++++++++++++++++++++++++------ test/type_construction_test.rb | 26 ++++++++++++++++++++ 3 files changed, 77 insertions(+), 7 deletions(-) diff --git a/lib/steep/diagnostic/ruby.rb b/lib/steep/diagnostic/ruby.rb index 9ba9e43eb..a430e0f21 100644 --- a/lib/steep/diagnostic/ruby.rb +++ b/lib/steep/diagnostic/ruby.rb @@ -669,6 +669,19 @@ def header_line end end + class ProcTypeExpected < Base + attr_reader :type + + def initialize(node:, type:) + super(node: node) + @type = type + end + + def header_line + "Proc type is expected but `#{type.to_s}` is specified" + end + end + class UnsupportedSyntax < Base attr_reader :message diff --git a/lib/steep/type_construction.rb b/lib/steep/type_construction.rb index 8f7926e46..98d2b2634 100644 --- a/lib/steep/type_construction.rb +++ b/lib/steep/type_construction.rb @@ -2727,6 +2727,18 @@ def type_masgn(node) end end + def optional_proc?(type) + if type.is_a?(AST::Types::Union) + if type.types.size == 2 + if type.types.find {|t| t.is_a?(AST::Types::Nil) } + if proc_type = type.types.find {|t| t.is_a?(AST::Types::Proc) } + proc_type + end + end + end + end + end + def type_lambda(node, params_node:, body_node:, type_hint:) block_annotations = source.annotations(block: node, factory: checker.factory, current_module: current_namespace) params = TypeInference::BlockParams.from_node(params_node, annotations: block_annotations) @@ -2751,6 +2763,13 @@ def type_lambda(node, params_node:, body_node:, type_hint:) block_constr.typing.add_context_for_body(node, context: block_constr.context) + default_proc_function = + Interface::Function.new( + params: Interface::Function::Params.empty, + return_type: AST::Builtin.any_type, + location: nil + ) + params.params.each do |param| _, block_constr = block_constr.synthesize(param.node, hint: param.type) end @@ -2761,13 +2780,25 @@ def type_lambda(node, params_node:, body_node:, type_hint:) case block_param_type when AST::Types::Proc Interface::Block.new(type: block_param_type.type, optional: false) - when AST::Types::Union - if block_param_type.types.size == 2 - if block_param_type.types.find {|t| t.is_a?(AST::Types::Nil) } - if proc_type = block_param_type.types.find {|t| t.is_a?(AST::Types::Proc) } - Interface::Block.new(type: proc_type.type, optional: true) - end - end + else + if proc_type = optional_proc?(block_param_type) + Interface::Block.new(type: proc_type.type, optional: true) + else + block_constr.typing.add_error( + Diagnostic::Ruby::ProcTypeExpected.new( + node: block_param.node, + type: block_param_type + ) + ) + + Interface::Block.new( + type: Interface::Function.new( + params: Interface::Function::Params.empty, + return_type: AST::Builtin.any_type, + location: nil + ), + optional: false + ) end end else diff --git a/test/type_construction_test.rb b/test/type_construction_test.rb index 22e951d6f..05ef377fd 100644 --- a/test/type_construction_test.rb +++ b/test/type_construction_test.rb @@ -8461,6 +8461,32 @@ def test_lambda_with_block_non_proc end end + def test_lambda_with_block_non_proc_arg + with_checker(<<-RBS) do |checker| + RBS + + source = parse_ruby(<<-'RUBY') +foo = -> (&block) do + # @type var block: Integer + block+1 +end + RUBY + + with_standard_construction(checker, source) do |construction, typing| + type, _, _ = construction.synthesize(source.node) + + assert_equal parse_type("^() { () -> untyped } -> ::Integer"), type + + assert_typing_error(typing, size: 1) do |errors| + assert_any!(errors) do |error| + assert_instance_of Diagnostic::Ruby::ProcTypeExpected, error + assert_equal parse_type("::Integer"), error.type + end + end + end + end + end + def test_flat_map with_checker(<<-RBS) do |checker| class FlatMap From 653259657606c23caec505272f0683567d123a59 Mon Sep 17 00:00:00 2001 From: Soutaro Matsumoto Date: Thu, 10 Mar 2022 21:54:40 +0900 Subject: [PATCH 4/4] Update output test --- smoke/diagnostics/proc_type_expected.rb | 3 +++ smoke/diagnostics/test_expectations.yml | 12 ++++++++++++ 2 files changed, 15 insertions(+) create mode 100644 smoke/diagnostics/proc_type_expected.rb diff --git a/smoke/diagnostics/proc_type_expected.rb b/smoke/diagnostics/proc_type_expected.rb new file mode 100644 index 000000000..dabf8b1ad --- /dev/null +++ b/smoke/diagnostics/proc_type_expected.rb @@ -0,0 +1,3 @@ +-> (&block) do + # @type var block: Integer +end diff --git a/smoke/diagnostics/test_expectations.yml b/smoke/diagnostics/test_expectations.yml index 4d5b92928..15c8ca998 100644 --- a/smoke/diagnostics/test_expectations.yml +++ b/smoke/diagnostics/test_expectations.yml @@ -332,6 +332,18 @@ severity: ERROR message: Type `::Integer` does not have method `foo` code: Ruby::NoMethod +- file: proc_type_expected.rb + diagnostics: + - range: + start: + line: 1 + character: 4 + end: + line: 1 + character: 10 + severity: ERROR + message: Proc type is expected but `::Integer` is specified + code: Ruby::ProcTypeExpected - file: required_block_missing.rb diagnostics: - range: