Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
5 changes: 2 additions & 3 deletions crates/ty_python_semantic/resources/mdtest/call/methods.md
Comment thread
AlexWaygood marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -229,9 +229,8 @@ def _(a: object, b: SupportsStr, c: Falsy, d: AlwaysFalsy, e: None, f: Foo | Non
b.__str__()
c.__str__()
d.__str__()
# TODO: these should not error
e.__str__() # error: [missing-argument]
f.__str__() # error: [missing-argument]
e.__str__()
f.__str__()
```

## Error cases: Calling `__get__` for methods
Expand Down
2 changes: 1 addition & 1 deletion crates/ty_python_semantic/src/place.rs
Original file line number Diff line number Diff line change
Expand Up @@ -267,7 +267,7 @@ impl<'db> Place<'db> {

Place::Defined(defined) => {
if let Some((dunder_get_return_ty, _)) =
defined.ty.try_call_dunder_get(db, Type::none(db), owner)
defined.ty.try_call_dunder_get(db, None, owner)
{
Place::Defined(DefinedPlace {
ty: dunder_get_return_ty,
Expand Down
48 changes: 30 additions & 18 deletions crates/ty_python_semantic/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2750,13 +2750,15 @@ impl<'db> Type<'db> {
pub(crate) fn try_call_dunder_get(
self,
db: &'db dyn Db,
instance: Type<'db>,
instance: Option<Type<'db>>,
owner: Type<'db>,
) -> Option<(Type<'db>, AttributeKind)> {
tracing::trace!(
"try_call_dunder_get: {}, {}, {}",
self.display(db),
instance.display(db),
instance
.map(|i| i.display(db).to_string())
.unwrap_or_else(|| "None".to_string()),
Comment thread
sharkdp marked this conversation as resolved.
Outdated
owner.display(db)
);
match self {
Expand All @@ -2776,22 +2778,36 @@ impl<'db> Type<'db> {
// method of these synthesized functions. The method-wrapper would then be returned from
// `find_name_in_mro` when called on function-like `Callable`s. This would allow us to
// correctly model the behavior of *explicit* `SomeDataclass.__init__.__get__` calls.
return if instance.is_none(db) && callable.is_function_like(db) {
return if instance.is_none() && callable.is_function_like(db) {
Some((self, AttributeKind::NormalOrNonDataDescriptor))
} else {
// For classmethod-like callables, bind to the owner class. For function-like callables, bind to the instance.
let self_type = if callable.is_classmethod_like(db) && instance.is_none(db) {
let self_type = instance.unwrap_or_else(|| {
// For classmethod-like callables, bind to the owner class.
owner.to_instance(db).unwrap_or(owner)
} else {
instance
};
});

Some((
Type::Callable(callable.bind_self(db, Some(self_type))),
AttributeKind::NormalOrNonDataDescriptor,
))
};
}
Type::FunctionLiteral(function)
if instance.is_some_and(|ty| ty.is_none(db))
&& !function.is_staticmethod(db)
&& !function.is_classmethod(db) =>
{
// When the instance is of type `None` (`NoneType`), we must handle
// `FunctionType.__get__` here rather than falling through to the generic
// `__get__` path. The stubs for `FunctionType.__get__` use an overload
// with `instance: None` to indicate class-level access (returning the
// unbound function). This incorrectly matches when the instance is actually
// an instance of `None`
return Some((
Type::BoundMethod(BoundMethodType::new(db, function, instance.unwrap())),
AttributeKind::NormalOrNonDataDescriptor,
));
}
_ => {}
}

Expand All @@ -2803,8 +2819,9 @@ impl<'db> Type<'db> {
..
}) = descr_get
{
let instance_ty = instance.unwrap_or_else(|| Type::none(db));
let return_ty = descr_get
.try_call(db, &CallArguments::positional([self, instance, owner]))
.try_call(db, &CallArguments::positional([self, instance_ty, owner]))
.map(|bindings| {
if descr_get_boundness == Definedness::AlwaysDefined {
bindings.return_type(db)
Expand Down Expand Up @@ -2834,7 +2851,7 @@ impl<'db> Type<'db> {
fn try_call_dunder_get_on_attribute(
db: &'db dyn Db,
attribute: PlaceAndQualifiers<'db>,
instance: Type<'db>,
instance: Option<Type<'db>>,
owner: Type<'db>,
) -> (PlaceAndQualifiers<'db>, AttributeKind) {
match attribute {
Expand Down Expand Up @@ -3023,7 +3040,7 @@ impl<'db> Type<'db> {
) = Self::try_call_dunder_get_on_attribute(
db,
self.class_member_with_policy(db, name.into(), member_policy),
self,
Some(self),
self.to_meta_type(db),
);

Expand Down Expand Up @@ -3498,13 +3515,8 @@ impl<'db> Type<'db> {
let class_attr_plain =
class_attr_plain.map_type(|ty| ty.bind_self_typevars(db, self_instance));

let class_attr_fallback = Self::try_call_dunder_get_on_attribute(
db,
class_attr_plain,
Type::none(db),
self,
)
.0;
let class_attr_fallback =
Self::try_call_dunder_get_on_attribute(db, class_attr_plain, None, self).0;

let result = self.invoke_descriptor_protocol(
db,
Expand Down
20 changes: 3 additions & 17 deletions crates/ty_python_semantic/src/types/bound_super.rs
Original file line number Diff line number Diff line change
Expand Up @@ -691,37 +691,23 @@ impl<'db> BoundSuperType<'db> {
// Also, invoking a descriptor on a dynamic attribute is meaningless, so we don't handle this.
SuperOwnerKind::Dynamic(_) => None,
SuperOwnerKind::Class(_) => Some(
Type::try_call_dunder_get_on_attribute(
db,
attribute,
Type::none(db),
owner.owner_type(db),
)
.0,
Type::try_call_dunder_get_on_attribute(db, attribute, None, owner.owner_type(db)).0,
),
SuperOwnerKind::Instance(_) | SuperOwnerKind::InstanceTypeVar(..) => {
let owner_type = owner.owner_type(db);
Some(
Type::try_call_dunder_get_on_attribute(
db,
attribute,
owner_type,
Some(owner_type),
owner_type.to_meta_type(db),
)
.0,
)
}
SuperOwnerKind::ClassTypeVar(..) => {
let owner_type = owner.owner_type(db);
Some(
Type::try_call_dunder_get_on_attribute(
db,
attribute,
Type::none(db),
owner_type,
)
.0,
)
Some(Type::try_call_dunder_get_on_attribute(db, attribute, None, owner_type).0)
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion crates/ty_python_semantic/src/types/class.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3266,7 +3266,7 @@ impl<'db> StaticClassLiteral<'db> {

if let Some(ref mut default_ty) = default_ty {
*default_ty = default_ty
.try_call_dunder_get(db, Type::none(db), Type::from(self))
.try_call_dunder_get(db, None, Type::from(self))
.map(|(return_ty, _)| return_ty)
.unwrap_or_else(Type::unknown);
}
Expand Down
Loading