From ae6dd04910fb0e53434b714c2ccce92b1c4e47f1 Mon Sep 17 00:00:00 2001 From: David Peter Date: Wed, 28 May 2025 09:58:52 +0200 Subject: [PATCH] =?UTF-8?q?[ty]=20Type=20inference=20for=20hasattr(?= =?UTF-8?q?=E2=80=A6)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ty_python_semantic/src/types/call/bind.rs | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/crates/ty_python_semantic/src/types/call/bind.rs b/crates/ty_python_semantic/src/types/call/bind.rs index 67a8721be0..672aa5385a 100644 --- a/crates/ty_python_semantic/src/types/call/bind.rs +++ b/crates/ty_python_semantic/src/types/call/bind.rs @@ -655,6 +655,26 @@ impl<'db> Bindings<'db> { } } + Some(KnownFunction::HasAttr) => { + if let [Some(obj), Some(Type::StringLiteral(attr_name))] = + overload.parameter_types() + { + match obj.member(db, attr_name.value(db)).symbol { + Symbol::Type(_, Boundness::Bound) => { + overload.set_return_type(Type::BooleanLiteral(true)); + } + Symbol::Type(_, Boundness::PossiblyUnbound) => { + // Fall back to bool (from typeshed) + } + Symbol::Unbound => { + // Returning `Literal[False]` here seems potentially + // dangerous. The attribute could have been added + // dynamically, so fall back to `bool` here to be safe. + } + } + } + } + Some(KnownFunction::IsProtocol) => { if let [Some(ty)] = overload.parameter_types() { overload.set_return_type(Type::BooleanLiteral(