Unnamed repository; edit this file 'description' to name the repository.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
//! Representation of a `TextEdit`.
//!
//! `rust-analyzer` never mutates text itself and only sends diffs to clients,
//! so `TextEdit` is the ultimate representation of the work done by
//! rust-analyzer.

pub use text_size::{TextRange, TextSize};

/// `InsertDelete` -- a single "atomic" change to text
///
/// Must not overlap with other `InDel`s
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Indel {
    pub insert: String,
    /// Refers to offsets in the original text
    pub delete: TextRange,
}

#[derive(Default, Debug, Clone)]
pub struct TextEdit {
    /// Invariant: disjoint and sorted by `delete`.
    indels: Vec<Indel>,
}

#[derive(Debug, Default, Clone)]
pub struct TextEditBuilder {
    indels: Vec<Indel>,
}

impl Indel {
    pub fn insert(offset: TextSize, text: String) -> Indel {
        Indel::replace(TextRange::empty(offset), text)
    }
    pub fn delete(range: TextRange) -> Indel {
        Indel::replace(range, String::new())
    }
    pub fn replace(range: TextRange, replace_with: String) -> Indel {
        Indel { delete: range, insert: replace_with }
    }

    pub fn apply(&self, text: &mut String) {
        let start: usize = self.delete.start().into();
        let end: usize = self.delete.end().into();
        text.replace_range(start..end, &self.insert);
    }
}

impl TextEdit {
    pub fn builder() -> TextEditBuilder {
        TextEditBuilder::default()
    }

    pub fn insert(offset: TextSize, text: String) -> TextEdit {
        let mut builder = TextEdit::builder();
        builder.insert(offset, text);
        builder.finish()
    }

    pub fn delete(range: TextRange) -> TextEdit {
        let mut builder = TextEdit::builder();
        builder.delete(range);
        builder.finish()
    }

    pub fn replace(range: TextRange, replace_with: String) -> TextEdit {
        let mut builder = TextEdit::builder();
        builder.replace(range, replace_with);
        builder.finish()
    }

    pub fn len(&self) -> usize {
        self.indels.len()
    }

    pub fn is_empty(&self) -> bool {
        self.indels.is_empty()
    }

    pub fn iter(&self) -> std::slice::Iter<'_, Indel> {
        self.into_iter()
    }

    pub fn apply(&self, text: &mut String) {
        match self.len() {
            0 => return,
            1 => {
                self.indels[0].apply(text);
                return;
            }
            _ => (),
        }

        let mut total_len = TextSize::of(&*text);
        for indel in &self.indels {
            total_len += TextSize::of(&indel.insert);
            total_len -= indel.delete.end() - indel.delete.start();
        }
        let mut buf = String::with_capacity(total_len.into());
        let mut prev = 0;
        for indel in &self.indels {
            let start: usize = indel.delete.start().into();
            let end: usize = indel.delete.end().into();
            if start > prev {
                buf.push_str(&text[prev..start]);
            }
            buf.push_str(&indel.insert);
            prev = end;
        }
        buf.push_str(&text[prev..text.len()]);
        assert_eq!(TextSize::of(&buf), total_len);

        // FIXME: figure out a way to mutate the text in-place or reuse the
        // memory in some other way
        *text = buf;
    }

    pub fn union(&mut self, other: TextEdit) -> Result<(), TextEdit> {
        // FIXME: can be done without allocating intermediate vector
        let mut all = self.iter().chain(other.iter()).collect::<Vec<_>>();
        if !check_disjoint_and_sort(&mut all) {
            return Err(other);
        }

        self.indels.extend(other.indels);
        check_disjoint_and_sort(&mut self.indels);
        // Only dedup deletions and replacements, keep all insertions
        self.indels.dedup_by(|a, b| a == b && !a.delete.is_empty());
        Ok(())
    }

    pub fn apply_to_offset(&self, offset: TextSize) -> Option<TextSize> {
        let mut res = offset;
        for indel in &self.indels {
            if indel.delete.start() >= offset {
                break;
            }
            if offset < indel.delete.end() {
                return None;
            }
            res += TextSize::of(&indel.insert);
            res -= indel.delete.len();
        }
        Some(res)
    }
}

impl IntoIterator for TextEdit {
    type Item = Indel;
    type IntoIter = std::vec::IntoIter<Indel>;

    fn into_iter(self) -> Self::IntoIter {
        self.indels.into_iter()
    }
}

impl<'a> IntoIterator for &'a TextEdit {
    type Item = &'a Indel;
    type IntoIter = std::slice::Iter<'a, Indel>;

