@@ -47,7 +47,7 @@ use ty_python_semantic::semantic_index::definition::Definition;
4747use ty_python_semantic:: types:: TypeVarKind ;
4848use ty_python_semantic:: {
4949 HasType , SemanticModel , definitions_for_attribute, semantic_index:: definition:: DefinitionKind ,
50- types:: Type , types:: ide_support:: definition_for_name,
50+ types:: PropertyInstanceType , types :: Type , types:: ide_support:: definition_for_name,
5151} ;
5252
5353/// Semantic token types supported by the language server.
@@ -467,6 +467,7 @@ impl<'db> SemanticTokenVisitor<'db> {
467467 }
468468 }
469469
470+ let db = self . model . db ( ) ;
470471 let attr_name_str = attr_name. id . as_str ( ) ;
471472 let mut modifiers = SemanticTokenModifier :: empty ( ) ;
472473
@@ -475,7 +476,7 @@ impl<'db> SemanticTokenVisitor<'db> {
475476 }
476477
477478 let elements = if let Some ( union) = ty. as_union ( ) {
478- union. elements ( self . model . db ( ) )
479+ union. elements ( db )
479480 } else {
480481 std:: slice:: from_ref ( & ty)
481482 } ;
@@ -526,24 +527,34 @@ impl<'db> SemanticTokenVisitor<'db> {
526527 ( SemanticTokenType :: Variable , modifiers)
527528 }
528529
529- /// Check if an attribute access refers to a property by examining the attribute's
530- /// definition. This is needed because instance-level property access (e.g., `obj.prop`)
531- /// resolves through the descriptor protocol, so the inferred type is the property's
532- /// return type rather than a `PropertyInstance`.
533- fn is_property_from_definition ( & self , attr : & ast:: ExprAttribute ) -> bool {
530+ /// Returns the `PropertyInstanceType` for an attribute access if the attribute
531+ /// is a property, by examining the attribute's definition. This is needed because
532+ /// instance-level property access (e.g., `obj.prop`) resolves through the descriptor
533+ /// protocol, so the inferred type is the property's return type rather than a
534+ /// `PropertyInstance`.
535+ fn property_from_definition < ' a > (
536+ & self ,
537+ attr : & ast:: ExprAttribute ,
538+ ) -> Option < PropertyInstanceType < ' a > >
539+ where
540+ ' db : ' a ,
541+ {
534542 let db = self . model . db ( ) ;
535543 let definitions = definitions_for_attribute ( self . model , attr) ;
536- definitions. iter ( ) . any ( |resolved| {
537- let Some ( definition) = resolved. definition ( ) else {
538- return false ;
539- } ;
544+ definitions. iter ( ) . find_map ( |resolved| {
545+ let definition = resolved. definition ( ) ?;
540546 if let DefinitionKind :: Function ( func_ref) = definition. kind ( db) {
541547 let def_model = SemanticModel :: new ( db, definition. file ( db) ) ;
542548 let parsed = parsed_module ( db, definition. file ( db) ) . load ( db) ;
543549 let func_node = func_ref. node ( & parsed) ;
544- matches ! ( func_node. inferred_type( & def_model) , Some ( ty) if ty. is_property_instance( ) )
550+ if let Some ( Type :: PropertyInstance ( property) ) = func_node. inferred_type ( & def_model)
551+ {
552+ Some ( property)
553+ } else {
554+ None
555+ }
545556 } else {
546- false
557+ None
547558 }
548559 } )
549560 }
@@ -921,11 +932,16 @@ impl SourceOrderVisitor<'_> for SemanticTokenVisitor<'_> {
921932
922933 // Then add token for the attribute name (e.g., 'path' in 'os.path')
923934 let ty = expr. inferred_type ( self . model ) . unwrap_or ( Type :: unknown ( ) ) ;
924- let ( token_type, modifiers) = if self . is_property_from_definition ( attr) {
925- ( SemanticTokenType :: Property , SemanticTokenModifier :: empty ( ) )
926- } else {
927- self . classify_from_type_for_attribute ( ty, & attr. attr )
928- } ;
935+ let ( token_type, modifiers) =
936+ if let Some ( property) = self . property_from_definition ( attr) {
937+ let mut modifiers = SemanticTokenModifier :: empty ( ) ;
938+ if property. setter ( self . model . db ( ) ) . is_none ( ) {
939+ modifiers |= SemanticTokenModifier :: READONLY ;
940+ }
941+ ( SemanticTokenType :: Property , modifiers)
942+ } else {
943+ self . classify_from_type_for_attribute ( ty, & attr. attr )
944+ } ;
929945 self . add_token ( & attr. attr , token_type, modifiers) ;
930946 }
931947 ast:: Expr :: NumberLiteral ( _) => {
0 commit comments