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                occurrences.push(scip_types::Occurrence {
193                    range: text_range_to_scip_range(&line_index, text_range),
194                    symbol,
195                    symbol_roles,
196                    override_documentation: Vec::new(),
197                    syntax_kind: Default::default(),
198                    diagnostics: Vec::new(),
199                    special_fields: Default::default(),
200                    enclosing_range: Vec::new(),
201                });
202            }
203
204            if occurrences.is_empty() {
205                continue;
206            }
207
208            let position_encoding =
209                scip_types::PositionEncoding::UTF8CodeUnitOffsetFromLineStart.into();
210            documents.push(scip_types::Document {
211                relative_path,
212                language: "rust".to_owned(),
213                occurrences,
214                symbols,
215                text: String::new(),
216                position_encoding,
217                special_fields: Default::default(),
218            });
219            if !file_ids_emitted.insert(file_id) {
220                panic!("Invariant violation: file emitted multiple times.");
221            }
222        }
223
224        // Collect all symbols referenced by the files but not defined within them.
225        let mut external_symbols = Vec::new();
226        for id in token_ids_referenced.difference(&token_ids_emitted) {
227            let id = *id;
228            let token = si.tokens.get(id).unwrap();
229
230            let Some(definition) = token.definition else {
231                break;
232            };
233
234            let file_id = definition.file_id;
235            let Some(relative_path) = get_relative_filepath(&vfs, &root, file_id) else { continue };
236            let line_index = get_line_index(db, file_id);
237            let text_range = definition.range;
238            if file_ids_emitted.contains(&file_id) {
239                tracing::error!(
240                    "Bug: definition at {} should have been in an SCIP document but was not.",
241                    text_range_to_string(relative_path.as_str(), &line_index, text_range)
242                );
243                continue;
244            }
245
246            let TokenSymbols { symbol, enclosing_symbol, .. } = symbol_generator
247                .token_symbols(id, token)
248                .expect("To have been referenced, the symbol must be in the cache.");
249
250            record_error_if_symbol_already_used(
251                symbol.clone(),
252                false,
253                relative_path.as_str(),
254                &line_index,
255                text_range,
256            );
257            external_symbols.push(compute_symbol_info(symbol.clone(), enclosing_symbol, token));
258        }
259
260        let index = scip_types::Index {
261            metadata: Some(metadata).into(),
262            documents,
263            external_symbols,
264            special_fields: Default::default(),
265        };
266
267        if !duplicate_symbol_errors.is_empty() {
268            eprintln!("{DUPLICATE_SYMBOLS_MESSAGE}");
269            for (source_location, symbol) in duplicate_symbol_errors {
270                eprintln!("{source_location}");
271                eprintln!("  Duplicate symbol: {symbol}");
272                eprintln!();
273            }
274        }
275
276        let out_path = self.output.unwrap_or_else(|| PathBuf::from(r"index.scip"));
277        scip::write_message_to_file(out_path, index)
278            .map_err(|err| anyhow::format_err!("Failed to write scip to file: {}", err))?;
279
280        eprintln!("Generating SCIP finished {:?}", now.elapsed());
281        Ok(())
282    }
283}
284
285// FIXME: Known buggy cases are described here.
286const DUPLICATE_SYMBOLS_MESSAGE: &str = "
287Encountered duplicate scip symbols, indicating an internal rust-analyzer bug. These duplicates are
288included in the output, but this causes information lookup to be ambiguous and so information about
289these symbols presented by downstream tools may be incorrect.
290
291Known rust-analyzer bugs that can cause this:
292
293  * Definitions in crate example binaries which have the same symbol as definitions in the library
294    or some other example.
295
296  * Struct/enum/const/static/impl definitions nested in a function do not mention the function name.
297    See #18771.
298
299Duplicate symbols encountered:
300";
301
302fn compute_symbol_info(
303    symbol: String,
304    enclosing_symbol: Option<String>,
305    token: &TokenStaticData,
306) -> SymbolInformation {
307    let documentation = match &token.documentation {
308        Some(doc) => vec![doc.as_str().to_owned()],
309        None => vec![],
310    };
311
312    let position_encoding = scip_types::PositionEncoding::UTF8CodeUnitOffsetFromLineStart.into();
313    let signature_documentation = token.signature.clone().map(|text| scip_types::Document {
314        relative_path: "".to_owned(),
315        language: "rust".to_owned(),
316        text,
317        position_encoding,
318        ..Default::default()
319    });
320    scip_types::SymbolInformation {
321        symbol,
322        documentation,
323        relationships: Vec::new(),
324        special_fields: Default::default(),
325        kind: symbol_kind(token.kind).into(),
326        display_name: token.display_name.clone().unwrap_or_default(),
327        signature_documentation: signature_documentation.into(),
328        enclosing_symbol: enclosing_symbol.unwrap_or_default(),
329    }
330}
331
332fn get_relative_filepath(
333    vfs: &vfs::Vfs,
334    rootpath: &vfs::AbsPathBuf,
335    file_id: ide::FileId,
336) -> Option<String> {
337    Some(vfs.file_path(file_id).as_path()?.strip_prefix(rootpath)?.as_str().to_owned())
338}
339
340fn get_line_index(db: &RootDatabase, file_id: FileId) -> LineIndex {
341    LineIndex {
342        index: db.line_index(file_id),
343        encoding: PositionEncoding::Utf8,
344        endings: LineEndings::Unix,
345    }
346}
347
348// SCIP Ranges have a (very large) optimization that ranges if they are on the same line
349// only encode as a vector of [start_line, start_col, end_col].
350//
351// This transforms a line index into the optimized SCIP Range.
352fn text_range_to_scip_range(line_index: &LineIndex, range: TextRange) -> Vec<i32> {
353    let LineCol { line: start_line, col: start_col } = line_index.index.line_col(range.start());
354    let LineCol { line: end_line, col: end_col } = line_index.index.line_col(range.end());
355
356    if start_line == end_line {
357        vec![start_line as i32, start_col as i32, end_col as i32]
358    } else {
359        vec![start_line as i32, start_col as i32, end_line as i32, end_col as i32]
360    }
361}
362
363fn text_range_to_string(relative_path: &str, line_index: &LineIndex, range: TextRange) -> String {
364    let LineCol { line: start_line, col: start_col } = line_index.index.line_col(range.start());
365    let LineCol { line: end_line, col: end_col } = line_index.index.line_col(range.end());
366
367    format!("{relative_path}:{start_line}:{start_col}-{end_line}:{end_col}")
368}
369
370fn new_descriptor_str(
371    name: &str,
372    suffix: scip_types::descriptor::Suffix,
373) -> scip_types::Descriptor {
374    scip_types::Descriptor {
375        name: name.to_owned(),
376        disambiguator: "".to_owned(),
377        suffix: suffix.into(),
378        special_fields: Default::default(),
379    }
380}
381
382fn symbol_kind(kind: SymbolInformationKind) -> scip_types::symbol_information::Kind {
383    use scip_types::symbol_information::Kind as ScipKind;
384    match kind {
385        SymbolInformationKind::AssociatedType => ScipKind::AssociatedType,
386        SymbolInformationKind::Attribute => ScipKind::Attribute,
387        SymbolInformationKind::Constant => ScipKind::Constant,
388        SymbolInformationKind::Enum => ScipKind::Enum,
389        SymbolInformationKind::EnumMember => ScipKind::EnumMember,
390        SymbolInformationKind::Field => ScipKind::Field,
391        SymbolInformationKind::Function => ScipKind::Function,
392        SymbolInformationKind::Macro => ScipKind::Macro,
393        SymbolInformationKind::Method => ScipKind::Method,
394        SymbolInformationKind::Module => ScipKind::Module,
395        SymbolInformationKind::Parameter => ScipKind::Parameter,
396        SymbolInformationKind::SelfParameter => ScipKind::SelfParameter,
397        SymbolInformationKind::StaticMethod => ScipKind::StaticMethod,
398        SymbolInformationKind::StaticVariable => ScipKind::StaticVariable,
399        SymbolInformationKind::Struct => ScipKind::Struct,
400        SymbolInformationKind::Trait => ScipKind::Trait,
401        SymbolInformationKind::TraitMethod => ScipKind::TraitMethod,
402        SymbolInformationKind::Type => ScipKind::Type,
403        SymbolInformationKind::TypeAlias => ScipKind::TypeAlias,
404        SymbolInformationKind::TypeParameter => ScipKind::TypeParameter,
405        SymbolInformationKind::Union => ScipKind::Union,
406        SymbolInformationKind::Variable => ScipKind::Variable,
407    }
408}
409
410#[derive(Clone)]
411struct TokenSymbols {
412    symbol: String,
413    /// Definition that contains this one. Only set when `symbol` is local.
414    enclosing_symbol: Option<String>,
415    /// True if this symbol is for an inherent impl. This is used to only emit `SymbolInformation`
416    /// for a struct's first inherent impl, since their symbol names are not disambiguated.
417    is_inherent_impl: bool,
418}
419
420#[derive(Default)]
421struct SymbolGenerator {
422    token_to_symbols: FxHashMap<TokenId, Option<TokenSymbols>>,
423    local_count: usize,
424}
425
426impl SymbolGenerator {
427    fn clear_document_local_state(&mut self) {
428        self.local_count = 0;
429    }
430
431    fn token_symbols(&mut self, id: TokenId, token: &TokenStaticData) -> Option<TokenSymbols> {
432        let mut local_count = self.local_count;
433        let token_symbols = self
434            .token_to_symbols
435            .entry(id)
436            .or_insert_with(|| {
437                Some(match token.moniker.as_ref()? {
438                    MonikerResult::Moniker(moniker) => TokenSymbols {
439                        symbol: scip::symbol::format_symbol(moniker_to_symbol(moniker)),
440                        enclosing_symbol: None,
441                        is_inherent_impl: match &moniker.identifier.description[..] {
442                            // inherent impls are represented as impl#[SelfType]
443                            [.., descriptor, _] => {
444                                descriptor.desc == MonikerDescriptorKind::Type
445                                    && descriptor.name == "impl"
446                            }
447                            _ => false,
448                        },
449                    },
450                    MonikerResult::Local { enclosing_moniker } => {
451                        let local_symbol = scip::types::Symbol::new_local(local_count);
452                        local_count += 1;
453                        TokenSymbols {
454                            symbol: scip::symbol::format_symbol(local_symbol),
455                            enclosing_symbol: enclosing_moniker
456                                .as_ref()
457                                .map(moniker_to_symbol)
458                                .map(scip::symbol::format_symbol),
459                            is_inherent_impl: false,
460                        }
461                    }
462                })
463            })
464            .clone();
465        self.local_count = local_count;
466        token_symbols
467    }
468}
469
470fn moniker_to_symbol(moniker: &Moniker) -> scip_types::Symbol {
471    scip_types::Symbol {
472        scheme: "rust-analyzer".into(),
473        package: Some(scip_types::Package {
474            manager: "cargo".to_owned(),
475            name: moniker.package_information.name.clone(),
476            version: moniker.package_information.version.clone().unwrap_or_else(|| ".".to_owned()),
477            special_fields: Default::default(),
478        })
479        .into(),
480        descriptors: moniker_descriptors(&moniker.identifier),
481        special_fields: Default::default(),
482    }
483}
484
485fn moniker_descriptors(identifier: &MonikerIdentifier) -> Vec<scip_types::Descriptor> {
486    use scip_types::descriptor::Suffix::*;
487    identifier
488        .description
489        .iter()
490        .map(|desc| {
491            new_descriptor_str(
492                &desc.name,
493                match desc.desc {
494                    MonikerDescriptorKind::Namespace => Namespace,
495                    MonikerDescriptorKind::Type => Type,
496                    MonikerDescriptorKind::Term => Term,
497                    MonikerDescriptorKind::Method => Method,
498                    MonikerDescriptorKind::TypeParameter => TypeParameter,
499                    MonikerDescriptorKind::Parameter => Parameter,
500                    MonikerDescriptorKind::Macro => Macro,
501                    MonikerDescriptorKind::Meta => Meta,
502                },
503            )
504        })
505        .collect()
506}
507
508#[cfg(test)]
509mod test {
510    use super::*;
511    use ide::{FilePosition, TextSize};
512    use test_fixture::ChangeFixture;
513    use vfs::VfsPath;
514
515    fn position(#[rust_analyzer::rust_fixture] ra_fixture: &str) -> (AnalysisHost, FilePosition) {
516        let mut host = AnalysisHost::default();
517        let change_fixture = ChangeFixture::parse(host.raw_database(), ra_fixture);
518        host.raw_database_mut().apply_change(change_fixture.change);
519        let (file_id, range_or_offset) =
520            change_fixture.file_position.expect("expected a marker ()");
521        let offset = range_or_offset.expect_offset();
522        let position = FilePosition { file_id: file_id.file_id(host.raw_database()), offset };
523        (host, position)
524    }
525
526    /// If expected == "", then assert that there are no symbols (this is basically local symbol)
527    #[track_caller]
528    fn check_symbol(#[rust_analyzer::rust_fixture] ra_fixture: &str, expected: &str) {
529        let (host, position) = position(ra_fixture);
530
531        let analysis = host.analysis();
532        let si = StaticIndex::compute(
533            &analysis,
534            VendoredLibrariesConfig::Included {
535                workspace_root: &VfsPath::new_virtual_path("/workspace".to_owned()),
536            },
537        );
538
539        let FilePosition { file_id, offset } = position;
540
541        let mut found_symbol = None;
542        for file in &si.files {
543            if file.file_id != file_id {
544                continue;
545            }
546            for &(range, id) in &file.tokens {
547                // check if cursor is within token, ignoring token for the module defined by the file (whose range is the whole file)
548                if range.start() != TextSize::from(0) && range.contains(offset - TextSize::from(1))
549                {
550                    let token = si.tokens.get(id).unwrap();
551                    found_symbol = match token.moniker.as_ref() {
552                        None => None,
553                        Some(MonikerResult::Moniker(moniker)) => {
554                            Some(scip::symbol::format_symbol(moniker_to_symbol(moniker)))
555                        }
556                        Some(MonikerResult::Local { enclosing_moniker: Some(moniker) }) => {
557                            Some(format!(
558                                "local enclosed by {}",
559                                scip::symbol::format_symbol(moniker_to_symbol(moniker))
560                            ))
561                        }
562                        Some(MonikerResult::Local { enclosing_moniker: None }) => {
563                            Some("unenclosed local".to_owned())
564                        }
565                    };
566                    break;
567                }
568            }
569        }
570
571        if expected.is_empty() {
572            assert!(found_symbol.is_none(), "must have no symbols {found_symbol:?}");
573            return;
574        }
575
576        assert!(found_symbol.is_some(), "must have one symbol {found_symbol:?}");
577        assert_eq!(found_symbol.unwrap(), expected);
578    }
579
580    #[test]
581    fn basic() {
582        check_symbol(
583            r#"
584//- /workspace/lib.rs crate:main deps:foo
585use foo::example_mod::func;
586fn main() {
587    func$0();
588}
589//- /foo/lib.rs crate:foo@0.1.0,https://a.b/foo.git library
590pub mod example_mod {
591    pub fn func() {}
592}
593"#,
594            "rust-analyzer cargo foo 0.1.0 example_mod/func().",
595        );
596    }
597
598    #[test]
599    fn symbol_for_trait() {
600        check_symbol(
601            r#"
602//- /foo/lib.rs crate:foo@0.1.0,https://a.b/foo.git library
603pub mod module {
604    pub trait MyTrait {
605        pub fn func$0() {}
606    }
607}
608"#,
609            "rust-analyzer cargo foo 0.1.0 module/MyTrait#func().",
610        );
611    }
612
613    #[test]
614    fn symbol_for_trait_alias() {
615        check_symbol(
616            r#"
617//- /foo/lib.rs crate:foo@0.1.0,https://a.b/foo.git library
618#![feature(trait_alias)]
619pub mod module {
620    pub trait MyTrait {}
621    pub trait MyTraitAlias$0 = MyTrait;
622}
623"#,
624            "rust-analyzer cargo foo 0.1.0 module/MyTraitAlias#",
625        );
626    }
627
628    #[test]
629    fn symbol_for_trait_constant() {
630        check_symbol(
631            r#"
632    //- /foo/lib.rs crate:foo@0.1.0,https://a.b/foo.git library
633    pub mod module {
634        pub trait MyTrait {
635            const MY_CONST$0: u8;
636        }
637    }
638    "#,
639            "rust-analyzer cargo foo 0.1.0 module/MyTrait#MY_CONST.",
640        );
641    }
642
643    #[test]
644    fn symbol_for_trait_type() {
645        check_symbol(
646            r#"
647    //- /foo/lib.rs crate:foo@0.1.0,https://a.b/foo.git library
648    pub mod module {
649        pub trait MyTrait {
650            type MyType$0;
651        }
652    }
653    "#,
654            "rust-analyzer cargo foo 0.1.0 module/MyTrait#MyType#",
655        );
656    }
657
658    #[test]
659    fn symbol_for_trait_impl_function() {
660        check_symbol(
661            r#"
662    //- /foo/lib.rs crate:foo@0.1.0,https://a.b/foo.git library
663    pub mod module {
664        pub trait MyTrait {
665            pub fn func() {}
666        }
667
668        struct MyStruct {}
669
670        impl MyTrait for MyStruct {
671            pub fn func$0() {}
672        }
673    }
674    "#,
675            "rust-analyzer cargo foo 0.1.0 module/impl#[MyStruct][MyTrait]func().",
676        );
677    }
678
679    #[test]
680    fn symbol_for_field() {
681        check_symbol(
682            r#"
683    //- /workspace/lib.rs crate:main deps:foo
684    use foo::St;
685    fn main() {
686        let x = St { a$0: 2 };
687    }
688    //- /foo/lib.rs crate:foo@0.1.0,https://a.b/foo.git library
689    pub struct St {
690        pub a: i32,
691    }
692    "#,
693            "rust-analyzer cargo foo 0.1.0 St#a.",
694        );
695    }
696
697    #[test]
698    fn symbol_for_param() {
699        check_symbol(
700            r#"
701//- /workspace/lib.rs crate:main deps:foo
702use foo::example_mod::func;
703fn main() {
704    func(42);
705}
706//- /foo/lib.rs crate:foo@0.1.0,https://a.b/foo.git library
707pub mod example_mod {
708    pub fn func(x$0: usize) {}
709}
710"#,
711            "local enclosed by rust-analyzer cargo foo 0.1.0 example_mod/func().",
712        );
713    }
714
715    #[test]
716    fn symbol_for_closure_param() {
717        check_symbol(
718            r#"
719//- /workspace/lib.rs crate:main deps:foo
720use foo::example_mod::func;
721fn main() {
722    func();
723}
724//- /foo/lib.rs crate:foo@0.1.0,https://a.b/foo.git library
725pub mod example_mod {
726    pub fn func() {
727        let f = |x$0: usize| {};
728    }
729}
730"#,
731            "local enclosed by rust-analyzer cargo foo 0.1.0 example_mod/func().",
732        );
733    }
734
735    #[test]
736    fn local_symbol_for_local() {
737        check_symbol(
738            r#"
739    //- /workspace/lib.rs crate:main deps:foo
740    use foo::module::func;
741    fn main() {
742        func();
743    }
744    //- /foo/lib.rs crate:foo@0.1.0,https://a.b/foo.git library
745    pub mod module {
746        pub fn func() {
747            let x$0 = 2;
748        }
749    }
750    "#,
751            "local enclosed by rust-analyzer cargo foo 0.1.0 module/func().",
752        );
753    }
754
755    #[test]
756    fn global_symbol_for_pub_struct() {
757        check_symbol(
758            r#"
759    //- /workspace/lib.rs crate:main
760    mod foo;
761
762    fn main() {
763        let _bar = foo::Bar { i: 0 };
764    }
765    //- /workspace/foo.rs
766    pub struct Bar$0 {
767        pub i: i32,
768    }
769    "#,
770            "rust-analyzer cargo main . foo/Bar#",
771        );
772    }
773
774    #[test]
775    fn global_symbol_for_pub_struct_reference() {
776        check_symbol(
777            r#"
778    //- /workspace/lib.rs crate:main
779    mod foo;
780
781    fn main() {
782        let _bar = foo::Bar$0 { i: 0 };
783    }
784    //- /workspace/foo.rs
785    pub struct Bar {
786        pub i: i32,
787    }
788    "#,
789            "rust-analyzer cargo main . foo/Bar#",
790        );
791    }
792
793    #[test]
794    fn symbol_for_type_alias() {
795        check_symbol(
796            r#"
797    //- /workspace/lib.rs crate:main
798    pub type MyTypeAlias$0 = u8;
799    "#,
800            "rust-analyzer cargo main . MyTypeAlias#",
801        );
802    }
803
804    // FIXME: This test represents current misbehavior.
805    #[test]
806    fn symbol_for_nested_function() {
807        check_symbol(
808            r#"
809    //- /workspace/lib.rs crate:main
810    pub fn func() {
811       pub fn inner_func$0() {}
812    }
813    "#,
814            "rust-analyzer cargo main . inner_func().",
815            // FIXME: This should be a local:
816            // "local enclosed by rust-analyzer cargo main . func().",
817        );
818    }
819
820    // FIXME: This test represents current misbehavior.
821    #[test]
822    fn symbol_for_struct_in_function() {
823        check_symbol(
824            r#"
825    //- /workspace/lib.rs crate:main
826    pub fn func() {
827       struct SomeStruct$0 {}
828    }
829    "#,
830            "rust-analyzer cargo main . SomeStruct#",
831            // FIXME: This should be a local:
832            // "local enclosed by rust-analyzer cargo main . func().",
833        );
834    }
835
836    // FIXME: This test represents current misbehavior.
837    #[test]
838    fn symbol_for_const_in_function() {
839        check_symbol(
840            r#"
841    //- /workspace/lib.rs crate:main
842    pub fn func() {
843       const SOME_CONST$0: u32 = 1;
844    }
845    "#,
846            "rust-analyzer cargo main . SOME_CONST.",
847            // FIXME: This should be a local:
848            // "local enclosed by rust-analyzer cargo main . func().",
849        );
850    }
851
852    // FIXME: This test represents current misbehavior.
853    #[test]
854    fn symbol_for_static_in_function() {
855        check_symbol(
856            r#"
857    //- /workspace/lib.rs crate:main
858    pub fn func() {
859       static SOME_STATIC$0: u32 = 1;
860    }
861    "#,
862            "rust-analyzer cargo main . SOME_STATIC.",
863            // FIXME: This should be a local:
864            // "local enclosed by rust-analyzer cargo main . func().",
865        );
866    }
867
868    #[test]
869    fn documentation_matches_doc_comment() {
870        let s = "/// foo\nfn bar() {}";
871
872        let mut host = AnalysisHost::default();
873        let change_fixture = ChangeFixture::parse(host.raw_database(), s);
874        host.raw_database_mut().apply_change(change_fixture.change);
875
876        let analysis = host.analysis();
877        let si = StaticIndex::compute(
878            &analysis,
879            VendoredLibrariesConfig::Included {
880                workspace_root: &VfsPath::new_virtual_path("/workspace".to_owned()),
881            },
882        );
883
884        let file = si.files.first().unwrap();
885        let (_, token_id) = file.tokens.get(1).unwrap(); // first token is file module, second is `bar`
886        let token = si.tokens.get(*token_id).unwrap();
887
888        assert_eq!(token.documentation.as_ref().map(|d| d.as_str()), Some("foo"));
889    }
890}