Skip to content
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
23 changes: 22 additions & 1 deletion crates/ty_python_semantic/resources/mdtest/narrow/hasattr.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ def f(x: Foo):
else:
reveal_type(x) # revealed: Foo

def y(x: Bar):
def g(x: Bar):
if hasattr(x, "spam"):
reveal_type(x) # revealed: Never
reveal_type(x.spam) # revealed: Never
Expand All @@ -35,4 +35,25 @@ def y(x: Bar):

# error: [unresolved-attribute]
reveal_type(x.spam) # revealed: Unknown

def returns_bool() -> bool:
return False

class Baz:
if returns_bool():
x: int = 42

def h(obj: Baz):
reveal_type(obj) # revealed: Baz
# error: [possibly-unbound-attribute]
reveal_type(obj.x) # revealed: int

if hasattr(obj, "x"):
reveal_type(obj) # revealed: Baz & <Protocol with members 'x'>
reveal_type(obj.x) # revealed: int
else:
reveal_type(obj) # revealed: Baz & ~<Protocol with members 'x'>

# TODO: should emit `[unresolved-attribute]` and reveal `Unknown`
reveal_type(obj.x) # revealed: @Todo(map_with_boundness: intersections with negative contributions)
```
14 changes: 7 additions & 7 deletions crates/ty_python_semantic/src/types/instance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use std::marker::PhantomData;

use super::protocol_class::ProtocolInterface;
use super::{ClassType, KnownClass, SubclassOfType, Type};
use crate::symbol::{Symbol, SymbolAndQualifiers};
use crate::symbol::{Boundness, Symbol, SymbolAndQualifiers};
use crate::types::{ClassLiteral, TypeMapping, TypeVarInstance};
use crate::{Db, FxOrderSet};

Expand Down Expand Up @@ -45,12 +45,12 @@ impl<'db> Type<'db> {
protocol: ProtocolInstanceType<'db>,
) -> bool {
// TODO: this should consider the types of the protocol members
// as well as whether each member *exists* on `self`.
protocol
.inner
.interface(db)
.members(db)
.all(|member| !self.member(db, member.name()).symbol.is_unbound())
protocol.inner.interface(db).members(db).all(|member| {
matches!(
self.member(db, member.name()).symbol,
Symbol::Type(_, Boundness::Bound)
)
})
}
}

Expand Down
Loading