Repository: astral-sh/ruff
File: crates/ty_python_semantic/src/semantic_model.rs (Rust)

Code before:
                self.expr_in_ast(&Expr::StringLiteral(string_expr.clone()))
                    .clone(),
            )),
        };
        Some((ast, model))
    }

    /// Returns the type qualifiers (e.g. `Final`, `ClassVar`) for a given expression,
    /// if the expression refers to a name or attribute with declared qualifiers.
    pub fn type_qualifiers(&self, expr: ExprRef<'_>) -> TypeQualifiers {
        match expr {
            ExprRef::Name(name) => {
                let index = semantic_index(self.db, self.file);
                let Some(file_scope) = index.try_expression_scope_id(&self.expr_ref_in_ast(expr))
                else {
                    return TypeQualifiers::empty();
                };
                let scope = file_scope.to_scope_id(self.db, self.file);
                let symbol = crate::semantic_index::symbol::Symbol::new(name.id.clone());
                crate::place::place(
                    self.db,
                    scope,
                    (&symbol).into(),
                    crate::place::ConsideredDefinitions::AllReachable,
                )
                .qualifiers
            }
            ExprRef::Attribute(attr) => {
                let Some(value_ty) = attr.value.inferred_type(self) else {
                    return TypeQualifiers::empty();
                };
                value_ty
                    .member_lookup_with_policy(
                        self.db,
                        attr.attr.id.clone(),
                        crate::types::MemberLookupPolicy::default(),
                    )
                    .qualifiers
            }
            _ => TypeQualifiers::empty(),
        }
    }
}

/// The type and definition of a symbol.
#[derive(Clone, Debug)]
pub struct MemberDefinition<'db> {
    pub ty: Type<'db>,
    pub first_reachable_definition: Definition<'db>,
}


Review comment (suggestion):
It seems to me that this will only work in a limited set of cases where the symbol is defined in the same scope. For example, consider something like:
```py
from typing import Final

x: Final[int] = 1

def foo():
    global x
    x<CURSOR>
```
(or a case where we import `x` from a different module)

Calling `try_expression_scope_id` on `x` inside `foo` will give you `foo`'s scope, but the definition is in the global scope. So `place` will not find any reachable definitions here.

I'm not really familiar with the on-hover part of the codebase, but it looks like we should invoke the full name resolution algorithm (see `TypeInferenceBuilder::infer_place_load`)? Apparently `ide_support::definition(s)_for_name` serves a similar role for IDE features? Something like

```suggestion
                let Some(definition) =
                    definition_for_name(self, name, ImportAliasResolution::ResolveAliases)
                else {
                    return TypeQualifiers::empty();
                };
                let module = parsed_module(self.db, self.file).load(self.db);
                if !definition
                    .kind(self.db)
                    .category(self.file.is_stub(self.db), &module)
                    .is_declaration()
                {
                    return TypeQualifiers::empty();
                }
                declaration_type(self.db, definition).qualifiers()
```

Code after:
            file: self.file,
            // Use expr_in_ast here because we might be entering a sub-sub-AST
            in_string_annotation_expr: Some(Box::new(
                self.expr_in_ast(&Expr::StringLiteral(string_expr.clone()))
                    .clone(),
            )),
        };
        Some((ast, model))
    }

    /// Returns the type qualifiers (e.g. `Final`, `ClassVar`) for a given expression,
    /// if the expression refers to a name or attribute with declared qualifiers.
    pub fn type_qualifiers(&self, expr: ExprRef<'_>) -> TypeQualifiers {
        match expr {
            ExprRef::Name(name) => {
                let Some(definition) =
                    definition_for_name(self, name, ImportAliasResolution::ResolveAliases)
                else {
                    return TypeQualifiers::empty();
                };
                let module = parsed_module(self.db, self.file).load(self.db);
                if !definition
                    .kind(self.db)
                    .category(self.file.is_stub(self.db), &module)
                    .is_declaration()
                {
                    return TypeQualifiers::empty();
                }
                declaration_type(self.db, definition).qualifiers()
            }
            ExprRef::Attribute(attr) => {
                let Some(value_ty) = attr.value.inferred_type(self) else {
                    return TypeQualifiers::empty();
                };
                value_ty
                    .member_lookup_with_policy(
                        self.db,
                        attr.attr.id.clone(),
                        crate::types::MemberLookupPolicy::default(),
                    )
                    .qualifiers
            }
            _ => TypeQualifiers::empty(),
        }
    }
}

/// The type and definition of a symbol.
#[derive(Clone, Debug)]
pub struct MemberDefinition<'db> {
    pub ty: Type<'db>,