Skip to content

Commit 469dc6f

Browse files
committed
[ty] Set readonly modifier for properties from definition path
Add readonly modifier when a property's setter is None in the is_property_from_definition code path (line 925). Also fix missing db binding in classify_from_type_for_attribute.
1 parent 6f01ec3 commit 469dc6f

1 file changed

Lines changed: 34 additions & 18 deletions

File tree

crates/ty_ide/src/semantic_tokens.rs

Lines changed: 34 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ use ty_python_semantic::semantic_index::definition::Definition;
4747
use ty_python_semantic::types::TypeVarKind;
4848
use 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

Comments
 (0)