Skip to content
Open
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
8 changes: 6 additions & 2 deletions pyrefly/lib/alt/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1974,9 +1974,13 @@ impl<'a, Ans: LookupAnswer> AnswersSolver<'a, Ans> {
}
}
ty.as_bool().or_else(|| {
// If the object defines `__bool__`, we can check if it returns a statically known value
// If the object defines `__bool__`, we can check if it returns a statically known value.
// Implicit dunder lookups (like the `__bool__` used for truthiness) are resolved on the
// type and do not go through `__getattr__`, so we disable the `__getattr__` fallback here.
// Otherwise a class with a `__getattr__` returning a non-callable type would be treated as
// defining a non-callable `__bool__` and wrongly reported as `not-callable` (see #4467).
if self
.type_of_magic_dunder_attr(ty, &dunder::BOOL, range, errors, None, "as_bool", true)?
.type_of_magic_dunder_attr(ty, &dunder::BOOL, range, errors, None, "as_bool", false)?
.is_never()
{
return None;
Expand Down
16 changes: 16 additions & 0 deletions pyrefly/lib/test/attributes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -944,6 +944,22 @@ def test(foo: Foo) -> None:
"#,
);

testcase!(
test_getattr_does_not_provide_dunder_bool,
r#"
# Implicit dunders like `__bool__` are looked up on the type and are not
# resolved through `__getattr__`, so a `__getattr__` returning a non-callable
# type must not make an instance look like it has a non-callable `__bool__`.
# Regression test for https://github.com/facebook/pyrefly/issues/4467
class Tensor: ...
class M:
def __getattr__(self, name: str) -> "Tensor | M": ...

def f(x: M | None) -> M:
return x or M()
"#,
);

testcase!(
test_object_setattr_wrong_signature,
r#"
Expand Down
Loading