mirror of https://github.com/astral-sh/ruff
allow looking up types of identifiers
This commit is contained in:
parent
05d053376b
commit
7ca384d0de
|
|
@ -334,15 +334,24 @@ impl GotoTarget<'_> {
|
||||||
let (_, ty) = ty_python_semantic::definitions_for_unary_op(model, expression)?;
|
let (_, ty) = ty_python_semantic::definitions_for_unary_op(model, expression)?;
|
||||||
ty
|
ty
|
||||||
}
|
}
|
||||||
// TODO: Support identifier targets
|
GotoTarget::PatternMatchRest(pattern) => {
|
||||||
GotoTarget::PatternMatchRest(_)
|
model.inferred_type_for_identifier(pattern.rest.as_ref()?)
|
||||||
| GotoTarget::PatternKeywordArgument(_)
|
}
|
||||||
| GotoTarget::PatternMatchStarName(_)
|
GotoTarget::PatternKeywordArgument(pattern) => {
|
||||||
| GotoTarget::PatternMatchAsName(_)
|
model.inferred_type_for_identifier(&pattern.attr)
|
||||||
| GotoTarget::TypeParamParamSpecName(_)
|
}
|
||||||
| GotoTarget::TypeParamTypeVarTupleName(_)
|
GotoTarget::PatternMatchStarName(pattern) => {
|
||||||
| GotoTarget::NonLocal { .. }
|
model.inferred_type_for_identifier(pattern.name.as_ref()?)
|
||||||
| GotoTarget::Globals { .. } => return None,
|
}
|
||||||
|
GotoTarget::PatternMatchAsName(pattern) => {
|
||||||
|
model.inferred_type_for_identifier(pattern.name.as_ref()?)
|
||||||
|
}
|
||||||
|
GotoTarget::NonLocal { identifier } => model.inferred_type_for_identifier(identifier),
|
||||||
|
GotoTarget::Globals { identifier } => model.inferred_type_for_identifier(identifier),
|
||||||
|
// These don't really... *have* a type?
|
||||||
|
GotoTarget::TypeParamParamSpecName(_) | GotoTarget::TypeParamTypeVarTupleName(_) => {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Some(ty)
|
Some(ty)
|
||||||
|
|
|
||||||
|
|
@ -975,7 +975,26 @@ mod tests {
|
||||||
"#,
|
"#,
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_snapshot!(test.goto_type_definition(), @"No goto target found");
|
assert_snapshot!(test.goto_type_definition(), @r#"
|
||||||
|
info[goto-type-definition]: Type definition
|
||||||
|
--> stdlib/ty_extensions.pyi:20:1
|
||||||
|
|
|
||||||
|
19 | # Types
|
||||||
|
20 | Unknown = object()
|
||||||
|
| ^^^^^^^
|
||||||
|
21 | AlwaysTruthy = object()
|
||||||
|
22 | AlwaysFalsy = object()
|
||||||
|
|
|
||||||
|
info: Source
|
||||||
|
--> main.py:4:22
|
||||||
|
|
|
||||||
|
2 | def my_func(command: str):
|
||||||
|
3 | match command.split():
|
||||||
|
4 | case ["get", ab]:
|
||||||
|
| ^^
|
||||||
|
5 | x = ab
|
||||||
|
|
|
||||||
|
"#);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -1003,7 +1022,26 @@ mod tests {
|
||||||
"#,
|
"#,
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_snapshot!(test.goto_type_definition(), @"No goto target found");
|
assert_snapshot!(test.goto_type_definition(), @r#"
|
||||||
|
info[goto-type-definition]: Type definition
|
||||||
|
--> stdlib/ty_extensions.pyi:20:1
|
||||||
|
|
|
||||||
|
19 | # Types
|
||||||
|
20 | Unknown = object()
|
||||||
|
| ^^^^^^^
|
||||||
|
21 | AlwaysTruthy = object()
|
||||||
|
22 | AlwaysFalsy = object()
|
||||||
|
|
|
||||||
|
info: Source
|
||||||
|
--> main.py:4:23
|
||||||
|
|
|
||||||
|
2 | def my_func(command: str):
|
||||||
|
3 | match command.split():
|
||||||
|
4 | case ["get", *ab]:
|
||||||
|
| ^^
|
||||||
|
5 | x = ab
|
||||||
|
|
|
||||||
|
"#);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -1031,7 +1069,26 @@ mod tests {
|
||||||
"#,
|
"#,
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_snapshot!(test.goto_type_definition(), @"No goto target found");
|
assert_snapshot!(test.goto_type_definition(), @r#"
|
||||||
|
info[goto-type-definition]: Type definition
|
||||||
|
--> stdlib/ty_extensions.pyi:20:1
|
||||||
|
|
|
||||||
|
19 | # Types
|
||||||
|
20 | Unknown = object()
|
||||||
|
| ^^^^^^^
|
||||||
|
21 | AlwaysTruthy = object()
|
||||||
|
22 | AlwaysFalsy = object()
|
||||||
|
|
|
||||||
|
info: Source
|
||||||
|
--> main.py:4:37
|
||||||
|
|
|
||||||
|
2 | def my_func(command: str):
|
||||||
|
3 | match command.split():
|
||||||
|
4 | case ["get", ("a" | "b") as ab]:
|
||||||
|
| ^^
|
||||||
|
5 | x = ab
|
||||||
|
|
|
||||||
|
"#);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -1065,7 +1122,26 @@ mod tests {
|
||||||
"#,
|
"#,
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_snapshot!(test.goto_type_definition(), @"No goto target found");
|
assert_snapshot!(test.goto_type_definition(), @r"
|
||||||
|
info[goto-type-definition]: Type definition
|
||||||
|
--> stdlib/ty_extensions.pyi:20:1
|
||||||
|
|
|
||||||
|
19 | # Types
|
||||||
|
20 | Unknown = object()
|
||||||
|
| ^^^^^^^
|
||||||
|
21 | AlwaysTruthy = object()
|
||||||
|
22 | AlwaysFalsy = object()
|
||||||
|
|
|
||||||
|
info: Source
|
||||||
|
--> main.py:10:30
|
||||||
|
|
|
||||||
|
8 | def my_func(event: Click):
|
||||||
|
9 | match event:
|
||||||
|
10 | case Click(x, button=ab):
|
||||||
|
| ^^
|
||||||
|
11 | x = ab
|
||||||
|
|
|
||||||
|
");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -1143,7 +1219,26 @@ mod tests {
|
||||||
"#,
|
"#,
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_snapshot!(test.goto_type_definition(), @"No goto target found");
|
assert_snapshot!(test.goto_type_definition(), @r"
|
||||||
|
info[goto-type-definition]: Type definition
|
||||||
|
--> stdlib/ty_extensions.pyi:20:1
|
||||||
|
|
|
||||||
|
19 | # Types
|
||||||
|
20 | Unknown = object()
|
||||||
|
| ^^^^^^^
|
||||||
|
21 | AlwaysTruthy = object()
|
||||||
|
22 | AlwaysFalsy = object()
|
||||||
|
|
|
||||||
|
info: Source
|
||||||
|
--> main.py:10:23
|
||||||
|
|
|
||||||
|
8 | def my_func(event: Click):
|
||||||
|
9 | match event:
|
||||||
|
10 | case Click(x, button=ab):
|
||||||
|
| ^^^^^^
|
||||||
|
11 | x = ab
|
||||||
|
|
|
||||||
|
");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -1395,7 +1490,26 @@ def outer():
|
||||||
);
|
);
|
||||||
|
|
||||||
// Should find the variable declaration in the outer scope, not the nonlocal statement
|
// Should find the variable declaration in the outer scope, not the nonlocal statement
|
||||||
assert_snapshot!(test.goto_type_definition(), @"No goto target found");
|
assert_snapshot!(test.goto_type_definition(), @r#"
|
||||||
|
info[goto-type-definition]: Type definition
|
||||||
|
--> stdlib/ty_extensions.pyi:20:1
|
||||||
|
|
|
||||||
|
19 | # Types
|
||||||
|
20 | Unknown = object()
|
||||||
|
| ^^^^^^^
|
||||||
|
21 | AlwaysTruthy = object()
|
||||||
|
22 | AlwaysFalsy = object()
|
||||||
|
|
|
||||||
|
info: Source
|
||||||
|
--> main.py:6:18
|
||||||
|
|
|
||||||
|
5 | def inner():
|
||||||
|
6 | nonlocal xy
|
||||||
|
| ^^
|
||||||
|
7 | xy = "modified"
|
||||||
|
8 | return x # Should find the nonlocal x declaration in outer scope
|
||||||
|
|
|
||||||
|
"#);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -1447,7 +1561,26 @@ def function():
|
||||||
);
|
);
|
||||||
|
|
||||||
// Should find the global variable declaration, not the global statement
|
// Should find the global variable declaration, not the global statement
|
||||||
assert_snapshot!(test.goto_type_definition(), @"No goto target found");
|
assert_snapshot!(test.goto_type_definition(), @r#"
|
||||||
|
info[goto-type-definition]: Type definition
|
||||||
|
--> stdlib/ty_extensions.pyi:20:1
|
||||||
|
|
|
||||||
|
19 | # Types
|
||||||
|
20 | Unknown = object()
|
||||||
|
| ^^^^^^^
|
||||||
|
21 | AlwaysTruthy = object()
|
||||||
|
22 | AlwaysFalsy = object()
|
||||||
|
|
|
||||||
|
info: Source
|
||||||
|
--> main.py:5:12
|
||||||
|
|
|
||||||
|
4 | def function():
|
||||||
|
5 | global global_var
|
||||||
|
| ^^^^^^^^^^
|
||||||
|
6 | global_var = "modified"
|
||||||
|
7 | return global_var # Should find the global variable declaration
|
||||||
|
|
|
||||||
|
"#);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
||||||
|
|
@ -1704,7 +1704,26 @@ def outer():
|
||||||
);
|
);
|
||||||
|
|
||||||
// Should find the variable declaration in the outer scope, not the nonlocal statement
|
// Should find the variable declaration in the outer scope, not the nonlocal statement
|
||||||
assert_snapshot!(test.hover(), @"Hover provided no content");
|
assert_snapshot!(test.hover(), @r#"
|
||||||
|
Unknown
|
||||||
|
---------------------------------------------
|
||||||
|
```python
|
||||||
|
Unknown
|
||||||
|
```
|
||||||
|
---------------------------------------------
|
||||||
|
info[hover]: Hovered content is
|
||||||
|
--> main.py:6:18
|
||||||
|
|
|
||||||
|
5 | def inner():
|
||||||
|
6 | nonlocal xy
|
||||||
|
| ^-
|
||||||
|
| ||
|
||||||
|
| |Cursor offset
|
||||||
|
| source
|
||||||
|
7 | xy = "modified"
|
||||||
|
8 | return x # Should find the nonlocal x declaration in outer scope
|
||||||
|
|
|
||||||
|
"#);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -1756,7 +1775,26 @@ def function():
|
||||||
);
|
);
|
||||||
|
|
||||||
// Should find the global variable declaration, not the global statement
|
// Should find the global variable declaration, not the global statement
|
||||||
assert_snapshot!(test.hover(), @"Hover provided no content");
|
assert_snapshot!(test.hover(), @r#"
|
||||||
|
Unknown
|
||||||
|
---------------------------------------------
|
||||||
|
```python
|
||||||
|
Unknown
|
||||||
|
```
|
||||||
|
---------------------------------------------
|
||||||
|
info[hover]: Hovered content is
|
||||||
|
--> main.py:5:12
|
||||||
|
|
|
||||||
|
4 | def function():
|
||||||
|
5 | global global_var
|
||||||
|
| ^^^^^^^-^^
|
||||||
|
| | |
|
||||||
|
| | Cursor offset
|
||||||
|
| source
|
||||||
|
6 | global_var = "modified"
|
||||||
|
7 | return global_var # Should find the global variable declaration
|
||||||
|
|
|
||||||
|
"#);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -1770,7 +1808,26 @@ def function():
|
||||||
"#,
|
"#,
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_snapshot!(test.hover(), @"Hover provided no content");
|
assert_snapshot!(test.hover(), @r#"
|
||||||
|
Unknown
|
||||||
|
---------------------------------------------
|
||||||
|
```python
|
||||||
|
Unknown
|
||||||
|
```
|
||||||
|
---------------------------------------------
|
||||||
|
info[hover]: Hovered content is
|
||||||
|
--> main.py:4:22
|
||||||
|
|
|
||||||
|
2 | def my_func(command: str):
|
||||||
|
3 | match command.split():
|
||||||
|
4 | case ["get", ab]:
|
||||||
|
| ^-
|
||||||
|
| ||
|
||||||
|
| |Cursor offset
|
||||||
|
| source
|
||||||
|
5 | x = ab
|
||||||
|
|
|
||||||
|
"#);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -1816,7 +1873,26 @@ def function():
|
||||||
"#,
|
"#,
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_snapshot!(test.hover(), @"Hover provided no content");
|
assert_snapshot!(test.hover(), @r#"
|
||||||
|
Unknown
|
||||||
|
---------------------------------------------
|
||||||
|
```python
|
||||||
|
Unknown
|
||||||
|
```
|
||||||
|
---------------------------------------------
|
||||||
|
info[hover]: Hovered content is
|
||||||
|
--> main.py:4:23
|
||||||
|
|
|
||||||
|
2 | def my_func(command: str):
|
||||||
|
3 | match command.split():
|
||||||
|
4 | case ["get", *ab]:
|
||||||
|
| ^-
|
||||||
|
| ||
|
||||||
|
| |Cursor offset
|
||||||
|
| source
|
||||||
|
5 | x = ab
|
||||||
|
|
|
||||||
|
"#);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -1862,7 +1938,26 @@ def function():
|
||||||
"#,
|
"#,
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_snapshot!(test.hover(), @"Hover provided no content");
|
assert_snapshot!(test.hover(), @r#"
|
||||||
|
Unknown
|
||||||
|
---------------------------------------------
|
||||||
|
```python
|
||||||
|
Unknown
|
||||||
|
```
|
||||||
|
---------------------------------------------
|
||||||
|
info[hover]: Hovered content is
|
||||||
|
--> main.py:4:37
|
||||||
|
|
|
||||||
|
2 | def my_func(command: str):
|
||||||
|
3 | match command.split():
|
||||||
|
4 | case ["get", ("a" | "b") as ab]:
|
||||||
|
| ^-
|
||||||
|
| ||
|
||||||
|
| |Cursor offset
|
||||||
|
| source
|
||||||
|
5 | x = ab
|
||||||
|
|
|
||||||
|
"#);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -1914,7 +2009,26 @@ def function():
|
||||||
"#,
|
"#,
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_snapshot!(test.hover(), @"Hover provided no content");
|
assert_snapshot!(test.hover(), @r"
|
||||||
|
Unknown
|
||||||
|
---------------------------------------------
|
||||||
|
```python
|
||||||
|
Unknown
|
||||||
|
```
|
||||||
|
---------------------------------------------
|
||||||
|
info[hover]: Hovered content is
|
||||||
|
--> main.py:10:30
|
||||||
|
|
|
||||||
|
8 | def my_func(event: Click):
|
||||||
|
9 | match event:
|
||||||
|
10 | case Click(x, button=ab):
|
||||||
|
| ^-
|
||||||
|
| ||
|
||||||
|
| |Cursor offset
|
||||||
|
| source
|
||||||
|
11 | x = ab
|
||||||
|
|
|
||||||
|
");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -2011,7 +2125,26 @@ def function():
|
||||||
"#,
|
"#,
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_snapshot!(test.hover(), @"Hover provided no content");
|
assert_snapshot!(test.hover(), @r"
|
||||||
|
Unknown
|
||||||
|
---------------------------------------------
|
||||||
|
```python
|
||||||
|
Unknown
|
||||||
|
```
|
||||||
|
---------------------------------------------
|
||||||
|
info[hover]: Hovered content is
|
||||||
|
--> main.py:10:23
|
||||||
|
|
|
||||||
|
8 | def my_func(event: Click):
|
||||||
|
9 | match event:
|
||||||
|
10 | case Click(x, button=ab):
|
||||||
|
| ^^^-^^
|
||||||
|
| | |
|
||||||
|
| | Cursor offset
|
||||||
|
| source
|
||||||
|
11 | x = ab
|
||||||
|
|
|
||||||
|
");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
use ruff_db::files::{File, FilePath};
|
use ruff_db::files::{File, FilePath};
|
||||||
use ruff_db::source::{line_index, source_text};
|
use ruff_db::source::{line_index, source_text};
|
||||||
use ruff_python_ast::{self as ast, ExprStringLiteral, ModExpression};
|
use ruff_python_ast::{self as ast, ExprStringLiteral, Identifier, ModExpression};
|
||||||
use ruff_python_ast::{Expr, ExprRef, HasNodeIndex, name::Name};
|
use ruff_python_ast::{Expr, ExprRef, HasNodeIndex, name::Name};
|
||||||
use ruff_python_parser::Parsed;
|
use ruff_python_parser::Parsed;
|
||||||
use ruff_source_file::LineIndex;
|
use ruff_source_file::LineIndex;
|
||||||
|
|
@ -90,6 +90,19 @@ impl<'db> SemanticModel<'db> {
|
||||||
members
|
members
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn inferred_type_for_identifier(&self, identifier: &Identifier) -> Type<'db> {
|
||||||
|
// TODO(#1637): semantic tokens is making this crash even with
|
||||||
|
// `try_expr_ref_in_ast` guarding this, for now just use `try_expression_scope_id`.
|
||||||
|
// The problematic input is `x: "float` (with a dangling quote). I imagine the issue
|
||||||
|
// is we're too eagerly setting `is_string_annotation` in inference.
|
||||||
|
let Some(file_scope) = self.scope(identifier.into()) else {
|
||||||
|
return Type::unknown();
|
||||||
|
};
|
||||||
|
let scope = file_scope.to_scope_id(self.db, self.file);
|
||||||
|
|
||||||
|
infer_scope_types(self.db, scope).expression_type(identifier)
|
||||||
|
}
|
||||||
|
|
||||||
/// Resolve the given import made in this file to a Type
|
/// Resolve the given import made in this file to a Type
|
||||||
pub fn resolve_module_type(&self, module: Option<&str>, level: u32) -> Option<Type<'db>> {
|
pub fn resolve_module_type(&self, module: Option<&str>, level: u32) -> Option<Type<'db>> {
|
||||||
let module = self.resolve_module(module, level)?;
|
let module = self.resolve_module(module, level)?;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue