[ty] Improve semantic token classification for names (#21399)

This commit is contained in:
Micha Reiser 2025-11-12 17:34:26 +01:00 committed by GitHub
parent 84c3cecad6
commit 43427abb61
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 389 additions and 221 deletions

View File

@ -2644,9 +2644,17 @@ def ab(a: int, *, c: int):
assert_snapshot!(test.hover(), @r"
int | float
---------------------------------------------
Convert a string or number to a floating-point number, if possible.
---------------------------------------------
```python
int | float
```
---
```text
Convert a string or number to a floating-point number, if possible.
```
---------------------------------------------
info[hover]: Hovered content is

File diff suppressed because it is too large Load Diff

View File

@ -85,6 +85,7 @@ where
///
/// This method may panic or produce unspecified results if the provided module is from a
/// different file or Salsa revision than the module to which the node belongs.
#[track_caller]
pub fn node<'ast>(&self, module_ref: &'ast ParsedModuleRef) -> &'ast T {
#[cfg(debug_assertions)]
assert_eq!(module_ref.module().addr(), self.module_addr);

View File

@ -1034,7 +1034,7 @@ impl<'db> AssignmentDefinitionKind<'db> {
self.target_kind
}
pub(crate) fn value<'ast>(&self, module: &'ast ParsedModuleRef) -> &'ast ast::Expr {
pub fn value<'ast>(&self, module: &'ast ParsedModuleRef) -> &'ast ast::Expr {
self.value.node(module)
}

View File

@ -761,6 +761,7 @@ pub(crate) struct DeclarationsIterator<'map, 'db> {
inner: LiveDeclarationsIterator<'map>,
}
#[derive(Debug)]
pub(crate) struct DeclarationWithConstraint<'db> {
pub(crate) declaration: DefinitionState<'db>,
pub(crate) reachability_constraint: ScopedReachabilityConstraintId,

View File

@ -869,7 +869,7 @@ impl<'db> Type<'db> {
matches!(self, Type::Dynamic(DynamicType::Todo(_)))
}
pub(crate) const fn is_generic_alias(&self) -> bool {
pub const fn is_generic_alias(&self) -> bool {
matches!(self, Type::GenericAlias(_))
}
@ -1080,12 +1080,11 @@ impl<'db> Type<'db> {
.expect("Expected a Type::ClassLiteral variant")
}
pub(crate) const fn is_subclass_of(&self) -> bool {
pub const fn is_subclass_of(&self) -> bool {
matches!(self, Type::SubclassOf(..))
}
#[cfg(test)]
pub(crate) const fn is_class_literal(&self) -> bool {
pub const fn is_class_literal(&self) -> bool {
matches!(self, Type::ClassLiteral(..))
}
@ -8585,7 +8584,7 @@ impl<'db> TypeVarInstance<'db> {
self.identity(db).definition(db)
}
pub(crate) fn kind(self, db: &'db dyn Db) -> TypeVarKind {
pub fn kind(self, db: &'db dyn Db) -> TypeVarKind {
self.identity(db).kind(db)
}

View File

@ -477,32 +477,17 @@ pub fn all_members<'db>(db: &'db dyn Db, ty: Type<'db>) -> FxHashSet<Member<'db>
/// Get the primary definition kind for a name expression within a specific file.
/// Returns the first definition kind that is reachable for this name in its scope.
/// This is useful for IDE features like semantic tokens.
pub fn definition_kind_for_name<'db>(
pub fn definition_for_name<'db>(
db: &'db dyn Db,
file: File,
name: &ast::ExprName,
) -> Option<DefinitionKind<'db>> {
let index = semantic_index(db, file);
let name_str = name.id.as_str();
// Get the scope for this name expression
let file_scope = index.expression_scope_id(&ast::ExprRef::from(name));
// Get the place table for this scope
let place_table = index.place_table(file_scope);
// Look up the place by name
let symbol_id = place_table.symbol_id(name_str)?;
// Get the use-def map and look up definitions for this place
let declarations = index
.use_def_map(file_scope)
.all_reachable_symbol_declarations(symbol_id);
) -> Option<Definition<'db>> {
let definitions = definitions_for_name(db, file, name);
// Find the first valid definition and return its kind
for declaration in declarations {
if let Some(def) = declaration.declaration.definition() {
return Some(def.kind(db).clone());
for declaration in definitions {
if let Some(def) = declaration.definition() {
return Some(def);
}
}
@ -617,7 +602,7 @@ pub fn definitions_for_name<'db>(
// If we didn't find any definitions in scopes, fallback to builtins
if resolved_definitions.is_empty() {
let Some(builtins_scope) = builtins_module_scope(db) else {
return Vec::new();
return resolved_definitions;
};
// Special cases for `float` and `complex` in type annotation positions.
@ -633,11 +618,14 @@ pub fn definitions_for_name<'db>(
return union
.elements(db)
.iter()
// Use `rev` so that `complex` and `float` come first.
// This is required for hover to pick up the docstring of `complex` and `float`
// instead of `int` (hover only shows the docstring of the first definition).
.rev()
.filter_map(|ty| ty.as_nominal_instance())
.map(|instance| {
let definition = instance.class_literal(db).definition(db);
let parsed = parsed_module(db, definition.file(db));
ResolvedDefinition::FileWithRange(definition.focus_range(db, &parsed.load(db)))
ResolvedDefinition::Definition(definition)
})
.collect();
}
@ -1243,6 +1231,14 @@ mod resolve_definition {
}
impl<'db> ResolvedDefinition<'db> {
pub(crate) fn definition(&self) -> Option<Definition<'db>> {
match self {
ResolvedDefinition::Definition(definition) => Some(*definition),
ResolvedDefinition::Module(_) => None,
ResolvedDefinition::FileWithRange(_) => None,
}
}
fn file(&self, db: &'db dyn Db) -> File {
match self {
ResolvedDefinition::Definition(definition) => definition.file(db),