Skip to content

Commit e901841

Browse files
authored
Build exhaustiveness support for type members (sorbet#9535)
* Add a test showing behavior before * Build exhaustiveness support for type members The key observation is that if we just return the `droppedFromUpper`, then that means that we're not returning something that's a subtype of the input. To make it a subtype of the input, we have to use `T.all` to remember that it started as a type member, and also that there is a more narrow bound. * This test improves In sorbet#9505, I wrote: > we can't easily fix this one, so I didn't go out of my way to make it > work. Little did I know that it was actually not that hard.
1 parent bff98ae commit e901841

File tree

4 files changed

+64
-1
lines changed

4 files changed

+64
-1
lines changed

core/Types.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -858,6 +858,8 @@ TYPE(AndType) final : public Refcounted {
858858
friend TypePtr Types::lub(const GlobalState &gs, const TypePtr &t1, const TypePtr &t2);
859859
friend TypePtr Types::glb(const GlobalState &gs, const TypePtr &t1, const TypePtr &t2);
860860
friend TypePtr Types::unwrapSelfTypeParam(Context ctx, const TypePtr &t1);
861+
friend TypePtr Types::dropSubtypesOf(const GlobalState &gs, const TypePtr &from,
862+
absl::Span<const ClassOrModuleRef> klasses);
861863

862864
static TypePtr make_shared(const TypePtr &left, const TypePtr &right);
863865
};

core/types/types.cc

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,25 @@ TypePtr Types::dropSubtypesOf(const GlobalState &gs, const TypePtr &from, absl::
247247
result = from;
248248
}
249249
},
250+
[&](const SelfTypeParam &p) {
251+
if (!p.definition.isTypeMember()) {
252+
// Only type members have upper bounds--type parameters do not
253+
result = from;
254+
return;
255+
}
256+
257+
auto &lambdaParam = cast_type_nonnull<LambdaParam>(p.definition.asTypeMemberRef().data(gs)->resultType);
258+
auto droppedFromUpper = dropSubtypesOf(gs, lambdaParam.upperBound, klasses);
259+
if (droppedFromUpper.isBottom()) {
260+
result = Types::bottom();
261+
} else if (droppedFromUpper == lambdaParam.upperBound) {
262+
result = from;
263+
} else if (droppedFromUpper.kind() <= from.kind()) {
264+
result = AndType::make_shared(droppedFromUpper, from);
265+
} else {
266+
result = AndType::make_shared(from, droppedFromUpper);
267+
}
268+
},
250269
[&](const TypePtr &) {
251270
if (is_proxy_type(from) && dropSubtypesOf(gs, from.underlying(gs), klasses).isBottom()) {
252271
result = Types::bottom();
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# typed: true
2+
3+
class Parent
4+
extend T::Sig, T::Helpers
5+
abstract!
6+
sealed!
7+
8+
class Child1 < Parent; end
9+
class Child2 < Parent; end
10+
11+
sig { returns(T.attached_class) }
12+
def self.make_and_inspect
13+
inst = self.new
14+
T.reveal_type(inst) # error: `T.attached_class (of Parent)`
15+
case inst
16+
when Child1
17+
T.reveal_type(inst) # error: `T.all(Parent::Child1, T.attached_class (of Parent))`
18+
when Child2
19+
T.reveal_type(inst) # error: `T.all(Parent::Child2, T.attached_class (of Parent))`
20+
else
21+
T.absurd(inst)
22+
end
23+
end
24+
25+
sig { returns(T.attached_class) }
26+
def self.make_and_inspect_partial
27+
inst = self.new
28+
T.reveal_type(inst) # error: `T.attached_class (of Parent)`
29+
case inst
30+
when Child1
31+
T.reveal_type(inst) # error: `T.all(Parent::Child1, T.attached_class (of Parent))`
32+
else
33+
T.absurd(inst) # error: Control flow could reach `T.absurd` because the type `T.all(Parent::Child2, T.attached_class (of Parent))` wasn't handled
34+
end
35+
end
36+
end

test/testdata/infer/kwsplat_nil.rb

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,12 @@ def example(hash, maybe_hash_tm)
2121

2222
takes_kwargs(**hash)
2323

24-
takes_kwargs(**maybe_hash_tm) # error: Method `to_hash` does not exist on `NilClass` component
24+
takes_kwargs(**maybe_hash_tm)
25+
26+
case maybe_hash_tm
27+
when nil
28+
else
29+
T.reveal_type(maybe_hash_tm) # error: `T.all(T::Hash[T.untyped, T.untyped], Example::MaybeHash)`
30+
end
2531
end
2632
end

0 commit comments

Comments
 (0)