    fn into_iter(self) -> Self::IntoIter {
        self.indels.iter()
    }
}

impl TextEditBuilder {
    pub fn is_empty(&self) -> bool {
        self.indels.is_empty()
    }
    pub fn replace(&mut self, range: TextRange, replace_with: String) {
        self.indel(Indel::replace(range, replace_with));
    }
    pub fn delete(&mut self, range: TextRange) {
        self.indel(Indel::delete(range));
    }
    pub fn insert(&mut self, offset: TextSize, text: String) {
        self.indel(Indel::insert(offset, text));
    }
    pub fn finish(self) -> TextEdit {
        let mut indels = self.indels;
        assert_disjoint_or_equal(&mut indels);
        TextEdit { indels }
    }
    pub fn invalidates_offset(&self, offset: TextSize) -> bool {
        self.indels.iter().any(|indel| indel.delete.contains_inclusive(offset))
    }
    fn indel(&mut self, indel: Indel) {
        self.indels.push(indel);
        if self.indels.len() <= 16 {
            assert_disjoint_or_equal(&mut self.indels);
        }
    }
}

fn assert_disjoint_or_equal(indels: &mut [Indel]) {
    assert!(check_disjoint_and_sort(indels));
}
// FIXME: Remove the impl Bound here, it shouldn't be needed
fn check_disjoint_and_sort(indels: &mut [impl std::borrow::Borrow<Indel>]) -> bool {
    indels.sort_by_key(|indel| (indel.borrow().delete.start(), indel.borrow().delete.end()));
    indels.iter().zip(indels.iter().skip(1)).all(|(l, r)| {
        let l = l.borrow();
        let r = r.borrow();
        l.delete.end() <= r.delete.start() || l == r
    })
}
3'>433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106
//! A map of all publicly exported items in a crate.

use std::{fmt, hash::BuildHasherDefault, sync::Arc};

use base_db::CrateId;
use fst::{self, Streamer};
use hir_expand::name::Name;
use indexmap::{map::Entry, IndexMap};
use itertools::Itertools;
use rustc_hash::{FxHashSet, FxHasher};

use crate::{
    db::DefDatabase, item_scope::ItemInNs, visibility::Visibility, AssocItemId, ModuleDefId,
    ModuleId, TraitId,
};

type FxIndexMap<K, V> = IndexMap<K, V, BuildHasherDefault<FxHasher>>;

/// Item import details stored in the `ImportMap`.
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct ImportInfo {
    /// A path that can be used to import the item, relative to the crate's root.
    pub path: ImportPath,
    /// The module containing this item.
    pub container: ModuleId,
    /// Whether the import is a trait associated item or not.
    pub is_trait_assoc_item: bool,
}

#[derive(Debug, Clone, Eq, PartialEq)]
pub struct ImportPath {
    pub segments: Vec<Name>,
}

impl fmt::Display for ImportPath {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        fmt::Display::fmt(&self.segments.iter().format("::"), f)
    }
}

impl ImportPath {
    fn len(&self) -> usize {
        self.segments.len()
    }
}

/// A map from publicly exported items to the path needed to import/name them from a downstream
/// crate.
///
/// Reexports of items are taken into account, ie. if something is exported under multiple
/// names, the one with the shortest import path will be used.
///
/// Note that all paths are relative to the containing crate's root, so the crate name still needs
/// to be prepended to the `ModPath` before the path is valid.
#[derive(Default)]
pub struct ImportMap {
    map: FxIndexMap<ItemInNs, ImportInfo>,

    /// List of keys stored in `map`, sorted lexicographically by their `ModPath`. Indexed by the
    /// values returned by running `fst`.
    ///
    /// Since a path can refer to multiple items due to namespacing, we store all items with the
    /// same path right after each other. This allows us to find all items after the FST gives us
    /// the index of the first one.
    importables: Vec<ItemInNs>,
    fst: fst::Map<Vec<u8>>,
}

impl ImportMap {
    pub fn import_map_query(db: &dyn DefDatabase, krate: CrateId) -> Arc<Self> {
        let _p = profile::span("import_map_query");

        let mut import_map = collect_import_map(db, krate);

        let mut importables = import_map
            .map
            .iter()
            .map(|(item, info)| (item, fst_path(&info.path)))
            .collect::<Vec<_>>();
        importables.sort_by(|(_, fst_path), (_, fst_path2)| fst_path.cmp(fst_path2));

        // Build the FST, taking care not to insert duplicate values.

        let mut builder = fst::MapBuilder::memory();
        let mut last_batch_start = 0;

        for idx in 0..importables.len() {
            let key = &importables[last_batch_start].1;
            if let Some((_, fst_path)) = importables.get(idx + 1) {
                if key == fst_path {
                    continue;
                }
            }

            let _ = builder.insert(key, last_batch_start as u64);

            last_batch_start = idx + 1;
        }

        import_map.fst = builder.into_map();
        import_map.importables = importables.iter().map(|&(&item, _)| item).collect();

        Arc::new(import_map)
    }

