Skip to content

Commit

Permalink
Merge pull request #256 from soutaro/record-typing
Browse files Browse the repository at this point in the history
More flexible record typing
  • Loading branch information
soutaro committed Nov 14, 2020
2 parents 8cb1392 + 913b5e3 commit 24b27b5
Show file tree
Hide file tree
Showing 5 changed files with 113 additions and 17 deletions.
28 changes: 13 additions & 15 deletions lib/steep/subtyping/check.rb
Original file line number Diff line number Diff line change
Expand Up @@ -342,23 +342,21 @@ def check0(relation, self_type:, assumption:, trace:, constraints:)
constraints: constraints)

when relation.sub_type.is_a?(AST::Types::Record) && relation.super_type.is_a?(AST::Types::Record)
if Set.new(relation.sub_type.elements.keys).superset?(Set.new(relation.super_type.elements.keys))
keys = relation.super_type.elements.keys
type_pairs = keys.map {|key| [relation.sub_type.elements[key], relation.super_type.elements[key]] }
results = type_pairs.flat_map do |t1, t2|
relation = Relation.new(sub_type: t1, super_type: t2)
[check(relation, self_type: self_type, assumption: assumption, trace: trace, constraints: constraints),
check(relation.flip, self_type: self_type, assumption: assumption, trace: trace, constraints: constraints)]
end
keys = relation.super_type.elements.keys
relations = keys.map {|key|
Relation.new(
sub_type: relation.sub_type.elements[key] || AST::Builtin.nil_type,
super_type: relation.super_type.elements[key]
)
}
results = relations.map do |relation|
check(relation, self_type: self_type, assumption: assumption, trace: trace, constraints: constraints)
end

if results.all?(&:success?)
success(constraints: constraints)
else
results.find(&:failure?)
end
if results.all?(&:success?)
success(constraints: constraints)
else
failure(error: Result::Failure::UnknownPairError.new(relation: relation),
trace: trace)
results.find(&:failure?)
end

when relation.sub_type.is_a?(AST::Types::Record) && relation.super_type.is_a?(AST::Types::Name::Base)
Expand Down
2 changes: 2 additions & 0 deletions lib/steep/type_construction.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3491,6 +3491,8 @@ def try_array_type(node, hint)
end

def try_hash_type(node, hint)
hint = expand_alias(hint)

case hint
when AST::Types::Record
typing.new_child(node.loc.expression.yield_self {|l| l.begin_pos..l.end_pos }) do |child_typing|
Expand Down
7 changes: 6 additions & 1 deletion test/subtyping_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -617,13 +617,18 @@ def test_expand_alias
def test_hash
with_checker do |checker|
assert_success_check checker, "{ foo: ::Integer, bar: ::String }", "{ foo: ::Integer }"
assert_fail_check checker, "{ foo: ::String }", "{ foo: ::Integer }"
assert_success_check checker, "{ foo: ::String, bar: ::Integer }", "{ foo: ::String, bar: ::Integer? }"
assert_success_check checker, "{ foo: ::String, bar: nil }", "{ foo: ::String, bar: ::Integer? }"
assert_success_check checker, "{ foo: ::String }", "{ foo: ::String, bar: ::Integer? }"
assert_fail_check checker, "{ foo: ::String, bar: ::Symbol }", "{ foo: ::String, bar: ::Integer? }"

assert_success_check checker,
"{ foo: ::Integer }",
parse_type("{ foo: X }", checker: checker, variables: [:X]),
constraints: Constraints.new(unknowns: [:X]) do |result|
assert_operator result.constraints, :unknown?, :X
assert_equal "::Integer", result.constraints.upper_bound(:X).to_s
assert_equal parse_type("top", checker: checker), result.constraints.upper_bound(:X)
assert_equal "::Integer", result.constraints.lower_bound(:X).to_s
end

Expand Down
2 changes: 1 addition & 1 deletion test/test_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,7 @@ def end: -> A
class Regexp
end
class Array[A]
class Array[unchecked out A]
def initialize: () -> untyped
| (Integer, A) -> untyped
| (Integer) -> untyped
Expand Down
91 changes: 91 additions & 0 deletions test/type_construction_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6391,4 +6391,95 @@ def test_bool_typing
end
end
end

def test_typing_record
with_checker() do |checker|
source = parse_ruby(<<-RUBY)
# @type var a: { foo: String, bar: Integer?, baz: Symbol? }
a = { foo: "hello", bar: 42, baz: nil }
x = a[:foo]
y = a[:bar]
z = a[:baz]
RUBY

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

assert_no_error typing

assert_equal parse_type("::String"), context.lvar_env[:x]
assert_equal parse_type("::Integer?"), context.lvar_env[:y]
assert_equal parse_type("::Symbol?"), context.lvar_env[:z]
end
end
end

def test_typing_record_union
with_checker() do |checker|
source = parse_ruby(<<-RUBY)
# @type var x: { foo: String, bar: Integer } | String
x = { foo: "hello", bar: 42 }
x = "foo"
RUBY

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

assert_no_error typing
end
end
end

def test_typing_record_union_alias
with_checker(<<RBS) do |checker|
type foo_bar = { foo: String, bar: Integer } | String
RBS
source = parse_ruby(<<-RUBY)
# @type var x: foo_bar
x = { foo: "hello", bar: 42 }
x = "foo"
RUBY

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

assert_no_error typing
end
end
end

def test_typing_record_map1
with_checker() do |checker|
source = parse_ruby(<<-RUBY)
x = [1].map do |x|
# @type block: { foo: String, bar: Integer }
{ foo: "hello", bar: x }
end
RUBY

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

assert_no_error typing

assert_equal parse_type("::Array[{ foo: ::String, bar: ::Integer }]"), context.lvar_env[:x]
end
end
end

def test_typing_record_nilable_attribute
with_checker() do |checker|
source = parse_ruby(<<-RUBY)
# @type var x: { foo: Integer? }
x = { }
RUBY

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

assert_no_error typing
end
end
end
end

0 comments on commit 24b27b5

Please sign in to comment.