Skip to content

Commit b037b1e

Browse files
fangyi-zhoumeta-codesync[bot]
authored andcommitted
Allow final class returns for Self
Summary: Returning a concrete class instance from a method annotated with `Self` is normally unsafe because `Self` can represent a subclass. Final classes are the sound exception: there can be no subclass for `Self` to stand for. This allows an exact final class instance to satisfy Self while keeping non-final concrete class returns rejected. The rule is intentionally narrow so the removed `ClassType` <: `SelfType` behavior does not come back for subclassable classes. Reviewed By: yangdanny97 Differential Revision: D104711160 fbshipit-source-id: cfa0eabd89fa117419db4380c20637f278774d87
1 parent b69c0ac commit b037b1e

3 files changed

Lines changed: 25 additions & 0 deletions

File tree

pyrefly/lib/solver/subset.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2026,6 +2026,11 @@ impl<'a, Ans: LookupAnswer> Subset<'a, Ans> {
20262026
{
20272027
self.is_subset_eq(&got, want)
20282028
}
2029+
(Type::ClassType(got), Type::SelfType(want))
2030+
if got == want && self.type_order.is_final(got.class_object()) =>
2031+
{
2032+
Ok(())
2033+
}
20292034
(Type::SelfType(_), Type::SelfType(_)) => Ok(()),
20302035
(Type::SelfType(got), _) => self.is_subset_eq(&Type::ClassType(got.clone()), want),
20312036
(Type::Tuple(l), Type::Tuple(u)) => self.is_subset_tuple(l, u),

pyrefly/lib/solver/type_order.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,10 @@ impl<'a, Ans: LookupAnswer> TypeOrder<'a, Ans> {
8585
self.0.get_metadata_for_class(cls).is_protocol()
8686
}
8787

88+
pub fn is_final(self, cls: &Class) -> bool {
89+
self.0.get_metadata_for_class(cls).is_final()
90+
}
91+
8892
pub fn is_new_type(self, cls: &Class) -> bool {
8993
self.0.get_metadata_for_class(cls).is_new_type()
9094
}

pyrefly/lib/test/typing_self.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,22 @@ class Shape:
209209
"#,
210210
);
211211

212+
testcase!(
213+
test_self_return_final_concrete_class,
214+
r#"
215+
from typing import Self, final
216+
217+
@final
218+
class Shape:
219+
def method(self) -> Self:
220+
return Shape()
221+
222+
@classmethod
223+
def cls_method(cls) -> Self:
224+
return Shape()
225+
"#,
226+
);
227+
212228
testcase!(
213229
test_self_in_class_body_expression,
214230
r#"

0 commit comments

Comments
 (0)