@@ -6,7 +6,7 @@ use crate::{
66 } ,
77 symbol:: {
88 class_symbol, known_module_symbol, symbol_from_bindings, symbol_from_declarations,
9- LookupError , LookupResult , Symbol , SymbolAndQualifiers ,
9+ Boundness , LookupError , LookupResult , Symbol , SymbolAndQualifiers ,
1010 } ,
1111 types:: {
1212 definition_expression_type, CallArguments , CallError , MetaclassCandidate , TupleType ,
@@ -383,6 +383,9 @@ impl<'db> Class<'db> {
383383 ///
384384 /// The attribute might also be defined in a superclass of this class.
385385 pub ( super ) fn instance_member ( self , db : & ' db dyn Db , name : & str ) -> SymbolAndQualifiers < ' db > {
386+ let mut union = UnionBuilder :: new ( db) ;
387+ let mut union_qualifiers = TypeQualifiers :: empty ( ) ;
388+
386389 for superclass in self . iter_mro ( db) {
387390 match superclass {
388391 ClassBase :: Dynamic ( _) => {
@@ -391,16 +394,43 @@ impl<'db> Class<'db> {
391394 ) ;
392395 }
393396 ClassBase :: Class ( class) => {
394- if let member @ SymbolAndQualifiers ( Symbol :: Type ( _ , _ ) , _ ) =
397+ if let member @ SymbolAndQualifiers ( Symbol :: Type ( ty , boundness ) , qualifiers ) =
395398 class. own_instance_member ( db, name)
396399 {
397- return member;
400+ // TODO: We could raise a diagnostic here if there are conflicting type qualifiers
401+ union_qualifiers = union_qualifiers. union ( qualifiers) ;
402+
403+ if boundness == Boundness :: Bound {
404+ if union. is_empty ( ) {
405+ // Short-circuit, no need to allocate inside the union builder
406+ return member;
407+ }
408+
409+ return SymbolAndQualifiers (
410+ Symbol :: bound ( union. add ( ty) . build ( ) ) ,
411+ union_qualifiers,
412+ ) ;
413+ }
414+
415+ // If we see a possibly-unbound symbol, we need to keep looking
416+ // higher up in the MRO.
417+ union = union. add ( ty) ;
398418 }
399419 }
400420 }
401421 }
402422
403- SymbolAndQualifiers ( Symbol :: Unbound , TypeQualifiers :: empty ( ) )
423+ if union. is_empty ( ) {
424+ SymbolAndQualifiers ( Symbol :: Unbound , TypeQualifiers :: empty ( ) )
425+ } else {
426+ // If we have reached this point, we know that we have only seen possibly-unbound symbols.
427+ // This means that the final result is still possibly-unbound.
428+
429+ SymbolAndQualifiers (
430+ Symbol :: Type ( union. build ( ) , Boundness :: PossiblyUnbound ) ,
431+ union_qualifiers,
432+ )
433+ }
404434 }
405435
406436 /// Tries to find declarations/bindings of an instance attribute named `name` that are only
@@ -409,16 +439,18 @@ impl<'db> Class<'db> {
409439 db : & ' db dyn Db ,
410440 class_body_scope : ScopeId < ' db > ,
411441 name : & str ,
412- inferred_type_from_class_body : Option < Type < ' db > > ,
442+ inferred_from_class_body : & Symbol < ' db > ,
413443 ) -> Symbol < ' db > {
414444 // If we do not see any declarations of an attribute, neither in the class body nor in
415445 // any method, we build a union of `Unknown` with the inferred types of all bindings of
416446 // that attribute. We include `Unknown` in that union to account for the fact that the
417447 // attribute might be externally modified.
418448 let mut union_of_inferred_types = UnionBuilder :: new ( db) . add ( Type :: unknown ( ) ) ;
449+ let mut union_boundness = Boundness :: Bound ;
419450
420- if let Some ( ty) = inferred_type_from_class_body {
421- union_of_inferred_types = union_of_inferred_types. add ( ty) ;
451+ if let Symbol :: Type ( ty, boundness) = inferred_from_class_body {
452+ union_of_inferred_types = union_of_inferred_types. add ( * ty) ;
453+ union_boundness = * boundness;
422454 }
423455
424456 let attribute_assignments = attribute_assignments ( db, class_body_scope) ;
@@ -427,10 +459,10 @@ impl<'db> Class<'db> {
427459 . as_deref ( )
428460 . and_then ( |assignments| assignments. get ( name) )
429461 else {
430- if inferred_type_from_class_body . is_some ( ) {
431- return Symbol :: bound ( union_of_inferred_types . build ( ) ) ;
462+ if inferred_from_class_body . is_unbound ( ) {
463+ return Symbol :: Unbound ;
432464 }
433- return Symbol :: Unbound ;
465+ return Symbol :: Type ( union_of_inferred_types . build ( ) , union_boundness ) ;
434466 } ;
435467
436468 for attribute_assignment in attribute_assignments {
@@ -484,7 +516,7 @@ impl<'db> Class<'db> {
484516 }
485517 }
486518
487- Symbol :: bound ( union_of_inferred_types. build ( ) )
519+ Symbol :: Type ( union_of_inferred_types. build ( ) , union_boundness )
488520 }
489521
490522 /// A helper function for `instance_member` that looks up the `name` attribute only on
@@ -493,7 +525,6 @@ impl<'db> Class<'db> {
493525 // TODO: There are many things that are not yet implemented here:
494526 // - `typing.Final`
495527 // - Proper diagnostics
496- // - Handling of possibly-undeclared/possibly-unbound attributes
497528
498529 let body_scope = self . body_scope ( db) ;
499530 let table = symbol_table ( db, body_scope) ;
@@ -504,7 +535,7 @@ impl<'db> Class<'db> {
504535 let declarations = use_def. public_declarations ( symbol_id) ;
505536
506537 match symbol_from_declarations ( db, declarations) {
507- Ok ( SymbolAndQualifiers ( Symbol :: Type ( declared_ty, _) , qualifiers) ) => {
538+ Ok ( SymbolAndQualifiers ( declared @ Symbol :: Type ( declared_ty, _) , qualifiers) ) => {
508539 // The attribute is declared in the class body.
509540
510541 if let Some ( function) = declared_ty. into_function_literal ( ) {
@@ -514,7 +545,7 @@ impl<'db> Class<'db> {
514545 if function. has_known_class_decorator ( db, KnownClass :: Classmethod )
515546 && function. decorators ( db) . len ( ) == 1
516547 {
517- SymbolAndQualifiers ( Symbol :: bound ( declared_ty ) , qualifiers)
548+ SymbolAndQualifiers ( declared , qualifiers)
518549 } else if function. has_known_class_decorator ( db, KnownClass :: Property ) {
519550 SymbolAndQualifiers :: todo ( "@property" )
520551 } else if function. has_known_function_decorator ( db, KnownFunction :: Overload )
@@ -523,10 +554,10 @@ impl<'db> Class<'db> {
523554 } else if !function. decorators ( db) . is_empty ( ) {
524555 SymbolAndQualifiers :: todo ( "decorated method" )
525556 } else {
526- SymbolAndQualifiers ( Symbol :: bound ( declared_ty ) , qualifiers)
557+ SymbolAndQualifiers ( declared , qualifiers)
527558 }
528559 } else {
529- SymbolAndQualifiers ( Symbol :: bound ( declared_ty ) , qualifiers)
560+ SymbolAndQualifiers ( declared , qualifiers)
530561 }
531562 }
532563 Ok ( SymbolAndQualifiers ( Symbol :: Unbound , _) ) => {
@@ -535,9 +566,8 @@ impl<'db> Class<'db> {
535566
536567 let bindings = use_def. public_bindings ( symbol_id) ;
537568 let inferred = symbol_from_bindings ( db, bindings) ;
538- let inferred_ty = inferred. ignore_possibly_unbound ( ) ;
539569
540- Self :: implicit_instance_attribute ( db, body_scope, name, inferred_ty ) . into ( )
570+ Self :: implicit_instance_attribute ( db, body_scope, name, & inferred ) . into ( )
541571 }
542572 Err ( ( declared_ty, _conflicting_declarations) ) => {
543573 // There are conflicting declarations for this attribute in the class body.
@@ -551,7 +581,7 @@ impl<'db> Class<'db> {
551581 // This attribute is neither declared nor bound in the class body.
552582 // It could still be implicitly defined in a method.
553583
554- Self :: implicit_instance_attribute ( db, body_scope, name, None ) . into ( )
584+ Self :: implicit_instance_attribute ( db, body_scope, name, & Symbol :: Unbound ) . into ( )
555585 }
556586 }
557587
0 commit comments