Skip to content

Commit 6fc98ee

Browse files
committed
Add documentation for CompletionTreeName and CompletionDirEntry
1 parent 993466c commit 6fc98ee

File tree

5 files changed

+229
-64
lines changed

5 files changed

+229
-64
lines changed

crates/zuban_python/src/completion.rs

Lines changed: 37 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,15 @@ use crate::{
1212
database::{ClassKind, Database, ParentScope, PointKind},
1313
debug,
1414
file::{ClassNodeRef, File as _, FuncNodeRef, PythonFile, is_reexport_issue},
15-
goto::{PositionalDocument, unpack_union_types, with_i_s_non_self},
15+
goto::{
16+
FollowImportResult, PositionalDocument, try_to_follow_imports, unpack_union_types,
17+
with_i_s_non_self,
18+
},
1619
imports::{ImportResult, global_import},
1720
inference_state::InferenceState,
1821
inferred::Inferred,
1922
lines::BytePositionInfos,
20-
name::{Range, TreeName, process_docstring},
23+
name::{ModuleName, Range, TreeName, process_docstring},
2124
node_ref::NodeRef,
2225
recoverable_error,
2326
type_::{CallableParam, Enum, EnumMemberDefinition, FunctionKind, Namespace, Type},
@@ -732,22 +735,31 @@ impl<'db> Completion for CompletionTreeName<'db> {
732735
}
733736

734737
fn documentation(&self) -> Option<Cow<'db, str>> {
735-
Some(process_docstring(
736-
self.file,
737-
self.name.clean_docstring(),
738-
|| TreeName::with_unknown_parent_scope(self.db, self.file, self.name).goto_non_stub(),
739-
))
738+
let doc = self.name.clean_docstring();
739+
if doc.is_empty()
740+
&& let Some(r) = try_to_follow_imports(self.db, self.file, self.name)
741+
{
742+
return Some(match r {
743+
FollowImportResult::File(file_index) => {
744+
let file = self.db.loaded_python_file(file_index);
745+
ModuleName { db: self.db, file }.documentation()
746+
}
747+
FollowImportResult::TreeName(tree_name) => tree_name.documentation(),
748+
});
749+
}
750+
Some(process_docstring(self.file, doc, || {
751+
TreeName::with_unknown_parent_scope(self.db, self.file, self.name).goto_non_stub()
752+
}))
740753
}
741754
}
742755

743-
#[expect(dead_code)]
744756
struct CompletionDirEntry<'db, 'x> {
745757
db: &'db Database,
746758
name: &'db str,
747759
entry: &'x DirectoryEntry,
748760
}
749761

750-
impl Completion for CompletionDirEntry<'_, '_> {
762+
impl<'db> Completion for CompletionDirEntry<'db, '_> {
751763
fn label(&self) -> &str {
752764
self.name
753765
}
@@ -771,6 +783,22 @@ impl Completion for CompletionDirEntry<'_, '_> {
771783
*/
772784
None
773785
}
786+
787+
fn documentation(&self) -> Option<Cow<'db, str>> {
788+
match self.entry {
789+
DirectoryEntry::File(entry) => {
790+
let file_index = self.db.load_file_from_workspace(entry, false)?;
791+
Some(
792+
ModuleName {
793+
db: self.db,
794+
file: self.db.loaded_python_file(file_index),
795+
}
796+
.documentation(),
797+
)
798+
}
799+
_ => None,
800+
}
801+
}
774802
}
775803

776804
struct KeywordCompletion {

crates/zuban_python/src/goto.rs

Lines changed: 89 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -386,63 +386,27 @@ impl<'db, C: for<'a> FnMut(Name<'db, 'a>) -> T, T> GotoResolver<'db, C> {
386386
self.calculate_return(Name::ModuleName(ModuleName { db, file }))
387387
}
388388