    /// Returns the `ModPath` needed to import/mention `item`, relative to this crate's root.
    pub fn path_of(&self, item: ItemInNs) -> Option<&ImportPath> {
        self.import_info_for(item).map(|it| &it.path)
    }

    pub fn import_info_for(&self, item: ItemInNs) -> Option<&ImportInfo> {
        self.map.get(&item)
    }

    fn collect_trait_assoc_items(
        &mut self,
        db: &dyn DefDatabase,
        tr: TraitId,
        is_type_in_ns: bool,
        original_import_info: &ImportInfo,
    ) {
        let _p = profile::span("collect_trait_assoc_items");
        for (assoc_item_name, item) in &db.trait_data(tr).items {
            let module_def_id = match item {
                AssocItemId::FunctionId(f) => ModuleDefId::from(*f),
                AssocItemId::ConstId(c) => ModuleDefId::from(*c),
                // cannot use associated type aliases directly: need a `<Struct as Trait>::TypeAlias`
                // qualifier, ergo no need to store it for imports in import_map
                AssocItemId::TypeAliasId(_) => {
                    cov_mark::hit!(type_aliases_ignored);
                    continue;
                }
            };
            let assoc_item = if is_type_in_ns {
                ItemInNs::Types(module_def_id)
            } else {
                ItemInNs::Values(module_def_id)
            };

            let mut assoc_item_info = original_import_info.clone();
            assoc_item_info.path.segments.push(assoc_item_name.to_owned());
            assoc_item_info.is_trait_assoc_item = true;
            self.map.insert(assoc_item, assoc_item_info);
        }
    }
}

fn collect_import_map(db: &dyn DefDatabase, krate: CrateId) -> ImportMap {
    let _p = profile::span("collect_import_map");

    let def_map = db.crate_def_map(krate);
    let mut import_map = ImportMap::default();

    // We look only into modules that are public(ly reexported), starting with the crate root.
    let empty = ImportPath { segments: vec![] };
    let root = def_map.module_id(def_map.root());
    let mut worklist = vec![(root, empty)];
    while let Some((module, mod_path)) = worklist.pop() {
        let ext_def_map;
        let mod_data = if module.krate == krate {
            &def_map[module.local_id]
        } else {
            // The crate might reexport a module defined in another crate.
            ext_def_map = module.def_map(db);
            &ext_def_map[module.local_id]
        };

        let visible_items = mod_data.scope.entries().filter_map(|(name, per_ns)| {
            let per_ns = per_ns.filter_visibility(|vis| vis == Visibility::Public);
            if per_ns.is_none() {
                None
            } else {
                Some((name, per_ns))
            }
        });

        for (name, per_ns) in visible_items {
            let mk_path = || {
                let mut path = mod_path.clone();
                path.segments.push(name.clone());
                path
            };

            for item in per_ns.iter_items() {
                let path = mk_path();
                let path_len = path.len();
                let import_info =
                    ImportInfo { path, container: module, is_trait_assoc_item: false };

                if let Some(ModuleDefId::TraitId(tr)) = item.as_module_def_id() {
                    import_map.collect_trait_assoc_items(
                        db,
                        tr,
                        matches!(item, ItemInNs::Types(_)),
                        &import_info,
                    );
                }

                match import_map.map.entry(item) {
                    Entry::Vacant(entry) => {
                        entry.insert(import_info);
                    }
                    Entry::Occupied(mut entry) => {
                        // If the new path is shorter, prefer that one.
                        if path_len < entry.get().path.len() {
                            *entry.get_mut() = import_info;
                        } else {
                            continue;
                        }
                    }
                }

                // If we've just added a path to a module, descend into it. We might traverse
                // modules multiple times, but only if the new path to it is shorter than the
                // first (else we `continue` above).
                if let Some(ModuleDefId::ModuleId(mod_id)) = item.as_module_def_id() {
                    worklist.push((mod_id, mk_path()));
                }
            }
        }
    }

    import_map
}

impl PartialEq for ImportMap {
    fn eq(&self, other: &Self) -> bool {
        // `fst` and `importables` are built from `map`, so we don't need to compare them.
        self.map == other.map
    }
}

