Compare commits
5 Commits
dcreager/s
...
gankra/sig
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cfd75914be | ||
|
|
42abe02eac | ||
|
|
dcc451d4d2 | ||
|
|
3a10f87471 | ||
|
|
89236a3b2d |
@@ -65,11 +65,10 @@ impl Docstring {
|
||||
/// Render the docstring for markdown display
|
||||
pub fn render_markdown(&self) -> String {
|
||||
let trimmed = documentation_trim(&self.0);
|
||||
// TODO: now actually parse it and "render" it to markdown.
|
||||
//
|
||||
// For now we just wrap the content in a plaintext codeblock
|
||||
// to avoid the contents erroneously being interpreted as markdown.
|
||||
format!("```text\n{trimmed}\n```")
|
||||
|
||||
// Try to parse and render the contents as markdown,
|
||||
// and if we fail, wrap it in a codeblock and display it raw.
|
||||
try_render_markdown(&trimmed).unwrap_or_else(|| format!("```text\n{trimmed}\n```"))
|
||||
}
|
||||
|
||||
/// Extract parameter documentation from popular docstring formats.
|
||||
@@ -153,6 +152,26 @@ fn documentation_trim(docs: &str) -> String {
|
||||
output
|
||||
}
|
||||
|
||||
fn try_render_markdown(docstring: &str) -> Option<String> {
|
||||
let mut output = String::new();
|
||||
let mut first_line = true;
|
||||
for line in docstring.lines() {
|
||||
// We can assume leading whitespace has been normalized
|
||||
let trimmed_line = line.trim_start_matches(' ');
|
||||
let num_leading_spaces = line.len() - trimmed_line.len();
|
||||
|
||||
if !first_line {
|
||||
output.push_str(" \n");
|
||||
}
|
||||
for _ in 0..num_leading_spaces {
|
||||
output.push_str(" ");
|
||||
}
|
||||
output.push_str(trimmed_line);
|
||||
first_line = false;
|
||||
}
|
||||
Some(output)
|
||||
}
|
||||
|
||||
/// Extract parameter documentation from Google-style docstrings.
|
||||
fn extract_google_style_params(docstring: &str) -> HashMap<String, String> {
|
||||
let mut param_docs = HashMap::new();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
```
|
||||
---
|
||||
@@ -1090,21 +1090,19 @@ def ab(a: int):
|
||||
.build();
|
||||
|
||||
assert_snapshot!(test.hover(), @r"
|
||||
(
|
||||
def ab(
|
||||
a: int,
|
||||
b: int
|
||||
) -> Unknown
|
||||
(a: int) -> Unknown
|
||||
---------------------------------------------
|
||||
the two arg overload
|
||||
|
||||
---------------------------------------------
|
||||
```python
|
||||
(
|
||||
def ab(
|
||||
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
|
||||
(
|
||||
def ab(
|
||||
a: int,
|
||||
*,
|
||||
b: int
|
||||
) -> Unknown
|
||||
(
|
||||
a: int,
|
||||
*,
|
||||
c: int
|
||||
) -> Unknown
|
||||
---------------------------------------------
|
||||
keywordless overload
|
||||
|
||||
---------------------------------------------
|
||||
```python
|
||||
(a: int) -> Unknown
|
||||
(
|
||||
def ab(
|
||||
a: int,
|
||||
*,
|
||||
b: int
|
||||
) -> Unknown
|
||||
(
|
||||
a: int,
|
||||
*,
|
||||
c: int
|
||||
) -> Unknown
|
||||
```
|
||||
---
|
||||
```text
|
||||
@@ -1323,13 +1301,7 @@ def ab(a: int, *, c: int):
|
||||
.build();
|
||||
|
||||
assert_snapshot!(test.hover(), @r"
|
||||
(a: int) -> Unknown
|
||||
(
|
||||
a: int,
|
||||
*,
|
||||
b: int
|
||||
) -> Unknown
|
||||
(
|
||||
def ab(
|
||||
a: int,
|
||||
*,
|
||||
c: int
|
||||
@@ -1339,13 +1311,7 @@ def ab(a: int, *, c: int):
|
||||
|
||||
---------------------------------------------
|
||||
```python
|
||||
(a: int) -> Unknown
|
||||
(
|
||||
a: int,
|
||||
*,
|
||||
b: int
|
||||
) -> Unknown
|
||||
(
|
||||
def ab(
|
||||
a: int,
|
||||
*,
|
||||
c: int
|
||||
@@ -1397,11 +1363,11 @@ def ab(a: int, *, c: int):
|
||||
);
|
||||
|
||||
assert_snapshot!(test.hover(), @r#"
|
||||
(
|
||||
def foo(
|
||||
a: int,
|
||||
b
|
||||
) -> Unknown
|
||||
(
|
||||
def foo(
|
||||
a: str,
|
||||
b
|
||||
) -> Unknown
|
||||
@@ -1410,11 +1376,11 @@ def ab(a: int, *, c: int):
|
||||
|
||||
---------------------------------------------
|
||||
```python
|
||||
(
|
||||
def foo(
|
||||
a: int,
|
||||
b
|
||||
) -> Unknown
|
||||
(
|
||||
def foo(
|
||||
a: str,
|
||||
b
|
||||
) -> Unknown
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -16,6 +16,7 @@ use rustc_hash::{FxHashMap, FxHashSet};
|
||||
|
||||
use crate::Db;
|
||||
use crate::module_resolver::file_to_module;
|
||||
use crate::semantic_index::definition::Definition;
|
||||
use crate::semantic_index::{scope::ScopeKind, semantic_index};
|
||||
use crate::types::class::{ClassLiteral, ClassType, GenericAlias};
|
||||
use crate::types::function::{FunctionType, OverloadLiteral};
|
||||
@@ -40,6 +41,9 @@ pub struct DisplaySettings<'db> {
|
||||
pub qualified: Rc<FxHashMap<&'db str, QualificationLevel>>,
|
||||
/// Whether long unions and literals are displayed in full
|
||||
pub preserve_full_unions: bool,
|
||||
/// Disallow Signature printing to introduce a name
|
||||
/// (presumably because we rendered one already)
|
||||
pub disallow_signature_name: bool,
|
||||
}
|
||||
|
||||
impl<'db> DisplaySettings<'db> {
|
||||
@@ -59,6 +63,14 @@ impl<'db> DisplaySettings<'db> {
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn disallow_signature_name(&self) -> Self {
|
||||
Self {
|
||||
disallow_signature_name: true,
|
||||
..self.clone()
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn truncate_long_unions(self) -> Self {
|
||||
Self {
|
||||
@@ -473,7 +485,7 @@ impl Display for DisplayRepresentation<'_> {
|
||||
type_parameters = type_parameters,
|
||||
signature = signature
|
||||
.bind_self(self.db, Some(typing_self_ty))
|
||||
.display_with(self.db, self.settings.clone())
|
||||
.display_with(self.db, self.settings.disallow_signature_name())
|
||||
)
|
||||
}
|
||||
signatures => {
|
||||
@@ -768,7 +780,7 @@ impl Display for DisplayOverloadLiteral<'_> {
|
||||
"def {name}{type_parameters}{signature}",
|
||||
name = self.literal.name(self.db),
|
||||
type_parameters = type_parameters,
|
||||
signature = signature.display_with(self.db, self.settings.clone())
|
||||
signature = signature.display_with(self.db, self.settings.disallow_signature_name())
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -810,7 +822,8 @@ impl Display for DisplayFunctionType<'_> {
|
||||
"def {name}{type_parameters}{signature}",
|
||||
name = self.ty.name(self.db),
|
||||
type_parameters = type_parameters,
|
||||
signature = signature.display_with(self.db, self.settings.clone())
|
||||
signature =
|
||||
signature.display_with(self.db, self.settings.disallow_signature_name())
|
||||
)
|
||||
}
|
||||
signatures => {
|
||||
@@ -1081,6 +1094,7 @@ impl<'db> Signature<'db> {
|
||||
settings: DisplaySettings<'db>,
|
||||
) -> DisplaySignature<'db> {
|
||||
DisplaySignature {
|
||||
definition: self.definition(),
|
||||
parameters: self.parameters(),
|
||||
return_ty: self.return_ty,
|
||||
db,
|
||||
@@ -1090,6 +1104,7 @@ impl<'db> Signature<'db> {
|
||||
}
|
||||
|
||||
pub(crate) struct DisplaySignature<'db> {
|
||||
definition: Option<Definition<'db>>,
|
||||
parameters: &'db Parameters<'db>,
|
||||
return_ty: Option<Type<'db>>,
|
||||
db: &'db dyn Db,
|
||||
@@ -1111,6 +1126,18 @@ impl DisplaySignature<'_> {
|
||||
/// Internal method to write signature with the signature writer
|
||||
fn write_signature(&self, writer: &mut SignatureWriter) -> fmt::Result {
|
||||
let multiline = self.settings.multiline && self.parameters.len() > 1;
|
||||
// If we're multiline printing and a name hasn't been emitted, try to
|
||||
// make one up to make things more pretty
|
||||
if multiline && !self.settings.disallow_signature_name {
|
||||
writer.write_str("def ")?;
|
||||
if let Some(definition) = self.definition
|
||||
&& let Some(name) = definition.name(self.db)
|
||||
{
|
||||
writer.write_str(&name)?;
|
||||
} else {
|
||||
writer.write_str("_")?;
|
||||
}
|
||||
}
|
||||
// Opening parenthesis
|
||||
writer.write_char('(')?;
|
||||
if multiline {
|
||||
@@ -1979,7 +2006,7 @@ mod tests {
|
||||
Some(Type::none(&db))
|
||||
),
|
||||
@r"
|
||||
(
|
||||
def _(
|
||||
x=int,
|
||||
y: str = str
|
||||
) -> None
|
||||
@@ -1997,7 +2024,7 @@ mod tests {
|
||||
Some(Type::none(&db))
|
||||
),
|
||||
@r"
|
||||
(
|
||||
def _(
|
||||
x,
|
||||
y,
|
||||
/
|
||||
@@ -2016,7 +2043,7 @@ mod tests {
|
||||
Some(Type::none(&db))
|
||||
),
|
||||
@r"
|
||||
(
|
||||
def _(
|
||||
x,
|
||||
/,
|
||||
y
|
||||
@@ -2035,7 +2062,7 @@ mod tests {
|
||||
Some(Type::none(&db))
|
||||
),
|
||||
@r"
|
||||
(
|
||||
def _(
|
||||
*,
|
||||
x,
|
||||
y
|
||||
@@ -2054,7 +2081,7 @@ mod tests {
|
||||
Some(Type::none(&db))
|
||||
),
|
||||
@r"
|
||||
(
|
||||
def _(
|
||||
x,
|
||||
*,
|
||||
y
|
||||
@@ -2093,7 +2120,7 @@ mod tests {
|
||||
Some(KnownClass::Bytes.to_instance(&db))
|
||||
),
|
||||
@r"
|
||||
(
|
||||
def _(
|
||||
a,
|
||||
b: int,
|
||||
c=Literal[1],
|
||||
|
||||
@@ -17,7 +17,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;
|
||||
@@ -973,6 +973,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,
|
||||
|
||||
Reference in New Issue
Block a user