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