impl Eq for ImportMap {}

impl fmt::Debug for ImportMap {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let mut importable_paths: Vec<_> = self
            .map
            .iter()
            .map(|(item, info)| {
                let ns = match item {
                    ItemInNs::Types(_) => "t",
                    ItemInNs::Values(_) => "v",
                    ItemInNs::Macros(_) => "m",
                };
                format!("- {} ({})", info.path, ns)
            })
            .collect();

        importable_paths.sort();
        f.write_str(&importable_paths.join("\n"))
    }
}

fn fst_path(path: &ImportPath) -> String {
    let _p = profile::span("fst_path");
    let mut s = path.to_string();
    s.make_ascii_lowercase();
    s
}

#[derive(Debug, Eq, PartialEq, Hash)]
pub enum ImportKind {
    Module,
    Function,
    Adt,
    EnumVariant,
    Const,
    Static,
    Trait,
    TypeAlias,
    BuiltinType,
    AssociatedItem,
}

/// A way to match import map contents against the search query.
#[derive(Debug)]
pub enum SearchMode {
    /// Import map entry should strictly match the query string.
    Equals,
    /// Import map entry should contain the query string.
    Contains,
    /// Import map entry should contain all letters from the query string,
    /// in the same order, but not necessary adjacent.
    Fuzzy,
}

#[derive(Debug)]
pub struct Query {
    query: String,
    lowercased: String,
    name_only: bool,
    assoc_items_only: bool,
    search_mode: SearchMode,
    case_sensitive: bool,
    limit: usize,
    exclude_import_kinds: FxHashSet<ImportKind>,
}

impl Query {
    pub fn new(query: String) -> Self {
        let lowercased = query.to_lowercase();
        Self {
            query,
            lowercased,
            name_only: false,
            assoc_items_only: false,
            search_mode: SearchMode::Contains,
            case_sensitive: false,
            limit: usize::max_value(),
            exclude_import_kinds: FxHashSet::default(),
        }
    }

    /// Matches entries' names only, ignoring the rest of
    /// the qualifier.
    /// Example: for `std::marker::PhantomData`, the name is `PhantomData`.
    pub fn name_only(self) -> Self {
        Self { name_only: true, ..self }
    }

    /// Matches only the entries that are associated items, ignoring the rest.
    pub fn assoc_items_only(self) -> Self {
        Self { assoc_items_only: true, ..self }
    }

    /// Specifies the way to search for the entries using the query.
    pub fn search_mode(self, search_mode: SearchMode) -> Self {
        Self { search_mode, ..self }
    }

    /// Limits the returned number of items to `limit`.
    pub fn limit(self, limit: usize) -> Self {
        Self { limit, ..self }
    }

    /// Respect casing of the query string when matching.
    pub fn case_sensitive(self) -> Self {
        Self { case_sensitive: true, ..self }
    }

    /// Do not include imports of the specified kind in the search results.
    pub fn exclude_import_kind(mut self, import_kind: ImportKind) -> Self {
        self.exclude_import_kinds.insert(import_kind);
        self
    }

    fn import_matches(&self, import: &ImportInfo, enforce_lowercase: bool) -> bool {
        let _p = profile::span("import_map::Query::import_matches");
        if import.is_trait_assoc_item {
            if self.exclude_import_kinds.contains(&ImportKind::AssociatedItem) {
                return false;
            }
        } else if self.assoc_items_only {
            return false;
        }

        let mut input = if import.is_trait_assoc_item || self.name_only {
            import.path.segments.last().unwrap().to_string()
        } else {
            import.path.to_string()
        };
        if enforce_lowercase || !self.case_sensitive {
            input.make_ascii_lowercase();
        }

        let query_string =
            if !enforce_lowercase && self.case_sensitive { &self.query } else { &self.lowercased };

        match self.search_mode {
            SearchMode::Equals => &input == query_string,
            SearchMode::Contains => input.contains(query_string),
            SearchMode::Fuzzy => {
                let mut unchecked_query_chars = query_string.chars();
                let mut mismatching_query_char = unchecked_query_chars.next();

                for input_char in input.chars() {
                    match mismatching_query_char {
                        None => return true,
                        Some(matching_query_char) if matching_query_char == input_char => {
                            mismatching_query_char = unchecked_query_chars.next();
                        }
                        _ => (),
                    }
                }
                mismatching_query_char.is_none()
            }
        }
    }
}

