@@ -87,6 +87,7 @@ use crate::binding::binding::KeyUndecoratedFunction;
8787use crate :: binding:: binding:: LastStmt ;
8888use crate :: binding:: binding:: LinkedKey ;
8989use crate :: binding:: binding:: NoneIfRecursive ;
90+ use crate :: binding:: binding:: PrivateAttributeAccessExpect ;
9091use crate :: binding:: binding:: RaisedException ;
9192use crate :: binding:: binding:: ReturnTypeKind ;
9293use crate :: binding:: binding:: SizeExpectation ;
@@ -1783,10 +1784,97 @@ impl<'a, Ans: LookupAnswer> AnswersSolver<'a, Ans> {
17831784 ) ;
17841785 }
17851786 }
1787+ BindingExpect :: PrivateAttributeAccess ( expectation) => {
1788+ self . check_private_attribute_access ( expectation, errors) ;
1789+ }
17861790 }
17871791 Arc :: new ( EmptyAnswer )
17881792 }
17891793
1794+ fn check_private_attribute_access (
1795+ & self ,
1796+ expect : & PrivateAttributeAccessExpect ,
1797+ errors : & ErrorCollector ,
1798+ ) {
1799+ let class_binding = self . get_idx ( expect. class_idx ) ;
1800+ let Some ( owner) = class_binding. 0 . as_ref ( ) else {
1801+ return ;
1802+ } ;
1803+ if expect
1804+ . self_name
1805+ . as_ref ( )
1806+ . is_some_and ( |name| Self :: expr_matches_name ( & expect. value , name) )
1807+ {
1808+ return ;
1809+ }
1810+ let value_type = self . expr_infer ( & expect. value , errors) ;
1811+ if self . private_attr_type_allows ( owner, & value_type) {
1812+ return ;
1813+ }
1814+ self . error (
1815+ errors,
1816+ expect. attr . range ( ) ,
1817+ ErrorInfo :: Kind ( ErrorKind :: NoAccess ) ,
1818+ format ! (
1819+ "Private attribute `{}` cannot be accessed outside of its defining class" ,
1820+ expect. attr. id
1821+ ) ,
1822+ ) ;
1823+ }
1824+
1825+ fn expr_matches_name ( expr : & Expr , expected : & Name ) -> bool {
1826+ matches ! ( expr, Expr :: Name ( name) if & name. id == expected)
1827+ }
1828+
1829+ fn private_attr_type_allows ( & self , owner : & Class , ty : & Type ) -> bool {
1830+ match ty {
1831+ Type :: ClassType ( cls) | Type :: SelfType ( cls) => {
1832+ self . has_superclass ( cls. class_object ( ) , owner)
1833+ }
1834+ Type :: ClassDef ( cls) => self . has_superclass ( cls, owner) ,
1835+ Type :: Union ( box union) => union
1836+ . members
1837+ . iter ( )
1838+ . all ( |member| self . private_attr_type_allows ( owner, member) ) ,
1839+ Type :: Intersect ( box ( members, fallback) ) => {
1840+ members
1841+ . iter ( )
1842+ . all ( |member| self . private_attr_type_allows ( owner, member) )
1843+ && self . private_attr_type_allows ( owner, fallback)
1844+ }
1845+ Type :: Type ( inner) => self . private_attr_type_allows ( owner, inner) ,
1846+ Type :: TypeAlias ( alias) => {
1847+ self . private_attr_type_allows ( owner, & alias. as_value ( self . stdlib ) )
1848+ }
1849+ Type :: Quantified ( q) | Type :: QuantifiedValue ( q) => {
1850+ self . private_attr_restriction_allows ( owner, q. restriction ( ) )
1851+ }
1852+ Type :: TypeVar ( var) => self . private_attr_restriction_allows ( owner, var. restriction ( ) ) ,
1853+ Type :: Literal ( lit) => match lit {
1854+ Lit :: Enum ( lit_enum) => self . has_superclass ( lit_enum. class . class_object ( ) , owner) ,
1855+ _ => false ,
1856+ } ,
1857+ Type :: Never ( _) => true ,
1858+ Type :: TypeGuard ( inner) | Type :: TypeIs ( inner) => {
1859+ self . private_attr_type_allows ( owner, inner)
1860+ }
1861+ _ => false ,
1862+ }
1863+ }
1864+
1865+ fn private_attr_restriction_allows ( & self , owner : & Class , restriction : & Restriction ) -> bool {
1866+ match restriction {
1867+ Restriction :: Bound ( bound) => self . private_attr_type_allows ( owner, bound) ,
1868+ Restriction :: Constraints ( constraints) => {
1869+ !constraints. is_empty ( )
1870+ && constraints
1871+ . iter ( )
1872+ . all ( |constraint| self . private_attr_type_allows ( owner, constraint) )
1873+ }
1874+ Restriction :: Unrestricted => false ,
1875+ }
1876+ }
1877+
17901878 /// Check if a module path should be skipped for indexing purposes.
17911879 /// Skips typeshed (bundled stdlib and third-party stubs) and site-packages (external libraries).
17921880 fn should_skip_module_for_indexing (
0 commit comments