File tree Expand file tree Collapse file tree
crates/ty_python_semantic Expand file tree Collapse file tree Original file line number Diff line number Diff line change @@ -392,8 +392,14 @@ from inspect import getattr_static
392392
393393def f_okay (c : Callable[[], None ]):
394394 if hasattr (c, " __qualname__" ):
395- c.__qualname__ # okay
395+ reveal_type(c.__qualname__ ) # revealed: object
396+
397+ # TODO : should be `property`
398+ # (or complain that we don't know that `type(c)` has the attribute at all!)
399+ reveal_type(type (c).__qualname__ ) # revealed: @Todo(Intersection meta-type)
400+
396401 # `hasattr` only guarantees that an attribute is readable.
402+ #
397403 # error: [invalid-assignment] "Object of type `Literal["my_callable"]` is not assignable to attribute `__qualname__` on type `(() -> None) & <Protocol with members '__qualname__'>`"
398404 c.__qualname__ = " my_callable"
399405
Original file line number Diff line number Diff line change @@ -84,3 +84,17 @@ def _(obj: MaybeWithSpam):
8484 # error: [possibly-unbound-attribute]
8585 reveal_type(obj.spam) # revealed: int
8686```
87+
88+ All attribute available on ` object ` are still available on these synthesized protocols, but
89+ attributes that are not present on ` object ` are not available:
90+
91+ ``` py
92+ def f (x : object ):
93+ if hasattr (x, " __qualname__" ):
94+ reveal_type(x.__repr__ ) # revealed: bound method object.__repr__() -> str
95+ reveal_type(x.__str__ ) # revealed: bound method object.__str__() -> str
96+ reveal_type(x.__dict__ ) # revealed: dict[str, Any]
97+
98+ # error: [unresolved-attribute] "Type `<Protocol with members '__qualname__'>` has no attribute `foo`"
99+ reveal_type(x.foo) # revealed: Unknown
100+ ```
Original file line number Diff line number Diff line change @@ -3409,6 +3409,23 @@ impl<'db> Type<'db> {
34093409
34103410 Type :: ModuleLiteral ( module) => module. static_member ( db, name_str) ,
34113411
3412+ // If a protocol does not include a member and the policy disables falling back to
3413+ // `object`, we return `Place::Unbound` here. This short-circuits attribute lookup
3414+ // before we find the "fallback to attribute access on `object`" logic later on
3415+ // (otherwise we would infer that all synthesized protocols have `__getattribute__`
3416+ // methods, and therefore that all synthesized protocols have all possible attributes.)
3417+ //
3418+ // Note that we could do this for *all* protocols, but it's only *necessary* for synthesized
3419+ // ones, and the standard logic is *probably* more performant for class-based protocols?
3420+ Type :: ProtocolInstance ( ProtocolInstanceType {
3421+ inner : Protocol :: Synthesized ( protocol) ,
3422+ ..
3423+ } ) if policy. mro_no_object_fallback ( )
3424+ && !protocol. interface ( ) . includes_member ( db, name_str) =>
3425+ {
3426+ Place :: Unbound . into ( )
3427+ }
3428+
34123429 _ if policy. no_instance_fallback ( ) => self . invoke_descriptor_protocol (
34133430 db,
34143431 name_str,
Original file line number Diff line number Diff line change @@ -227,7 +227,7 @@ impl<'db> ProtocolInterface<'db> {
227227 place : Place :: bound ( member. ty ( ) ) ,
228228 qualifiers : member. qualifiers ( ) ,
229229 } )
230- . unwrap_or_else ( || Type :: object ( db) . instance_member ( db, name) )
230+ . unwrap_or_else ( || Type :: object ( db) . member ( db, name) )
231231 }
232232
233233 /// Return `true` if if all members on `self` are also members of `other`.
You can’t perform that action at this time.
0 commit comments