Skip to content

Commit

Permalink
Merge pull request #807 from soutaro/hint-type
Browse files Browse the repository at this point in the history
Better hint type handling given to lambda
  • Loading branch information
soutaro committed May 26, 2023
2 parents 92b3bf0 + 981acf1 commit 56c911b
Show file tree
Hide file tree
Showing 6 changed files with 143 additions and 23 deletions.
22 changes: 19 additions & 3 deletions lib/steep/diagnostic/ruby.rb
Original file line number Diff line number Diff line change
Expand Up @@ -908,6 +908,19 @@ def header_line
end
end

class ProcHintIgnored < Base
attr_reader :hint_type, :block_node

def initialize(hint_type:, node:)
@hint_type = hint_type
super(node: node)
end

def header_line
"The type hint given to the block is ignored: `#{hint_type}`"
end
end

ALL = ObjectSpace.each_object(Class).with_object([]) do |klass, array|
if klass < Base
array << klass
Expand All @@ -932,7 +945,8 @@ def self.default
UnexpectedTypeArgument => :information,
InsufficientTypeArgument => :information,
UnexpectedTypeArgument => :information,
UnsupportedSyntax => nil
UnsupportedSyntax => nil,
ProcHintIgnored => :information
}
).freeze
end
Expand All @@ -946,7 +960,8 @@ def self.strict
ElseOnExhaustiveCase => nil,
UnknownConstant => nil,
MethodDefinitionMissing => nil,
UnsupportedSyntax => nil
UnsupportedSyntax => nil,
ProcHintIgnored => :warning
}
).freeze
end
Expand All @@ -962,7 +977,8 @@ def self.lenient
MethodDefinitionMissing => nil,
UnexpectedJump => nil,
FalseAssertion => :hint,
UnsupportedSyntax => nil
UnsupportedSyntax => nil,
ProcHintIgnored => :hint
}
).freeze
end
Expand Down
11 changes: 11 additions & 0 deletions lib/steep/subtyping/variable_variance.rb
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,17 @@ def self.add_type(type, variance:, covariants:, contravariants:)
covariants << type.name
contravariants << type.name
end
when AST::Types::Proc
type.type.params.each_type do |type|
add_type(type, variance: variance, covariants: contravariants, contravariants: covariants)
end
add_type(type.type.return_type, variance: variance, covariants: covariants, contravariants: contravariants)
if type.block
type.block.type.params.each_type do |type|
add_type(type, variance: variance, covariants: covariants, contravariants: contravariants)
end
add_type(type.type.return_type, variance: variance, covariants: contravariants, contravariants: covariants)
end
when AST::Types::Union, AST::Types::Intersection, AST::Types::Tuple
type.types.each do |ty|
add_type(ty, variance: variance, covariants: covariants, contravariants: contravariants)
Expand Down
32 changes: 25 additions & 7 deletions lib/steep/type_construction.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2904,14 +2904,32 @@ def type_lambda(node, params_node:, body_node:, type_hint:)
block_annotations = source.annotations(block: node, factory: checker.factory, context: nesting)
params = TypeInference::BlockParams.from_node(params_node, annotations: block_annotations)

type_hint = deep_expand_alias(type_hint) if type_hint
if type_hint
original_hint = type_hint

case type_hint
when AST::Types::Proc
params_hint = type_hint.type.params
return_hint = type_hint.type.return_type
block_hint = type_hint.block
self_hint = type_hint.self_type
type_hint = deep_expand_alias(type_hint) || type_hint

procs = flatten_union(type_hint).select do |type|
check_relation(sub_type: type, super_type: AST::Builtin::Proc.instance_type).success?
end

proc_instances, proc_types = procs.partition {|type| AST::Builtin::Proc.instance_type?(type) }

case
when !proc_instances.empty? && proc_types.empty?
# `::Proc` is given as a hint
when proc_types.size == 1
# Proc type is given as a hint
hint_proc = proc_types[0] #: AST::Types::Proc
params_hint = hint_proc.type.params
return_hint = hint_proc.type.return_type
block_hint = hint_proc.block
self_hint = hint_proc.self_type
else
typing.add_error(
Diagnostic::Ruby::ProcHintIgnored.new(hint_type: original_hint, node: node)
)
end
end

block_constr = for_block(
Expand Down
11 changes: 11 additions & 0 deletions sig/steep/diagnostic/ruby.rbs
Original file line number Diff line number Diff line change
Expand Up @@ -530,6 +530,17 @@ module Steep
def header_line: () -> String
end

# Type hint is given to a proc/lambda but it was ignored
#
# 1. Because the hint is incompatible to `::Proc` type
# 2. More than one *proc type* is included in the hint
#
class ProcHintIgnored < Base
attr_reader hint_type: AST::Types::t

def initialize: (hint_type: AST::Types::t, node: Parser::AST::Node) -> void
end

# Argument forwarding `...` cannot be done safely, because of
#
# 1. The arguments are incompatible, or
Expand Down
18 changes: 9 additions & 9 deletions sig/steep/subtyping/variable_variance.rbs
Original file line number Diff line number Diff line change
@@ -1,25 +1,25 @@
module Steep
module Subtyping
class VariableVariance
attr_reader covariants: untyped
attr_reader covariants: Set[Symbol]

attr_reader contravariants: untyped
attr_reader contravariants: Set[Symbol]

def initialize: (covariants: untyped, contravariants: untyped) -> void
def initialize: (covariants: Set[Symbol], contravariants: Set[Symbol]) -> void

def covariant?: (untyped var) -> untyped
def covariant?: (Symbol var) -> bool

def contravariant?: (untyped var) -> untyped
def contravariant?: (Symbol var) -> bool

def invariant?: (untyped var) -> untyped
def invariant?: (Symbol var) -> bool

def self.from_type: (AST::Types::t) -> VariableVariance

def self.from_method_type: (untyped method_type) -> untyped
def self.from_method_type: (Interface::MethodType method_type) -> VariableVariance

def self.add_params: (untyped params, block: untyped, covariants: untyped, contravariants: untyped) -> untyped
def self.add_params: (Interface::Function::Params params, block: bool, covariants: Set[Symbol], contravariants: Set[Symbol]) -> void

def self.add_type: (untyped `type`, variance: untyped, covariants: untyped, contravariants: untyped) -> untyped
def self.add_type: (AST::Types::t `type`, variance: :covariant | :contravariant | :invariant, covariants: Set[Symbol], contravariants: Set[Symbol]) -> void
end
end
end
72 changes: 68 additions & 4 deletions test/type_construction_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4677,9 +4677,10 @@ def test_lambda2
with_standard_construction(checker, source) do |construction, typing|
construction.synthesize(source.node)

assert_equal 1, typing.errors.size
typing.errors[0].yield_self do |error|
assert_instance_of Diagnostic::Ruby::IncompatibleAssignment, error
assert_typing_error(typing, size: 2) do |errors|
assert_any!(errors) do |error|
assert_instance_of Diagnostic::Ruby::IncompatibleAssignment, error
end
end
end
end
Expand Down Expand Up @@ -8898,7 +8899,7 @@ def test_lambda_with_block_non_proc
with_standard_construction(checker, source) do |construction, typing|
type, _, _ = construction.synthesize(source.node)

assert_typing_error(typing, size: 1) do |errors|
assert_typing_error(typing, size: 2) do |errors|
assert_any!(errors) do |error|
assert_instance_of Diagnostic::Ruby::IncompatibleAssignment, error
end
Expand Down Expand Up @@ -10747,4 +10748,67 @@ class ReturnsProc
end
end
end

def test_type_hint__proc_optional
with_checker(<<~RBS) do |checker|
RBS
source = parse_ruby(<<~RUBY)
# @type var proc: nil | ^(Integer) -> String
proc = -> (x) do
x.hogehoge
end
RUBY

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

assert_typing_error(typing, size: 1) do |errors|
assert_any!(errors) do |error|
assert_instance_of Diagnostic::Ruby::NoMethod, error
assert_equal parse_type("::Integer"), error.type
end
end
end
end
end

def test_type_hint__proc_union_with_proc
with_checker(<<~RBS) do |checker|
RBS
source = parse_ruby(<<~RUBY)
# @type var proc: (^(Integer) -> String) | (^(String, String) -> Integer)
proc = -> (x) do
x.hogehoge
end
RUBY

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

assert_typing_error(typing, size: 1) do |errors|
assert_any!(errors) do |error|
assert_instance_of Diagnostic::Ruby::ProcHintIgnored, error
assert_equal parse_type("(^(::Integer) -> ::String) | (^(::String, ::String) -> ::Integer)"), error.hint_type
end
end
end
end
end

def test_type_hint__proc_union_proc_instance
with_checker(<<~RBS) do |checker|
RBS
source = parse_ruby(<<~RUBY)
# @type var proc: Proc
proc = -> (x) do
x.hogehoge
end
RUBY

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

0 comments on commit 56c911b

Please sign in to comment.