/// Searches dependencies of `krate` for an importable path matching `query`.
///
/// This returns a list of items that could be imported from dependencies of `krate`.
pub fn search_dependencies<'a>(
    db: &'a dyn DefDatabase,
    krate: CrateId,
    query: Query,
) -> FxHashSet<ItemInNs> {
    let _p = profile::span("search_dependencies").detail(|| format!("{:?}", query));

    let graph = db.crate_graph();
    let import_maps: Vec<_> =
        graph[krate].dependencies.iter().map(|dep| db.import_map(dep.crate_id)).collect();

    let automaton = fst::automaton::Subsequence::new(&query.lowercased);

    let mut op = fst::map::OpBuilder::new();
    for map in &import_maps {
        op = op.add(map.fst.search(&automaton));
    }

    let mut stream = op.union();

    let mut all_indexed_values = FxHashSet::default();
    while let Some((_, indexed_values)) = stream.next() {
        all_indexed_values.extend(indexed_values.iter().copied());
    }

    let mut res = FxHashSet::default();
    for indexed_value in all_indexed_values {
        let import_map = &import_maps[indexed_value.index];
        let importables = &import_map.importables[indexed_value.value as usize..];

        let common_importable_data = &import_map.map[&importables[0]];
        if !query.import_matches(common_importable_data, true) {
            continue;
        }

        // Path shared by the importable items in this group.
        let common_importables_path_fst = fst_path(&common_importable_data.path);
        // Add the items from this `ModPath` group. Those are all subsequent items in
        // `importables` whose paths match `path`.
        let iter = importables
            .iter()
            .copied()
            .take_while(|item| common_importables_path_fst == fst_path(&import_map.map[item].path))
            .filter(|&item| match item_import_kind(item) {
                Some(import_kind) => !query.exclude_import_kinds.contains(&import_kind),
                None => true,
            })
            .filter(|item| {
                !query.case_sensitive // we've already checked the common importables path case-insensitively
                        || query.import_matches(&import_map.map[item], false)
            });
        res.extend(iter);

        if res.len() >= query.limit {
            return res;
        }
    }

    res
}

fn item_import_kind(item: ItemInNs) -> Option<ImportKind> {
    Some(match item.as_module_def_id()? {
        ModuleDefId::ModuleId(_) => ImportKind::Module,
        ModuleDefId::FunctionId(_) => ImportKind::Function,
        ModuleDefId::AdtId(_) => ImportKind::Adt,
        ModuleDefId::EnumVariantId(_) => ImportKind::EnumVariant,
        ModuleDefId::ConstId(_) => ImportKind::Const,
        ModuleDefId::StaticId(_) => ImportKind::Static,
        ModuleDefId::TraitId(_) => ImportKind::Trait,
        ModuleDefId::TypeAliasId(_) => ImportKind::TypeAlias,
        ModuleDefId::BuiltinType(_) => ImportKind::BuiltinType,
    })
}

#[cfg(test)]
mod tests {
    use base_db::{fixture::WithFixture, SourceDatabase, Upcast};
    use expect_test::{expect, Expect};

    use crate::{test_db::TestDB, ItemContainerId, Lookup};

    use super::*;

    fn check_search(ra_fixture: &str, crate_name: &str, query: Query, expect: Expect) {
        let db = TestDB::with_files(ra_fixture);
        let crate_graph = db.crate_graph();
        let krate = crate_graph
            .iter()
            .find(|krate| {
                crate_graph[*krate].display_name.as_ref().map(|n| n.to_string())
                    == Some(crate_name.to_string())
            })
            .unwrap();

        let actual = search_dependencies(db.upcast(), krate, query)
            .into_iter()
            .filter_map(|dependency| {
                let dependency_krate = dependency.krate(db.upcast())?;
                let dependency_imports = db.import_map(dependency_krate);

                let (path, mark) = match assoc_item_path(&db, &dependency_imports, dependency) {
                    Some(assoc_item_path) => (assoc_item_path, "a"),
                    None => (
                        dependency_imports.path_of(dependency)?.to_string(),
                        match dependency {
                            ItemInNs::Types(ModuleDefId::FunctionId(_))
                            | ItemInNs::Values(ModuleDefId::FunctionId(_)) => "f",
                            ItemInNs::Types(_) => "t",
                            ItemInNs::Values(_) => "v",
                            ItemInNs::Macros(_) => "m",
                        },
                    ),
                };

                Some(format!(
                    "{}::{} ({})\n",
                    crate_graph[dependency_krate].display_name.as_ref()?,
                    path,
                    mark
                ))
            })
            .collect::<String>();
        expect.assert_eq(&actual)
    }

