This commit is contained in:
Aria Desires 2025-11-16 18:29:33 +00:00 committed by GitHub
commit e28779d1b9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 119 additions and 49 deletions

View file

@ -15,7 +15,7 @@ use ruff_text_size::{Ranged, TextRange, TextSize};
use ty_python_semantic::ResolvedDefinition;
use ty_python_semantic::types::Type;
use ty_python_semantic::types::ide_support::{
call_signature_details, definitions_for_keyword_argument,
call_signature_details, call_type_simplified_by_overloads, definitions_for_keyword_argument,
};
use ty_python_semantic::{
HasDefinition, HasType, ImportAliasResolution, SemanticModel, definitions_for_imported_symbol,
@ -326,6 +326,18 @@ impl GotoTarget<'_> {
Some(ty)
}
/// Try to get a simplified display of this callable type by resolving overloads
pub(crate) fn call_type_simplified_by_overloads(
&self,
model: &SemanticModel,
) -> Option<String> {
if let GotoTarget::Call { call, .. } = self {
call_type_simplified_by_overloads(model.db(), model, call)
} else {
None
}
}
/// Gets the definitions for this goto target.
///
/// The `alias_resolution` parameter controls whether import aliases

View file

@ -20,7 +20,6 @@ pub fn hover(db: &dyn Db, file: File, offset: TextSize) -> Option<RangedValue<Ho
}
let model = SemanticModel::new(db, file);
let ty = goto_target.inferred_type(&model);
let docs = goto_target
.get_definition_targets(
file,
@ -30,9 +29,10 @@ pub fn hover(db: &dyn Db, file: File, offset: TextSize) -> Option<RangedValue<Ho
.and_then(|definitions| definitions.docstring(db))
.map(HoverContent::Docstring);
// TODO: Render the symbol's signature instead of just its type.
let mut contents = Vec::new();
if let Some(ty) = ty {
if let Some(signature) = goto_target.call_type_simplified_by_overloads(&model) {
contents.push(HoverContent::Signature(signature));
} else if let Some(ty) = goto_target.inferred_type(&model) {
tracing::debug!("Inferred type of covering node is {}", ty.display(db));
contents.push(match ty {
Type::KnownInstance(KnownInstanceType::TypeVar(typevar)) => typevar
@ -62,7 +62,7 @@ pub struct Hover<'db> {
impl<'db> Hover<'db> {
/// Renders the hover to a string using the specified markup kind.
pub const fn display<'a>(&'a self, db: &'a dyn Db, kind: MarkupKind) -> DisplayHover<'a> {
pub const fn display<'a>(&'a self, db: &'db dyn Db, kind: MarkupKind) -> DisplayHover<'db, 'a> {
DisplayHover {
db,
hover: self,
@ -93,13 +93,13 @@ impl<'a, 'db> IntoIterator for &'a Hover<'db> {
}
}
pub struct DisplayHover<'a> {
db: &'a dyn Db,
hover: &'a Hover<'a>,
pub struct DisplayHover<'db, 'a> {
db: &'db dyn Db,
hover: &'a Hover<'db>,
kind: MarkupKind,
}
impl fmt::Display for DisplayHover<'_> {
impl fmt::Display for DisplayHover<'_, '_> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
let mut first = true;
for content in &self.hover.contents {
@ -115,8 +115,9 @@ impl fmt::Display for DisplayHover<'_> {
}
}
#[derive(Debug, Clone, Eq, PartialEq)]
#[derive(Debug, Clone)]
pub enum HoverContent<'db> {
Signature(String),
Type(Type<'db>, Option<TypeVarVariance>),
Docstring(Docstring),
}
@ -140,6 +141,9 @@ pub(crate) struct DisplayHoverContent<'a, 'db> {
impl fmt::Display for DisplayHoverContent<'_, '_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.content {
HoverContent::Signature(signature) => {
self.kind.fenced_code_block(&signature, "python").fmt(f)
}
HoverContent::Type(ty, variance) => {
let variance = match variance {
Some(TypeVarVariance::Covariant) => " (covariant)",
@ -961,14 +965,12 @@ def ab(a: str): ...
assert_snapshot!(test.hover(), @r"
(a: int) -> Unknown
(a: str) -> Unknown
---------------------------------------------
the int overload
---------------------------------------------
```python
(a: int) -> Unknown
(a: str) -> Unknown
```
---
```text
@ -1025,14 +1027,12 @@ def ab(a: str):
.build();
assert_snapshot!(test.hover(), @r#"
(a: int) -> Unknown
(a: str) -> Unknown
---------------------------------------------
the int overload
---------------------------------------------
```python
(a: int) -> Unknown
(a: str) -> Unknown
```
---
@ -1094,7 +1094,6 @@ def ab(a: int):
a: int,
b: int
) -> Unknown
(a: int) -> Unknown
---------------------------------------------
the two arg overload
@ -1104,7 +1103,6 @@ def ab(a: int):
a: int,
b: int
) -> Unknown
(a: int) -> Unknown
```
---
```text
@ -1161,20 +1159,12 @@ def ab(a: int):
.build();
assert_snapshot!(test.hover(), @r"
(
a: int,
b: int
) -> Unknown
(a: int) -> Unknown
---------------------------------------------
the two arg overload
---------------------------------------------
```python
(
a: int,
b: int
) -> Unknown
(a: int) -> Unknown
```
---
@ -1236,33 +1226,21 @@ def ab(a: int, *, c: int):
.build();
assert_snapshot!(test.hover(), @r"
(a: int) -> Unknown
(
a: int,
*,
b: int
) -> Unknown
(
a: int,
*,
c: int
) -> Unknown
---------------------------------------------
keywordless overload
---------------------------------------------
```python
(a: int) -> Unknown
(
a: int,
*,
b: int
) -> Unknown
(
a: int,
*,
c: int
) -> Unknown
```
---
```text
@ -1323,12 +1301,6 @@ def ab(a: int, *, c: int):
.build();
assert_snapshot!(test.hover(), @r"
(a: int) -> Unknown
(
a: int,
*,
b: int
) -> Unknown
(
a: int,
*,
@ -1339,12 +1311,6 @@ def ab(a: int, *, c: int):
---------------------------------------------
```python
(a: int) -> Unknown
(
a: int,
*,
b: int
) -> Unknown
(
a: int,
*,

View file

@ -66,6 +66,39 @@ impl<'a, 'db> CallArguments<'a, 'db> {
.collect()
}
/// Like [`Self::from_arguments`] but fills as much typing info in as possible.
///
/// This currently only exists for the LSP usecase, and shouldn't be used in normal
/// typechecking.
pub(crate) fn from_arguments_typed(
arguments: &'a ast::Arguments,
mut infer_argument_type: impl FnMut(Option<&ast::Expr>, &ast::Expr) -> Type<'db>,
) -> Self {
arguments
.arguments_source_order()
.map(|arg_or_keyword| match arg_or_keyword {
ast::ArgOrKeyword::Arg(arg) => match arg {
ast::Expr::Starred(ast::ExprStarred { value, .. }) => {
let ty = infer_argument_type(Some(arg), value);
(Argument::Variadic, Some(ty))
}
_ => {
let ty = infer_argument_type(None, arg);
(Argument::Positional, Some(ty))
}
},
ast::ArgOrKeyword::Keyword(ast::Keyword { arg, value, .. }) => {
let ty = infer_argument_type(None, value);
if let Some(arg) = arg {
(Argument::Keyword(&arg.id), Some(ty))
} else {
(Argument::Keywords, Some(ty))
}
}
})
.collect()
}
/// Create a [`CallArguments`] with no arguments.
pub(crate) fn none() -> Self {
Self::default()

View file

@ -18,7 +18,7 @@ use crate::types::{
ClassBase, ClassLiteral, DynamicType, KnownClass, KnownInstanceType, Type, TypeContext,
TypeVarBoundOrConstraints, class::CodeGeneratorKind,
};
use crate::{Db, HasType, NameKind, SemanticModel};
use crate::{Db, DisplaySettings, HasType, NameKind, SemanticModel};
use ruff_db::files::{File, FileRange};
use ruff_db::parsed::parsed_module;
use ruff_python_ast::name::Name;
@ -1006,6 +1006,65 @@ pub fn call_signature_details<'db>(
}
}
/// Given a call expression that has overloads, and whose overload is resolved to a
/// single option by its arguments, return the type of the Signature.
///
/// This is only used for simplifying complex call types, so if we ever detect that
/// the given callable type *is* simple, or that our answer *won't* be simple, we
/// bail at out and return None, so that the original type can be used.
///
/// We do this because `Type::Signature` intentionally loses a lot of context, and
/// so it has a "worse" display than say `Type::FunctionLiteral` or `Type::BoundMethod`,
/// which this analysis would naturally wipe away. The contexts this function
/// succeeds in are those where we would print a complicated/ugly type anyway.
pub fn call_type_simplified_by_overloads<'db>(
db: &'db dyn Db,
model: &SemanticModel<'db>,
call_expr: &ast::ExprCall,
) -> Option<String> {
let func_type = call_expr.func.inferred_type(model);
// Use into_callable to handle all the complex type conversions
let callable_type = func_type.try_upcast_to_callable(db)?;
let bindings = callable_type.bindings(db);
// If the callable is trivial this analysis is useless, bail out
if let Some(binding) = bindings.single_element()
&& binding.overloads().len() < 2
{
return None;
}
// Hand the overload resolution system as much type info as we have
let args = CallArguments::from_arguments_typed(&call_expr.arguments, |_, splatted_value| {
splatted_value.inferred_type(model)
});
// Try to resolve overloads with the arguments/types we have
let mut resolved = bindings
.match_parameters(db, &args)
.check_types(db, &args, TypeContext::default(), &[])
// Only use the Ok
.iter()
.flatten()
.flat_map(|binding| {
binding.matching_overloads().map(|(_, overload)| {
overload
.signature
.display_with(db, DisplaySettings::default().multiline())
.to_string()
})
})
.collect::<Vec<_>>();
// If at the end of this we still got multiple signatures (or no signatures), give up
if resolved.len() != 1 {
return None;
}
resolved.pop()
}
/// Returns the definitions of the binary operation along with its callable type.
pub fn definitions_for_bin_op<'db>(
db: &'db dyn Db,