Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix type inference problem on generics #609

Merged
merged 3 commits into from
Jul 20, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions lib/steep/subtyping/constraints.rb
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,18 @@ def eliminate_variable(type, to:)
else
type
end
when AST::Types::Tuple
AST::Types::Tuple.new(
types: type.types.map {|ty| eliminate_variable(ty, to: AST::Builtin.any_type) },
location: type.location
)
when AST::Types::Record
AST::Types::Record.new(
elements: type.elements.transform_values {|ty| eliminate_variable(ty, to: AST::Builtin.any_type) },
location: type.location
)
when AST::Types::Proc
type.map_type {|ty| eliminate_variable(ty, to: AST::Builtin.any_type) }
else
type
end
Expand Down
4 changes: 4 additions & 0 deletions lib/steep/type_construction.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2731,6 +2731,10 @@ def type_lambda(node, params_node:, body_node:, type_hint:)
)

if expected_block_type = block_constr.block_context.body_type
type_vars = expected_block_type.free_variables
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the fix.

The type error is caused by type checking lambda return type with given hint, which was type variable.
The type variable in the hint doesn't make any sense, because the lambda will be type checked with the hint in next step. The change apply a substitution that replaces all type variables to untyped.

subst = Interface::Substitution.build(type_vars, type_vars.map { AST::Builtin.any_type })
expected_block_type = expected_block_type.subst(subst)

check_relation(sub_type: return_type, super_type: expected_block_type).else do |result|
block_constr.typing.add_error(
Diagnostic::Ruby::BlockBodyTypeMismatch.new(
Expand Down
31 changes: 16 additions & 15 deletions sig/steep/ast/types/proc.rbs
Original file line number Diff line number Diff line change
Expand Up @@ -4,41 +4,42 @@ module Steep
class Proc
attr_reader location: untyped

attr_reader type: untyped
attr_reader type: Interface::Function

attr_reader block: untyped
attr_reader block: Interface::Block?

def initialize: (type: untyped, block: untyped, ?location: untyped) -> void
def initialize: (type: Interface::Function, block: Interface::Block?, ?location: untyped) -> void

def ==: (untyped other) -> untyped
def ==: (untyped other) -> bool

def hash: () -> untyped
def hash: () -> Integer

alias eql? ==

def subst: (untyped s) -> untyped
def subst: (Interface::Substitution s) -> Proc

def to_s: () -> ::String

def free_variables: () -> untyped
def free_variables: () -> Set[Symbol]

include Helper::ChildrenLevel

def level: () -> untyped
def level: () -> Array[Integer]

def closed?: () -> untyped
def closed?: () -> bool

def with_location: (untyped new_location) -> untyped
def with_location: (untyped new_location) -> Proc

def map_type: () ?{ () -> untyped } -> untyped
def map_type: () { (AST::Types::t) -> AST::Types::t } -> Proc

def one_arg?: () -> untyped
def one_arg?: () -> bool

def back_type: () -> untyped
def back_type: () -> AST::Types::t

def block_required?: () -> untyped
def block_required?: () -> bool

def each_child: () ?{ () -> untyped } -> untyped
def each_child: () { (AST::Types::t) -> void } -> void
| () -> Enumerator[AST::Types::t, void]
end
end
end
Expand Down
2 changes: 1 addition & 1 deletion sig/steep/subtyping/constraints.rbs
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ module Steep

def add: (untyped var, ?sub_type: untyped?, ?super_type: untyped?, ?skip: bool) -> untyped

def eliminate_variable: (untyped `type`, to: untyped) -> untyped
def eliminate_variable: (AST::Types::t `type`, to: AST::Types::t) -> AST::Types::t

def unknown?: (untyped var) -> untyped

Expand Down
4 changes: 2 additions & 2 deletions sig/steep/type_construction.rbs
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ module Steep

def synthesize: (Parser::AST::Node node, ?hint: AST::Types::t?, ?condition: bool) -> Pair

def check: (Parser::AST::Node node, untyped `type`, ?constraints: untyped) { (untyped, untyped, untyped) -> untyped } -> bool
def check: (Parser::AST::Node node, AST::Types::t `type`, ?constraints: Subtyping::Constraints) { (AST::Types::t, AST::Types::t, Subtyping::Result::Base) -> void } -> Pair

def masgn_lhs?: (untyped lhs) -> untyped

Expand Down Expand Up @@ -169,7 +169,7 @@ module Steep

def try_method_type: (Parser::AST::Node node, receiver_type: AST::Types::t, method_name: Symbol, method_type: Interface::MethodType, arguments: Array[Parser::AST::Node], block_params: Parser::AST::Node?, block_body: Parser::AST::Node?, topdown_hint: untyped) -> [TypeInference::MethodCall::t, TypeConstruction]

def type_check_argument: (untyped node, receiver_type: untyped, type: untyped, constraints: untyped, errors: untyped, ?report_node: untyped) -> untyped
def type_check_argument: (Parser::AST::Node node, receiver_type: AST::Types::t, type: AST::Types::t, constraints: Subtyping::Constraints, errors: Array[Diagnostic::Ruby::Base], ?report_node: Parser::AST::Node) -> Pair

def type_block_without_hint: (node: untyped, block_annotations: untyped, block_params: untyped, block_body: untyped) { () -> untyped } -> untyped

Expand Down
20 changes: 20 additions & 0 deletions test/type_construction_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9616,4 +9616,24 @@ def test_assignment_in_block
end
end
end

def test_issue_595_generics_type_inference
with_checker(<<-RBS) do |checker|
class ConfigurationReader
def read: [T](T, ^() -> T) -> T
end
RBS
source = parse_ruby(<<-RUBY)
reader = ConfigurationReader.new
reader.read("123", -> () { 123 })
RUBY

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

assert_no_error typing
assert_equal parse_type("::Integer | ::String"), type
end
end
end
end