    fn assoc_item_path(
        db: &dyn DefDatabase,
        dependency_imports: &ImportMap,
        dependency: ItemInNs,
    ) -> Option<String> {
        let dependency_assoc_item_id = match dependency {
            ItemInNs::Types(ModuleDefId::FunctionId(id))
            | ItemInNs::Values(ModuleDefId::FunctionId(id)) => AssocItemId::from(id),
            ItemInNs::Types(ModuleDefId::ConstId(id))
            | ItemInNs::Values(ModuleDefId::ConstId(id)) => AssocItemId::from(id),
            ItemInNs::Types(ModuleDefId::TypeAliasId(id))
            | ItemInNs::Values(ModuleDefId::TypeAliasId(id)) => AssocItemId::from(id),
            _ => return None,
        };

        let trait_ = assoc_to_trait(db, dependency)?;
        if let ModuleDefId::TraitId(tr) = trait_.as_module_def_id()? {
            let trait_data = db.trait_data(tr);
            let assoc_item_name =
                trait_data.items.iter().find_map(|(assoc_item_name, assoc_item_id)| {
                    if &dependency_assoc_item_id == assoc_item_id {
                        Some(assoc_item_name)
                    } else {
                        None
                    }
                })?;
            return Some(format!("{}::{}", dependency_imports.path_of(trait_)?, assoc_item_name));
        }
        None
    }

    fn assoc_to_trait(db: &dyn DefDatabase, item: ItemInNs) -> Option<ItemInNs> {
        let assoc: AssocItemId = match item {
            ItemInNs::Types(it) | ItemInNs::Values(it) => match it {
                ModuleDefId::TypeAliasId(it) => it.into(),
                ModuleDefId::FunctionId(it) => it.into(),
                ModuleDefId::ConstId(it) => it.into(),
                _ => return None,
            },
            _ => return None,
        };

        let container = match assoc {
            AssocItemId::FunctionId(it) => it.lookup(db).container,
            AssocItemId::ConstId(it) => it.lookup(db).container,
            AssocItemId::TypeAliasId(it) => it.lookup(db).container,
        };

        match container {
            ItemContainerId::TraitId(it) => Some(ItemInNs::Types(it.into())),
            _ => None,
        }
    }

    fn check(ra_fixture: &str, expect: Expect) {
        let db = TestDB::with_files(ra_fixture);
        let crate_graph = db.crate_graph();

        let actual = crate_graph
            .iter()
            .filter_map(|krate| {
                let cdata = &crate_graph[krate];
                let name = cdata.display_name.as_ref()?;

                let map = db.import_map(krate);

                Some(format!("{}:\n{:?}\n", name, map))
            })
            .collect::<String>();

        expect.assert_eq(&actual)
    }

