Skip to main content

rust_analyzer/cli/
scip.rs

1//! SCIP generator
2
3use std::{path::PathBuf, time::Instant};
4
5use ide::{
6    AnalysisHost, LineCol, Moniker, MonikerDescriptorKind, MonikerIdentifier, MonikerResult,
7    RootDatabase, StaticIndex, StaticIndexedFile, SymbolInformationKind, TextRange, TokenId,
8    TokenStaticData, VendoredLibrariesConfig,
9};
10use ide_db::line_index;
11use load_cargo::{LoadCargoConfig, ProcMacroServerChoice, load_workspace_at};
12use rustc_hash::{FxHashMap, FxHashSet};
13use scip::types::{self as scip_types, SymbolInformation};
14use tracing::error;
15use vfs::FileId;
16
17use crate::{
18    cli::flags,
19    config::ConfigChange,
20    line_index::{LineEndings, LineIndex, PositionEncoding},
21};
22
23impl flags::Scip {
24    pub fn run(self) -> anyhow::Result<()> {
25        eprintln!("Generating SCIP start...");
26        let now = Instant::now();
27
28        let no_progress = &|s| eprintln!("rust-analyzer: Loading {s}");
29        let root =
30            vfs::AbsPathBuf::assert_utf8(std::env::current_dir()?.join(&self.path)).normalize();
31
32        let mut config = crate::config::Config::new(
33            root.clone(),
34            lsp_types::ClientCapabilities::default(),
35            vec![],
36            None,
37        );
38
39        if let Some(p) = self.config_path {
40            let mut file = std::io::BufReader::new(std::fs::File::open(p)?);
41            let json = serde_json::from_reader(&mut file)?;
42            let mut change = ConfigChange::default();
43            change.change_client_config(json);
44
45            let error_sink;
46            (config, error_sink, _) = config.apply_change(change);
47
48            // FIXME @alibektas : What happens to errors without logging?
49            error!(?error_sink, "Config Error(s)");
50        }
51        let load_cargo_config = LoadCargoConfig {
52            load_out_dirs_from_check: true,
53            with_proc_macro_server: ProcMacroServerChoice::Sysroot,
54            prefill_caches: true,
55            num_worker_threads: self.num_threads.unwrap_or_else(num_cpus::get_physical),
56            proc_macro_processes: config.proc_macro_num_processes(),
57        };
58        let cargo_config = config.cargo(None);
59        let (db, vfs, _) = load_workspace_at(
60            root.as_path().as_ref(),
61            &cargo_config,
62            &load_cargo_config,
63            &no_progress,
64        )?;
65        let host = AnalysisHost::with_database(db);
66        let db = host.raw_database();
67        let analysis = host.analysis();
68
69        let vendored_libs_config = if self.exclude_vendored_libraries {
70            VendoredLibrariesConfig::Excluded
71        } else {
72            VendoredLibrariesConfig::Included { workspace_root: &root.clone().into() }
73        };
74
75        let si = StaticIndex::compute(&analysis, vendored_libs_config);
76
77        let metadata = scip_types::Metadata {
78            version: scip_types::ProtocolVersion::UnspecifiedProtocolVersion.into(),
79            tool_info: Some(scip_types::ToolInfo {
80                name: "rust-analyzer".to_owned(),
81                version: format!("{}", crate::version::version()),
82                arguments: vec![],
83                special_fields: Default::default(),
84            })
85            .into(),
86            project_root: format!("file://{root}"),
87            text_document_encoding: scip_types::TextEncoding::UTF8.into(),
88            special_fields: Default::default(),
89        };
90
91        let mut documents = Vec::new();
92
93        // All TokenIds where an Occurrence has been emitted that references a symbol.
94        let mut token_ids_referenced: FxHashSet<TokenId> = FxHashSet::default();
95        // All TokenIds where the SymbolInformation has been written to the document.
96        let mut token_ids_emitted: FxHashSet<TokenId> = FxHashSet::default();
97        // All FileIds emitted as documents.
98        let mut file_ids_emitted: FxHashSet<FileId> = FxHashSet::default();
99
100        // All non-local symbols encountered, for detecting duplicate symbol errors.
101        let mut nonlocal_symbols_emitted: FxHashSet<String> = FxHashSet::default();
102        // List of (source_location, symbol) for duplicate symbol errors to report.
103        let mut duplicate_symbol_errors: Vec<(String, String)> = Vec::new();
104        // This is called after definitions have been deduplicated by token_ids_emitted. The purpose
105        // is to detect reuse of symbol names because this causes ambiguity about their meaning.
106        let mut record_error_if_symbol_already_used =
107            |symbol: String,
108             is_inherent_impl: bool,
109             relative_path: &str,
110             line_index: &LineIndex,
111             text_range: TextRange| {
112                let is_local = symbol.starts_with("local ");
113                if !is_local && !nonlocal_symbols_emitted.insert(symbol.clone()) {
114                    if is_inherent_impl {
115                        // FIXME: See #18772. Duplicate SymbolInformation for inherent impls is
116                        // omitted. It would be preferable to emit them with numbers with
117                        // disambiguation, but this is more complex to implement.
118                        false
119                    } else {
120                        let source_location =
121                            text_range_to_string(relative_path, line_index, text_range);
122                        duplicate_symbol_errors.push((source_location, symbol));
123                        // Keep duplicate SymbolInformation. This behavior is preferred over
124                        // omitting so that the issue might be visible within downstream tools.
125                        true
126                    }
127                } else {
128                    true
129                }
130            };
131
132        // Generates symbols from token monikers.
133        let mut symbol_generator = SymbolGenerator::default();
134
135        for StaticIndexedFile { file_id, tokens, .. } in si.files {
136            symbol_generator.clear_document_local_state();
137
138            let Some(relative_path) = get_relative_filepath(&vfs, &root, file_id) else { continue };
139            let line_index = get_line_index(db, file_id);
140
141            let mut occurrences = Vec::new();
142            let mut symbols = Vec::new();
143
144            for (text_range, id) in tokens.into_iter() {
145                let token = si.tokens.get(id).unwrap();
146
147                let Some(TokenSymbols { symbol, enclosing_symbol, is_inherent_impl }) =
148                    symbol_generator.token_symbols(id, token)
149                else {
150                    // token did not have a moniker, so there is no reasonable occurrence to emit
151                    // see ide::moniker::def_to_moniker
152                    continue;
153                };
154
155                let is_defined_in_this_document = match token.definition {
156                    Some(def) => def.file_id == file_id,
157                    _ => false,
158                };
159                if is_defined_in_this_document {
160                    if token_ids_emitted.insert(id) {
161                        // token_ids_emitted does deduplication. This checks that this results
162                        // in unique emitted symbols, as otherwise references are ambiguous.
163                        let should_emit = record_error_if_symbol_already_used(
164                            symbol.clone(),
165                            is_inherent_impl,
166                            relative_path.as_str(),
167                            &line_index,
168                            text_range,
169                        );
170                        if should_emit {
171                            symbols.push(compute_symbol_info(
172                                symbol.clone(),
173                                enclosing_symbol,
174                                token,
175                            ));
176                        }
177                    }
178                } else {
179                    token_ids_referenced.insert(id);
180                }
181
182                // If the range of the def and the range of the token are the same, this must be the definition.
183                // they also must be in the same file. See https://github.com/rust-lang/rust-analyzer/pull/17988
184                let is_definition = match token.definition {
185                    Some(def) => def.file_id == file_id && def.range == text_range,
186                    _ => false,
187                };
188
189                let mut symbol_roles = Default::default();
190                if is_definition {
191                    symbol_roles |= scip_types::SymbolRole::Definition as i32;
192                }
193
194                let enclosing_range = match token.definition_body {
195                    Some(def_body) if def_body.file_id == file_id => {
196                        text_range_to_scip_range(&line_index, def_body.range)
197                    }
198                    _ => Vec::new(),
199                };
200
201                occurrences.push(scip_types::Occurrence {
202                    range: text_range_to_scip_range(&line_index, text_range),
203                    symbol,
204                    symbol_roles,
205                    override_documentation: Vec::new(),
206                    syntax_kind: Default::default(),
207                    diagnostics: Vec::new(),
208                    special_fields: Default::default(),
209                    enclosing_range,
210                });
211            }
212
213            if occurrences.is_empty() {
214                continue;
215            }
216
217            let position_encoding =
218                scip_types::PositionEncoding::UTF8CodeUnitOffsetFromLineStart.into();
219            documents.push(scip_types::Document {
220                relative_path,
221                language: "rust".to_owned(),
222                occurrences,
223                symbols,
224                text: String::new(),
225                position_encoding,
226                special_fields: Default::default(),
227            });
228            if !file_ids_emitted.insert(file_id) {
229                panic!("Invariant violation: file emitted multiple times.");
230            }
231        }
232
233        // Collect all symbols referenced by the files but not defined within them.
234        let mut external_symbols = Vec::new();
235        for id in token_ids_referenced.difference(&token_ids_emitted) {
236            let id = *id;
237            let token = si.tokens.get(id).unwrap();
238
239            let Some(definition) = token.definition else {
240                break;
241            };
242
243            let file_id = definition.file_id;
244            let Some(relative_path) = get_relative_filepath(&vfs, &root, file_id) else { continue };
245            let line_index = get_line_index(db, file_id);
246            let text_range = definition.range;
247            if file_ids_emitted.contains(&file_id) {
248                tracing::error!(
249                    "Bug: definition at {} should have been in an SCIP document but was not.",
250                    text_range_to_string(relative_path.as_str(), &line_index, text_range)
251                );
252                continue;
253            }
254
255            let TokenSymbols { symbol, enclosing_symbol, .. } = symbol_generator
256                .token_symbols(id, token)
257                .expect("To have been referenced, the symbol must be in the cache.");
258
259            record_error_if_symbol_already_used(
260                symbol.clone(),
261                false,
262                relative_path.as_str(),
263                &line_index,
264                text_range,
265            );
266            external_symbols.push(compute_symbol_info(symbol.clone(), enclosing_symbol, token));
267        }
268
269        let index = scip_types::Index {
270            metadata: Some(metadata).into(),
271            documents,
272            external_symbols,
273            special_fields: Default::default(),
274        };
275
276        if !duplicate_symbol_errors.is_empty() {
277            eprintln!("{DUPLICATE_SYMBOLS_MESSAGE}");
278            for (source_location, symbol) in duplicate_symbol_errors {
279                eprintln!("{source_location}");
280                eprintln!("  Duplicate symbol: {symbol}");
281                eprintln!();
282            }
283        }
284
285        let out_path = self.output.unwrap_or_else(|| PathBuf::from(r"index.scip"));
286        scip::write_message_to_file(out_path, index)
287            .map_err(|err| anyhow::format_err!("Failed to write scip to file: {}", err))?;
288
289        eprintln!("Generating SCIP finished {:?}", now.elapsed());
290        Ok(())
291    }
292}
293
294// FIXME: Known buggy cases are described here.
295const DUPLICATE_SYMBOLS_MESSAGE: &str = "
296Encountered duplicate scip symbols, indicating an internal rust-analyzer bug. These duplicates are
297included in the output, but this causes information lookup to be ambiguous and so information about
298these symbols presented by downstream tools may be incorrect.
299
300Known rust-analyzer bugs that can cause this:
301
302  * Definitions in crate example binaries which have the same symbol as definitions in the library
303    or some other example.
304
305  * Struct/enum/const/static/impl definitions nested in a function do not mention the function name.
306    See #18771.
307
308Duplicate symbols encountered:
309";
310
311fn compute_symbol_info(
312    symbol: String,
313    enclosing_symbol: Option<String>,
314    token: &TokenStaticData,
315) -> SymbolInformation {
316    let documentation = match &token.documentation {
317        Some(doc) => vec![doc.as_str().to_owned()],
318        None => vec![],
319    };
320
321    let position_encoding = scip_types::PositionEncoding::UTF8CodeUnitOffsetFromLineStart.into();
322    let signature_documentation = token.signature.clone().map(|text| scip_types::Document {
323        relative_path: "".to_owned(),
324        language: "rust".to_owned(),
325        text,
326        position_encoding,
327        ..Default::default()
328    });
329    scip_types::SymbolInformation {
330        symbol,
331        documentation,
332        relationships: Vec::new(),
333        special_fields: Default::default(),
334        kind: symbol_kind(token.kind).into(),
335        display_name: token.display_name.clone().unwrap_or_default(),
336        signature_documentation: signature_documentation.into(),
337        enclosing_symbol: enclosing_symbol.unwrap_or_default(),
338    }
339}
340
341fn get_relative_filepath(
342    vfs: &vfs::Vfs,
343    rootpath: &vfs::AbsPathBuf,
344    file_id: ide::FileId,
345) -> Option<String> {
346    Some(vfs.file_path(file_id).as_path()?.strip_prefix(rootpath)?.as_str().to_owned())
347}
348
349fn get_line_index(db: &RootDatabase, file_id: FileId) -> LineIndex {
350    LineIndex {
351        index: line_index(db, file_id).clone(),
352        encoding: PositionEncoding::Utf8,
353        endings: LineEndings::Unix,
354    }
355}
356
357// SCIP Ranges have a (very large) optimization that ranges if they are on the same line
358// only encode as a vector of [start_line, start_col, end_col].
359//
360// This transforms a line index into the optimized SCIP Range.
361fn text_range_to_scip_range(line_index: &LineIndex, range: TextRange) -> Vec<i32> {
362    let LineCol { line: start_line, col: start_col } = line_index.index.line_col(range.start());
363    let LineCol { line: end_line, col: end_col } = line_index.index.line_col(range.end());
364
365    if start_line == end_line {
366        vec![start_line as i32, start_col as i32, end_col as i32]
367    } else {
368        vec![start_line as i32, start_col as i32, end_line as i32, end_col as i32]
369    }
370}
371
372fn text_range_to_string(relative_path: &str, line_index: &LineIndex, range: TextRange) -> String {
373    let LineCol { line: start_line, col: start_col } = line_index.index.line_col(range.start());
374    let LineCol { line: end_line, col: end_col } = line_index.index.line_col(range.end());
375
376    format!("{relative_path}:{start_line}:{start_col}-{end_line}:{end_col}")
377}
378
379fn new_descriptor_str(
380    name: &str,
381    suffix: scip_types::descriptor::Suffix,
382) -> scip_types::Descriptor {
383    scip_types::Descriptor {
384        name: name.to_owned(),
385        disambiguator: "".to_owned(),
386        suffix: suffix.into(),
387        special_fields: Default::default(),
388    }
389}
390
391fn symbol_kind(kind: SymbolInformationKind) -> scip_types::symbol_information::Kind {
392    use scip_types::symbol_information::Kind as ScipKind;
393    match kind {
394        SymbolInformationKind::AssociatedType => ScipKind::AssociatedType,
395        SymbolInformationKind::Attribute => ScipKind::Attribute,
396        SymbolInformationKind::Constant => ScipKind::Constant,
397        SymbolInformationKind::Enum => ScipKind::Enum,
398        SymbolInformationKind::EnumMember => ScipKind::EnumMember,
399        SymbolInformationKind::Field => ScipKind::Field,
400        SymbolInformationKind::Function => ScipKind::Function,
401        SymbolInformationKind::Macro => ScipKind::Macro,
402        SymbolInformationKind::Method => ScipKind::Method,
403        SymbolInformationKind::Module => ScipKind::Module,
404        SymbolInformationKind::Parameter => ScipKind::Parameter,
405        SymbolInformationKind::SelfParameter => ScipKind::SelfParameter,
406        SymbolInformationKind::StaticMethod => ScipKind::StaticMethod,
407        SymbolInformationKind::StaticVariable => ScipKind::StaticVariable,
408        SymbolInformationKind::Struct => ScipKind::Struct,
409        SymbolInformationKind::Trait => ScipKind::Trait,
410        SymbolInformationKind::TraitMethod => ScipKind::TraitMethod,
411        SymbolInformationKind::Type => ScipKind::Type,
412        SymbolInformationKind::TypeAlias => ScipKind::TypeAlias,
413        SymbolInformationKind::TypeParameter => ScipKind::TypeParameter,
414        SymbolInformationKind::Union => ScipKind::Union,
415        SymbolInformationKind::Variable => ScipKind::Variable,
416    }
417}
418
419#[derive(Clone)]
420struct TokenSymbols {
421    symbol: String,
422    /// Definition that contains this one. Only set when `symbol` is local.
423    enclosing_symbol: Option<String>,
424    /// True if this symbol is for an inherent impl. This is used to only emit `SymbolInformation`
425    /// for a struct's first inherent impl, since their symbol names are not disambiguated.
426    is_inherent_impl: bool,
427}
428
429#[derive(Default)]
430struct SymbolGenerator {
431    token_to_symbols: FxHashMap<TokenId, Option<TokenSymbols>>,
432    local_count: usize,
433}
434
435impl SymbolGenerator {
436    fn clear_document_local_state(&mut self) {
437        self.local_count = 0;
438    }
439
440    fn token_symbols(&mut self, id: TokenId, token: &TokenStaticData) -> Option<TokenSymbols> {
441        let mut local_count = self.local_count;
442        let token_symbols = self
443            .token_to_symbols
444            .entry(id)
445            .or_insert_with(|| {
446                Some(match token.moniker.as_ref()? {
447                    MonikerResult::Moniker(moniker) => TokenSymbols {
448                        symbol: scip::symbol::format_symbol(moniker_to_symbol(moniker)),
449                        enclosing_symbol: None,
450                        is_inherent_impl: match &moniker.identifier.description[..] {
451                            // inherent impls are represented as impl#[SelfType]
452                            [.., descriptor, _] => {
453                                descriptor.desc == MonikerDescriptorKind::Type
454                                    && descriptor.name == "impl"
455                            }
456                            _ => false,
457                        },
458                    },
459                    MonikerResult::Local { enclosing_moniker } => {
460                        let local_symbol = scip::types::Symbol::new_local(local_count);
461                        local_count += 1;
462                        TokenSymbols {
463                            symbol: scip::symbol::format_symbol(local_symbol),
464                            enclosing_symbol: enclosing_moniker
465                                .as_ref()
466                                .map(moniker_to_symbol)
467                                .map(scip::symbol::format_symbol),
468                            is_inherent_impl: false,
469                        }
470                    }
471                })
472            })
473            .clone();
474        self.local_count = local_count;
475        token_symbols
476    }
477}
478
479fn moniker_to_symbol(moniker: &Moniker) -> scip_types::Symbol {
480    scip_types::Symbol {
481        scheme: "rust-analyzer".into(),
482        package: Some(scip_types::Package {
483            manager: "cargo".to_owned(),
484            name: moniker.package_information.name.clone(),
485            version: moniker.package_information.version.clone().unwrap_or_else(|| ".".to_owned()),
486            special_fields: Default::default(),
487        })
488        .into(),
489        descriptors: moniker_descriptors(&moniker.identifier),
490        special_fields: Default::default(),
491    }
492}
493
494fn moniker_descriptors(identifier: &MonikerIdentifier) -> Vec<scip_types::Descriptor> {
495    use scip_types::descriptor::Suffix::*;
496    identifier
497        .description
498        .iter()
499        .map(|desc| {
500            new_descriptor_str(
501                &desc.name,
502                match desc.desc {
503                    MonikerDescriptorKind::Namespace => Namespace,
504                    MonikerDescriptorKind::Type => Type,
505                    MonikerDescriptorKind::Term => Term,
506                    MonikerDescriptorKind::Method => Method,
507                    MonikerDescriptorKind::TypeParameter => TypeParameter,
508                    MonikerDescriptorKind::Parameter => Parameter,
509                    MonikerDescriptorKind::Macro => Macro,
510                    MonikerDescriptorKind::Meta => Meta,
511                },
512            )
513        })
514        .collect()
515}
516
517#[cfg(test)]
518mod test {
519    use super::*;
520    use hir::FileRangeWrapper;
521    use ide::{FilePosition, TextSize};
522    use test_fixture::ChangeFixture;
523    use vfs::VfsPath;
524
525    fn position(#[rust_analyzer::rust_fixture] ra_fixture: &str) -> (AnalysisHost, FilePosition) {
526        let mut host = AnalysisHost::default();
527        let change_fixture = ChangeFixture::parse(ra_fixture);
528        host.raw_database_mut().apply_change(change_fixture.change);
529        let (file_id, range_or_offset) =
530            change_fixture.file_position.expect("expected a marker ()");
531        let offset = range_or_offset.expect_offset();
532        let position = FilePosition { file_id: file_id.file_id(), offset };
533        (host, position)
534    }
535
536    /// If expected == "", then assert that there are no symbols (this is basically local symbol)
537    #[track_caller]
538    fn check_symbol(#[rust_analyzer::rust_fixture] ra_fixture: &str, expected: &str) {
539        let (host, position) = position(ra_fixture);
540
541        let analysis = host.analysis();
542        let si = StaticIndex::compute(
543            &analysis,
544            VendoredLibrariesConfig::Included {
545                workspace_root: &VfsPath::new_virtual_path("/workspace".to_owned()),
546            },
547        );
548
549        let FilePosition { file_id, offset } = position;
550
551        let mut found_symbol = None;
552        for file in &si.files {
553            if file.file_id != file_id {
554                continue;
555            }
556            for &(range, id) in &file.tokens {
557                // check if cursor is within token, ignoring token for the module defined by the file (whose range is the whole file)
558                if range.start() != TextSize::from(0) && range.contains(offset - TextSize::from(1))
559                {
560                    let token = si.tokens.get(id).unwrap();
561                    found_symbol = match token.moniker.as_ref() {
562                        None => None,
563                        Some(MonikerResult::Moniker(moniker)) => {
564                            Some(scip::symbol::format_symbol(moniker_to_symbol(moniker)))
565                        }
566                        Some(MonikerResult::Local { enclosing_moniker: Some(moniker) }) => {
567                            Some(format!(
568                                "local enclosed by {}",
569                                scip::symbol::format_symbol(moniker_to_symbol(moniker))
570                            ))
571                        }
572                        Some(MonikerResult::Local { enclosing_moniker: None }) => {
573                            Some("unenclosed local".to_owned())
574                        }
575                    };
576                    break;
577                }
578            }
579        }
580
581        if expected.is_empty() {
582            assert!(found_symbol.is_none(), "must have no symbols {found_symbol:?}");
583            return;
584        }
585
586        assert!(found_symbol.is_some(), "must have one symbol {found_symbol:?}");
587        assert_eq!(found_symbol.unwrap(), expected);
588    }
589
590    #[test]
591    fn basic() {
592        check_symbol(
593            r#"
594//- /workspace/lib.rs crate:main deps:foo
595use foo::example_mod::func;
596fn main() {
597    func$0();
598}
599//- /foo/lib.rs crate:foo@0.1.0,https://a.b/foo.git library
600pub mod example_mod {
601    pub fn func() {}
602}
603"#,
604            "rust-analyzer cargo foo 0.1.0 example_mod/func().",
605        );
606    }
607
608    #[test]
609    fn operator_overload() {
610        check_symbol(
611            r#"
612//- minicore: add
613//- /workspace/lib.rs crate:main
614use core::ops::AddAssign;
615
616struct S;
617
618impl AddAssign for S {
619    fn add_assign(&mut self, _rhs: Self) {}
620}
621
622fn main() {
623    let mut s = S;
624    s +=$0 S;
625}
626"#,
627            "rust-analyzer cargo main . impl#[S][`AddAssign<Self>`]add_assign().",
628        );
629    }
630
631    #[test]
632    fn symbol_for_trait() {
633        check_symbol(
634            r#"
635//- /foo/lib.rs crate:foo@0.1.0,https://a.b/foo.git library
636pub mod module {
637    pub trait MyTrait {
638        pub fn func$0() {}
639    }
640}
641"#,
642            "rust-analyzer cargo foo 0.1.0 module/MyTrait#func().",
643        );
644    }
645
646    #[test]
647    fn symbol_for_trait_alias() {
648        check_symbol(
649            r#"
650//- /foo/lib.rs crate:foo@0.1.0,https://a.b/foo.git library
651#![feature(trait_alias)]
652pub mod module {
653    pub trait MyTrait {}
654    pub trait MyTraitAlias$0 = MyTrait;
655}
656"#,
657            "rust-analyzer cargo foo 0.1.0 module/MyTraitAlias#",
658        );
659    }
660
661    #[test]
662    fn symbol_for_trait_constant() {
663        check_symbol(
664            r#"
665    //- /foo/lib.rs crate:foo@0.1.0,https://a.b/foo.git library
666    pub mod module {
667        pub trait MyTrait {
668            const MY_CONST$0: u8;
669        }
670    }
671    "#,
672            "rust-analyzer cargo foo 0.1.0 module/MyTrait#MY_CONST.",
673        );
674    }
675
676    #[test]
677    fn symbol_for_trait_type() {
678        check_symbol(
679            r#"
680    //- /foo/lib.rs crate:foo@0.1.0,https://a.b/foo.git library
681    pub mod module {
682        pub trait MyTrait {
683            type MyType$0;
684        }
685    }
686    "#,
687            "rust-analyzer cargo foo 0.1.0 module/MyTrait#MyType#",
688        );
689    }
690
691    #[test]
692    fn symbol_for_trait_impl_function() {
693        check_symbol(
694            r#"
695    //- /foo/lib.rs crate:foo@0.1.0,https://a.b/foo.git library
696    pub mod module {
697        pub trait MyTrait {
698            pub fn func() {}
699        }
700
701        struct MyStruct {}
702
703        impl MyTrait for MyStruct {
704            pub fn func$0() {}
705        }
706    }
707    "#,
708            "rust-analyzer cargo foo 0.1.0 module/impl#[MyStruct][MyTrait]func().",
709        );
710    }
711
712    #[test]
713    fn symbol_for_field() {
714        check_symbol(
715            r#"
716    //- /workspace/lib.rs crate:main deps:foo
717    use foo::St;
718    fn main() {
719        let x = St { a$0: 2 };
720    }
721    //- /foo/lib.rs crate:foo@0.1.0,https://a.b/foo.git library
722    pub struct St {
723        pub a: i32,
724    }
725    "#,
726            "rust-analyzer cargo foo 0.1.0 St#a.",
727        );
728    }
729
730    #[test]
731    fn symbol_for_param() {
732        check_symbol(
733            r#"
734//- /workspace/lib.rs crate:main deps:foo
735use foo::example_mod::func;
736fn main() {
737    func(42);
738}
739//- /foo/lib.rs crate:foo@0.1.0,https://a.b/foo.git library
740pub mod example_mod {
741    pub fn func(x$0: usize) {}
742}
743"#,
744            "local enclosed by rust-analyzer cargo foo 0.1.0 example_mod/func().",
745        );
746    }
747
748    #[test]
749    fn symbol_for_closure_param() {
750        check_symbol(
751            r#"
752//- /workspace/lib.rs crate:main deps:foo
753use foo::example_mod::func;
754fn main() {
755    func();
756}
757//- /foo/lib.rs crate:foo@0.1.0,https://a.b/foo.git library
758pub mod example_mod {
759    pub fn func() {
760        let f = |x$0: usize| {};
761    }
762}
763"#,
764            "local enclosed by rust-analyzer cargo foo 0.1.0 example_mod/func().",
765        );
766    }
767
768    #[test]
769    fn local_symbol_for_local() {
770        check_symbol(
771            r#"
772    //- /workspace/lib.rs crate:main deps:foo
773    use foo::module::func;
774    fn main() {
775        func();
776    }
777    //- /foo/lib.rs crate:foo@0.1.0,https://a.b/foo.git library
778    pub mod module {
779        pub fn func() {
780            let x$0 = 2;
781        }
782    }
783    "#,
784            "local enclosed by rust-analyzer cargo foo 0.1.0 module/func().",
785        );
786    }
787
788    #[test]
789    fn global_symbol_for_pub_struct() {
790        check_symbol(
791            r#"
792    //- /workspace/lib.rs crate:main
793    mod foo;
794
795    fn main() {
796        let _bar = foo::Bar { i: 0 };
797    }
798    //- /workspace/foo.rs
799    pub struct Bar$0 {
800        pub i: i32,
801    }
802    "#,
803            "rust-analyzer cargo main . foo/Bar#",
804        );
805    }
806
807    #[test]
808    fn global_symbol_for_pub_struct_reference() {
809        check_symbol(
810            r#"
811    //- /workspace/lib.rs crate:main
812    mod foo;
813
814    fn main() {
815        let _bar = foo::Bar$0 { i: 0 };
816    }
817    //- /workspace/foo.rs
818    pub struct Bar {
819        pub i: i32,
820    }
821    "#,
822            "rust-analyzer cargo main . foo/Bar#",
823        );
824    }
825
826    #[test]
827    fn symbol_for_type_alias() {
828        check_symbol(
829            r#"
830    //- /workspace/lib.rs crate:main
831    pub type MyTypeAlias$0 = u8;
832    "#,
833            "rust-analyzer cargo main . MyTypeAlias#",
834        );
835    }
836
837    // FIXME: This test represents current misbehavior.
838    #[test]
839    fn symbol_for_nested_function() {
840        check_symbol(
841            r#"
842    //- /workspace/lib.rs crate:main
843    pub fn func() {
844       pub fn inner_func$0() {}
845    }
846    "#,
847            "rust-analyzer cargo main . inner_func().",
848            // FIXME: This should be a local:
849            // "local enclosed by rust-analyzer cargo main . func().",
850        );
851    }
852
853    // FIXME: This test represents current misbehavior.
854    #[test]
855    fn symbol_for_struct_in_function() {
856        check_symbol(
857            r#"
858    //- /workspace/lib.rs crate:main
859    pub fn func() {
860       struct SomeStruct$0 {}
861    }
862    "#,
863            "rust-analyzer cargo main . SomeStruct#",
864            // FIXME: This should be a local:
865            // "local enclosed by rust-analyzer cargo main . func().",
866        );
867    }
868
869    // FIXME: This test represents current misbehavior.
870    #[test]
871    fn symbol_for_const_in_function() {
872        check_symbol(
873            r#"
874    //- /workspace/lib.rs crate:main
875    pub fn func() {
876       const SOME_CONST$0: u32 = 1;
877    }
878    "#,
879            "rust-analyzer cargo main . SOME_CONST.",
880            // FIXME: This should be a local:
881            // "local enclosed by rust-analyzer cargo main . func().",
882        );
883    }
884
885    // FIXME: This test represents current misbehavior.
886    #[test]
887    fn symbol_for_static_in_function() {
888        check_symbol(
889            r#"
890    //- /workspace/lib.rs crate:main
891    pub fn func() {
892       static SOME_STATIC$0: u32 = 1;
893    }
894    "#,
895            "rust-analyzer cargo main . SOME_STATIC.",
896            // FIXME: This should be a local:
897            // "local enclosed by rust-analyzer cargo main . func().",
898        );
899    }
900
901    #[test]
902    fn documentation_matches_doc_comment() {
903        let s = "/// foo\nfn bar() {}";
904
905        let mut host = AnalysisHost::default();
906        let change_fixture = ChangeFixture::parse(s);
907        host.raw_database_mut().apply_change(change_fixture.change);
908
909        let analysis = host.analysis();
910        let si = StaticIndex::compute(
911            &analysis,
912            VendoredLibrariesConfig::Included {
913                workspace_root: &VfsPath::new_virtual_path("/workspace".to_owned()),
914            },
915        );
916
917        let file = si.files.first().unwrap();
918        let (_, token_id) = file.tokens.get(1).unwrap(); // first token is file module, second is `bar`
919        let token = si.tokens.get(*token_id).unwrap();
920
921        assert_eq!(token.documentation.as_ref().map(|d| d.as_str()), Some("foo"));
922    }
923
924    #[test]
925    fn function_has_enclosing_range() {
926        let s = "fn foo() {}";
927
928        let mut host = AnalysisHost::default();
929        let change_fixture = ChangeFixture::parse(s);
930        host.raw_database_mut().apply_change(change_fixture.change);
931
932        let analysis = host.analysis();
933        let si = StaticIndex::compute(
934            &analysis,
935            VendoredLibrariesConfig::Included {
936                workspace_root: &VfsPath::new_virtual_path("/workspace".to_owned()),
937            },
938        );
939
940        let file = si.files.first().unwrap();
941        let (_, token_id) = file.tokens.get(1).unwrap(); // first token is file module, second is `foo`
942        let token = si.tokens.get(*token_id).unwrap();
943
944        let expected_range = FileRangeWrapper {
945            file_id: FileId::from_raw(0),
946            range: TextRange::new(0.into(), 11.into()),
947        };
948
949        assert_eq!(token.definition_body, Some(expected_range));
950    }
951}