389-
fn try_to_follow(&mut self, n: NodeRef, follow_imports: bool) -> Option<Option<T>> {
390-
let p = n.point();
391-
if !p.calculated() {
392-
return None;
393-
}
394-
match p.kind() {
395-
PointKind::Redirect => Some(self.check_node_ref_and_maybe_follow_import(
396-
p.as_redirected_node_ref(self.infos.db),
397-
follow_imports,
398-
)),
399-
PointKind::FileReference => Some(Some(self.goto_on_file(p.file_index()))),
400-
_ => None,
401-
}
389+
fn try_to_follow(&mut self, n: NodeRef<'db>, follow_imports: bool) -> Option<T> {
390+
self.process_follow_import_result(try_to_follow(self.infos.db, n, follow_imports)?)
391+
}
392+
393+
fn process_follow_import_result(&mut self, r: Option<FollowImportResult<'db>>) -> Option<T> {
394+
Some(match r? {
395+
FollowImportResult::File(file_index) => self.goto_on_file(file_index),
396+
FollowImportResult::TreeName(n) => self.calculate_return(Name::TreeName(n)),
397+
})
402398
}
403399

404400
fn check_node_ref_and_maybe_follow_import(
405401
&mut self,
406402
node_ref: NodeRef<'db>,
407403
follow_imports: bool,
408404
) -> Option<T> {
409-
let n = node_ref.maybe_name()?;
410-
let db = self.infos.db;
411-
if follow_imports && let Some(name_def) = n.name_def() {
412-
match name_def.maybe_import() {
413-
Some(NameImportParent::ImportFromAsName(_)) => {
414-
let ref_ = NodeRef::new(node_ref.file, name_def.index());
415-
if let Some(result) = self.try_to_follow(ref_, follow_imports) {
416-
return result;
417-
}
418-
}
419-
Some(NameImportParent::DottedAsName(_)) => {
420-
let p = NodeRef::new(node_ref.file, name_def.index()).point();
421-
if p.kind() == PointKind::FileReference {
422-
return Some(self.goto_on_file(p.file_index()));
423-
}
424-
}
425-
None => {
426-
if matches!(
427-
name_def.parent(),
428-
NameDefParent::GlobalStmt | NameDefParent::NonlocalStmt
429-
) {
430-
let ref_ = NodeRef::new(self.infos.file, name_def.index())
431-
.global_or_nonlocal_ref();
432-
if let Some(result) = self.try_to_follow(ref_, follow_imports) {
433-
return result;
434-
}
435-
}
436-
}
437-
}
438-
}
439-
Some(
440-
self.calculate_return(Name::TreeName(TreeName::with_unknown_parent_scope(
441-
db,
442-
node_ref.file,
443-
n,
444-
))),
445-
)
405+
self.process_follow_import_result(check_node_ref_and_maybe_follow_import(
406+
self.infos.db,
407+
node_ref,
408+
follow_imports,
409+
))
446410
}
447411

448412
fn goto_name(&mut self, follow_imports: bool, check_inferred_attrs: bool) -> Option<Vec<T>> {
@@ -526,10 +490,10 @@ impl<'db, C: for<'a> FnMut(Name<'db, 'a>) -> T, T> GotoResolver<'db, C> {
526490
GotoNode::ImportFromAsName { import_as_name, .. } => Some(vec![self.try_to_follow(
527491
NodeRef::new(file, import_as_name.name_def().index()),
528492
follow_imports,
529-
)??]),
493+
)?]),
530494
GotoNode::GlobalName(name_def) | GotoNode::NonlocalName(name_def) => {
531495
let ref_ = NodeRef::new(file, name_def.index()).global_or_nonlocal_ref();
532-
if let Some(result) = self.try_to_follow(ref_, follow_imports).flatten() {
496+
if let Some(result) = self.try_to_follow(ref_, follow_imports) {
533497
Some(vec![result])
534498
} else {
535499
// This essentially just returns the name of the global definition, because we
@@ -591,6 +555,79 @@ impl<'db, C: for<'a> FnMut(Name<'db, 'a>) -> T, T> GotoResolver<'db, C> {
591555
}
592556
}
593557

558+
pub(crate) fn try_to_follow_imports<'db>(
559+
db: &'db Database,
560+
file: &'db PythonFile,
561+
n: CSTName,
562+
) -> Option<FollowImportResult<'db>> {
563+
let name_def = n.name_def()?;
564+
match name_def.maybe_import() {
565+
Some(NameImportParent::ImportFromAsName(_)) => {
566+
let ref_ = NodeRef::new(file, name_def.index());
567+
if let Some(result) = try_to_follow(db, ref_, true) {
568+
return result;
569+
}
570+
}
571+
Some(NameImportParent::DottedAsName(_)) => {
572+
let p = NodeRef::new(file, name_def.index()).point();
573+
if p.kind() == PointKind::FileReference {
574+
return Some(FollowImportResult::File(p.file_index()));
575+
}
576+
}
577+
None => {
578+
if matches!(
579+
name_def.parent(),
580+
NameDefParent::GlobalStmt | NameDefParent::NonlocalStmt
581+
) {
582+
let ref_ = NodeRef::new(file, name_def.index()).global_or_nonlocal_ref();
583+
if let Some(result) = try_to_follow(db, ref_, true) {
584+
return result;
585+
}
586+
}
587+
}
588+
}
589+
None
590+
}
591+
592+
fn try_to_follow<'db>(
593+
db: &'db Database,
594+
n: NodeRef<'db>,
595+
follow_imports: bool,
596+
) -> Option<Option<FollowImportResult<'db>>> {
597+
let p = n.point();
598+
if !p.calculated() {
599+
return None;
600+
}
601+
match p.kind() {
602+
PointKind::Redirect => Some(check_node_ref_and_maybe_follow_import(
603+
db,
604+
p.as_redirected_node_ref(db),
605+
follow_imports,
606+
)),
607+
PointKind::FileReference => Some(Some(FollowImportResult::File(p.file_index()))),
608+
_ => None,
609+
}
610+
}
611+
612+
fn check_node_ref_and_maybe_follow_import<'db>(
613+
db: &'db Database,
614+
node_ref: NodeRef<'db>,
615+
follow_imports: bool,
616+
) -> Option<FollowImportResult<'db>> {
617+
let n = node_ref.maybe_name()?;
618+
if follow_imports && let result @ Some(_) = try_to_follow_imports(db, node_ref.file, n) {
619+
return result;
620+
}
621+
Some(FollowImportResult::TreeName(
622+
TreeName::with_unknown_parent_scope(db, node_ref.file, n),
623+
))
624+
}
625+
626+
pub(crate) enum FollowImportResult<'db> {
627+
File(FileIndex),
628+
TreeName(TreeName<'db>),
629+
}
630+
594631
pub(crate) struct ReferencesResolver<'db, C, T> {
595632
infos: PositionalDocument<'db, GotoNode<'db>>,
596633
definitions: FastHashSet<(FileIndex, usize)>,