    #[test]
    fn smoke() {
        check(
            r"
            //- /main.rs crate:main deps:lib

            mod private {
                pub use lib::Pub;
                pub struct InPrivateModule;
            }

            pub mod publ1 {
                use lib::Pub;
            }

            pub mod real_pub {
                pub use lib::Pub;
            }
            pub mod real_pu2 { // same path length as above
                pub use lib::Pub;
            }

            //- /lib.rs crate:lib
            pub struct Pub {}
            pub struct Pub2; // t + v
            struct Priv;
        ",
            expect![[r#"
                main:
                - publ1 (t)
                - real_pu2 (t)
                - real_pub (t)
                - real_pub::Pub (t)
                lib:
                - Pub (t)
                - Pub2 (t)
                - Pub2 (v)
            "#]],
        );
    }

    #[test]
    fn prefers_shortest_path() {
        check(
            r"
            //- /main.rs crate:main

            pub mod sub {
                pub mod subsub {
                    pub struct Def {}
                }

                pub use super::sub::subsub::Def;
            }
        ",
            expect![[r#"
                main:
                - sub (t)
                - sub::Def (t)
                - sub::subsub (t)
            "#]],
        );
    }

    #[test]
    fn type_reexport_cross_crate() {
        // Reexports need to be visible from a crate, even if the original crate exports the item
        // at a shorter path.
        check(
            r"
            //- /main.rs crate:main deps:lib
            pub mod m {
                pub use lib::S;
            }
            //- /lib.rs crate:lib
            pub struct S;
        ",
            expect![[r#"
                main:
                - m (t)
                - m::S (t)
                - m::S (v)
                lib:
                - S (t)
                - S (v)
            "#]],
        );
    }

    #[test]
    fn macro_reexport() {
        check(
            r"
            //- /main.rs crate:main deps:lib
            pub mod m {
                pub use lib::pub_macro;
            }
            //- /lib.rs crate:lib
            #[macro_export]
            macro_rules! pub_macro {
                () => {};
            }
        ",
            expect![[r#"
                main:
                - m (t)
                - m::pub_macro (m)
                lib:
                - pub_macro (m)
            "#]],
        );
    }

    #[test]
    fn module_reexport() {
        // Reexporting modules from a dependency adds all contents to the import map.
        check(
            r"
            //- /main.rs crate:main deps:lib
            pub use lib::module as reexported_module;
            //- /lib.rs crate:lib
            pub mod module {
                pub struct S;
            }
        ",
            expect![[r#"
                main:
                - reexported_module (t)
                - reexported_module::S (t)
                - reexported_module::S (v)
                lib:
                - module (t)
                - module::S (t)
                - module::S (v)
            "#]],
        );
    }

    #[test]
    fn cyclic_module_reexport() {
        // A cyclic reexport does not hang.
        check(
            r"
            //- /lib.rs crate:lib
            pub mod module {
                pub struct S;
                pub use super::sub::*;
            }

            pub mod sub {
                pub use super::module;
            }
        ",
            expect![[r#"
                lib:
                - module (t)
                - module::S (t)
                - module::S (v)
                - sub (t)
            "#]],
        );
    }

    #[test]
    fn private_macro() {
        check(
            r"
            //- /lib.rs crate:lib
            macro_rules! private_macro {
                () => {};
            }
        ",
            expect![[r#"
                lib:

            "#]],
        );
    }

    #[test]
    fn namespacing() {
        check(
            r"
            //- /lib.rs crate:lib
            pub struct Thing;     // t + v
            #[macro_export]
            macro_rules! Thing {  // m
                () => {};
            }
        ",
            expect![[r#"
                lib:
                - Thing (m)
                - Thing (t)
                - Thing (v)
            "#]],
        );

        check(
            r"
            //- /lib.rs crate:lib
            pub mod Thing {}      // t
            #[macro_export]
            macro_rules! Thing {  // m
                () => {};
            }
        ",
            expect![[r#"
                lib:
                - Thing (m)
                - Thing (t)
            "#]],
        );
    }

    #[test]
    fn fuzzy_import_trait_and_assoc_items() {
        cov_mark::check!(type_aliases_ignored);
        let ra_fixture = r#"
        //- /main.rs crate:main deps:dep
        //- /dep.rs crate:dep
        pub mod fmt {
            pub trait Display {
                type FmtTypeAlias;
                const FMT_CONST: bool;

                fn format_function();
                fn format_method(&self);
            }
        }
    "#;

        check_search(
            ra_fixture,
            "main",
            Query::new("fmt".to_string()).search_mode(SearchMode::Fuzzy),
            expect![[r#"
                dep::fmt (t)
                dep::fmt::Display::format_method (a)
                dep::fmt::Display (t)
                dep::fmt::Display::FMT_CONST (a)
                dep::fmt::Display::format_function (a)
            "#]],
        );
    }

    #[test]
    fn assoc_items_filtering() {
        let ra_fixture = r#"
        //- /main.rs crate:main deps:dep
        //- /dep.rs crate:dep
        pub mod fmt {
            pub trait Display {
                type FmtTypeAlias;
                const FMT_CONST: bool;

                fn format_function();
                fn format_method(&self);
            }
        }
    "#;

        check_search(
            ra_fixture,
            "main",
            Query::new("fmt".to_string()).search_mode(SearchMode::Fuzzy).assoc_items_only(),
            expect![[r#"
            dep::fmt::Display::format_method (a)
            dep::fmt::Display::FMT_CONST (a)
            dep::fmt::Display::format_function (a)
        "#]],
        );

        check_search(
            ra_fixture,
            "main",
            Query::new("fmt".to_string())
                .search_mode(SearchMode::Fuzzy)
                .exclude_import_kind(ImportKind::AssociatedItem),
            expect![[r#"
            dep::fmt (t)
            dep::fmt::Display (t)
        "#]],
        );

        check_search(
            ra_fixture,
            "main",
            Query::new("fmt".to_string())
                .search_mode(SearchMode::Fuzzy)
                .assoc_items_only()
                .exclude_import_kind(ImportKind::AssociatedItem),
            expect![[r#""#]],
        );
    }

    #[test]
    fn search_mode() {
        let ra_fixture = r#"
            //- /main.rs crate:main deps:dep
            //- /dep.rs crate:dep deps:tdep
            use tdep::fmt as fmt_dep;
            pub mod fmt {
                pub trait Display {
                    fn fmt();
                }
            }
            #[macro_export]
            macro_rules! Fmt {
                () => {};
            }
            pub struct Fmt;

            pub fn format() {}
            pub fn no() {}

            //- /tdep.rs crate:tdep
            pub mod fmt {
                pub struct NotImportableFromMain;
            }
        "#;

        check_search(
            ra_fixture,
            "main",
            Query::new("fmt".to_string()).search_mode(SearchMode::Fuzzy),
            expect![[r#"
                dep::fmt (t)
                dep::format (f)
                dep::Fmt (v)
                dep::Fmt (m)
                dep::Fmt (t)
                dep::fmt::Display::fmt (a)
                dep::fmt::Display (t)
            "#]],
        );

        check_search(
            ra_fixture,
            "main",
            Query::new("fmt".to_string()).search_mode(SearchMode::Equals),
            expect![[r#"
                dep::fmt (t)
                dep::Fmt (v)
                dep::Fmt (m)
                dep::Fmt (t)
                dep::fmt::Display::fmt (a)
            "#]],
        );

        check_search(
            ra_fixture,
            "main",
            Query::new("fmt".to_string()).search_mode(SearchMode::Contains),
            expect![[r#"
                dep::fmt (t)
                dep::Fmt (v)
                dep::Fmt (m)
                dep::Fmt (t)
                dep::fmt::Display::fmt (a)
                dep::fmt::Display (t)
            "#]],
        );
    }

    #[test]
    fn name_only() {
        let ra_fixture = r#"
            //- /main.rs crate:main deps:dep
            //- /dep.rs crate:dep deps:tdep
            use tdep::fmt as fmt_dep;
            pub mod fmt {
                pub trait Display {
                    fn fmt();
                }
            }
            #[macro_export]
            macro_rules! Fmt {
                () => {};
            }
            pub struct Fmt;

            pub fn format() {}
            pub fn no() {}

            //- /tdep.rs crate:tdep
            pub mod fmt {
                pub struct NotImportableFromMain;
            }
        "#;

        check_search(
            ra_fixture,
            "main",
            Query::new("fmt".to_string()),
            expect![[r#"
                dep::fmt (t)
                dep::Fmt (v)
                dep::Fmt (m)
                dep::Fmt (t)
                dep::fmt::Display::fmt (a)
                dep::fmt::Display (t)
            "#]],
        );

        check_search(
            ra_fixture,
            "main",
            Query::new("fmt".to_string()).name_only(),
            expect![[r#"
                dep::fmt (t)
                dep::Fmt (v)
                dep::Fmt (m)
                dep::Fmt (t)
                dep::fmt::Display::fmt (a)
            "#]],
        );
    }

    #[test]
    fn search_casing() {
        let ra_fixture = r#"
            //- /main.rs crate:main deps:dep
            //- /dep.rs crate:dep

            pub struct fmt;
            pub struct FMT;
        "#;

        check_search(
            ra_fixture,
            "main",
            Query::new("FMT".to_string()),
            expect![[r#"
                dep::fmt (t)
                dep::FMT (v)
                dep::fmt (v)
                dep::FMT (t)
            "#]],
        );

        check_search(
            ra_fixture,
            "main",
            Query::new("FMT".to_string()).case_sensitive(),
            expect![[r#"
                dep::FMT (t)
                dep::FMT (v)
            "#]],
        );
    }

    #[test]
    fn search_limit() {
        check_search(
            r#"
        //- /main.rs crate:main deps:dep
        //- /dep.rs crate:dep
        pub mod fmt {
            pub trait Display {
                fn fmt();
            }
        }
        #[macro_export]
        macro_rules! Fmt {
            () => {};
        }
        pub struct Fmt;

        pub fn format() {}
        pub fn no() {}
    "#,
            "main",
            Query::new("".to_string()).limit(2),
            expect![[r#"
                dep::fmt (t)
                dep::Fmt (m)
                dep::Fmt (t)
                dep::Fmt (v)
            "#]],
        );
    }

    #[test]
    fn search_exclusions() {
        let ra_fixture = r#"
            //- /main.rs crate:main deps:dep
            //- /dep.rs crate:dep

            pub struct fmt;
            pub struct FMT;
        "#;

        check_search(
            ra_fixture,
            "main",
            Query::new("FMT".to_string()),
            expect![[r#"
                dep::fmt (t)
                dep::FMT (v)
                dep::fmt (v)
                dep::FMT (t)
            "#]],
        );

        check_search(
            ra_fixture,
            "main",
            Query::new("FMT".to_string()).exclude_import_kind(ImportKind::Adt),
            expect![[r#""#]],
        );
    }
}