crates/zuban_python/src/name.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -498,7 +498,7 @@ impl<'db> TreeName<'db> {
498498
)))
499499
}
500500

501-
fn documentation(&self) -> Cow<'db, str> {
501+
pub(crate) fn documentation(&self) -> Cow<'db, str> {
502502
let doc = self.cst_name.clean_docstring();
503503
/*
504504
// If we don't have a result try to lookup the next assignment like
@@ -561,7 +561,7 @@ pub struct ModuleName<'db> {
561561
}
562562

563563
impl<'db> ModuleName<'db> {
564-
fn documentation(&self) -> Cow<'db, str> {
564+
pub(crate) fn documentation(&self) -> Cow<'db, str> {
565565
let result = self.file.tree.root().clean_docstring();
566566
process_docstring(self.file, result, || self.goto_non_stub())
567567
}

crates/zuban_python/tests/mypylike/skipped

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ testLiteralAndGenericsRespectsUpperBound
77
self_instance_variable1
88
testNewRedefineForStatementIndexNarrowing
99
goto_doc_contains_stdlib_compiled
10-
completion_wrong_ranges_after_non_empty_line
1110

1211
testNarrowingTypeVarMultiple
1312

crates/zuban_python/tests/mypylike/tests/completion.test

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -296,3 +296,104 @@ Asdf().fooba
296296
__main__.py:14:complete -> [Asdf:Some("hello")]
297297
__main__.py:16:complete -> [foobar:Some("world\n\n```python\n>>> asdf\nasdf\n```\n")]
298298
__main__.py:18:complete -> [foobar:Some("world\n\n```python\n>>> asdf\nasdf\n```\n")]
299+
300+
[case completion_docstr_from_import]
301+
# flags: --no-typecheck
302+
from m import Asdf
303+
#? complete --show-documentation
304+
Asd
305+
#? complete --show-documentation
306+
Asdf.fooba
307+
#? complete --show-documentation
308+
Asdf().fooba
309+
310+
[file m.py]
311+
# flags: --no-typecheck
312+
class Asdf:
313+
"""
314+
hello
315+
"""
316+
def foobar(self):
317+
"""
318+
world
319+
"""
320+
[file m.pyi]
321+
class Asdf:
322+
def foobar(self) -> None: ...
323+
324+
[out]
325+
__main__.py:4:complete -> [Asdf:Some("hello")]
326+
__main__.py:6:complete -> [foobar:Some("world")]
327+
__main__.py:8:complete -> [foobar:Some("world")]
328+
329+
[case completion_docstr_from_stubs]
330+
# flags: --no-typecheck
331+
from m import Asdf
332+
#? complete --show-documentation
333+
Asd
334+
#? complete --show-documentation
335+
Asdf.fooba
336+
#? complete --show-documentation
337+
Asdf().fooba
338+
339+
[file m.py]
340+
# flags: --no-typecheck
341+
class Asdf:
342+
"""
343+
hello
344+
"""
345+
def foobar(self):
346+
"""
347+
world
348+
"""
349+
[file m.pyi]
350+
class Asdf:
351+
def foobar(self) -> None: ...
352+
353+
[out]
354+
__main__.py:4:complete -> [Asdf:Some("hello")]
355+
__main__.py:6:complete -> [foobar:Some("world")]
356+
__main__.py:8:complete -> [foobar:Some("world")]
357+
358+
[case completion_modules]
359+
#? complete --show-documentation
360+
import mod_m
361+
#? complete --show-documentation
362+
import mod_n
363+
#? complete --show-documentation
364+
mod_m
365+
#? complete --show-documentation
366+
mod_n
367+
368+
#? complete --show-documentation
369+
mod_m.mod_o
370+
#? complete --show-documentation
371+
mod_m.mod_p
372+
373+
#? complete --show-documentation
374+
from mod_m import mod_o
375+
#? complete --show-documentation
376+
from mod_m import mod_p
377+
378+
[file mod_m.py]
379+
"m docstr"
380+
import mod_o
381+
import mod_p
382+
[file mod_n.py]
383+
"n docstr"
384+
[file mod_n.pyi]
385+
[file mod_o.py]
386+
"o docstr"
387+
[file mod_p.py]
388+
"p docstr"
389+
[file mod_p.pyi]
390+
391+
[out]
392+
__main__.py:2:complete -> [mod_m:Some("m docstr")]
393+
__main__.py:4:complete -> [mod_n:Some("n docstr")]
394+
__main__.py:6:complete -> [mod_m:Some("m docstr")]
395+
__main__.py:8:complete -> [mod_n:Some("n docstr")]
396+
__main__.py:11:complete -> [mod_o:Some("o docstr")]
397+
__main__.py:13:complete -> [mod_p:Some("p docstr")]
398+
__main__.py:16:complete -> [mod_o:Some("o docstr")]
399+
__main__.py:18:complete -> [mod_p:Some("p docstr")]

0 commit comments

Comments
 (0)