ra_ap_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::{load_workspace_at, LoadCargoConfig, ProcMacroServerChoice};
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::new();
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
420struct SymbolGenerator {
421    token_to_symbols: FxHashMap<TokenId, Option<TokenSymbols>>,
422    local_count: usize,
423}
424
425impl SymbolGenerator {
426    fn new() -> Self {
427        SymbolGenerator { token_to_symbols: FxHashMap::default(), local_count: 0 }
428    }
429
430    fn clear_document_local_state(&mut self) {
431        self.local_count = 0;
432    }
433
434    fn token_symbols(&mut self, id: TokenId, token: &TokenStaticData) -> Option<TokenSymbols> {
435        let mut local_count = self.local_count;
436        let token_symbols = self
437            .token_to_symbols
438            .entry(id)
439            .or_insert_with(|| {
440                Some(match token.moniker.as_ref()? {
441                    MonikerResult::Moniker(moniker) => TokenSymbols {
442                        symbol: scip::symbol::format_symbol(moniker_to_symbol(moniker)),
443                        enclosing_symbol: None,
444                        is_inherent_impl: match &moniker.identifier.description[..] {
445                            // inherent impls are represented as impl#[SelfType]
446                            [.., descriptor, _] => {
447                                descriptor.desc == MonikerDescriptorKind::Type
448                                    && descriptor.name == "impl"
449                            }
450                            _ => false,
451                        },
452                    },
453                    MonikerResult::Local { enclosing_moniker } => {
454                        let local_symbol = scip::types::Symbol::new_local(local_count);
455                        local_count += 1;
456                        TokenSymbols {
457                            symbol: scip::symbol::format_symbol(local_symbol),
458                            enclosing_symbol: enclosing_moniker
459                                .as_ref()
460                                .map(moniker_to_symbol)
461                                .map(scip::symbol::format_symbol),
462                            is_inherent_impl: false,
463                        }
464                    }
465                })
466            })
467            .clone();
468        self.local_count = local_count;
469        token_symbols
470    }
471}
472
473fn moniker_to_symbol(moniker: &Moniker) -> scip_types::Symbol {
474    scip_types::Symbol {
475        scheme: "rust-analyzer".into(),
476        package: Some(scip_types::Package {
477            manager: "cargo".to_owned(),
478            name: moniker.package_information.name.clone(),
479            version: moniker.package_information.version.clone().unwrap_or_else(|| ".".to_owned()),
480            special_fields: Default::default(),
481        })
482        .into(),
483        descriptors: moniker_descriptors(&moniker.identifier),
484        special_fields: Default::default(),
485    }
486}
487
488fn moniker_descriptors(identifier: &MonikerIdentifier) -> Vec<scip_types::Descriptor> {
489    use scip_types::descriptor::Suffix::*;
490    identifier
491        .description
492        .iter()
493        .map(|desc| {
494            new_descriptor_str(
495                &desc.name,
496                match desc.desc {
497                    MonikerDescriptorKind::Namespace => Namespace,
498                    MonikerDescriptorKind::Type => Type,
499                    MonikerDescriptorKind::Term => Term,
500                    MonikerDescriptorKind::Method => Method,
501                    MonikerDescriptorKind::TypeParameter => TypeParameter,
502                    MonikerDescriptorKind::Parameter => Parameter,
503                    MonikerDescriptorKind::Macro => Macro,
504                    MonikerDescriptorKind::Meta => Meta,
505                },
506            )
507        })
508        .collect()
509}
510
511#[cfg(test)]
512mod test {
513    use super::*;
514    use ide::{FilePosition, TextSize};
515    use test_fixture::ChangeFixture;
516    use vfs::VfsPath;
517
518    fn position(#[ra_ap_rust_analyzer::rust_fixture] ra_fixture: &str) -> (AnalysisHost, FilePosition) {
519        let mut host = AnalysisHost::default();
520        let change_fixture = ChangeFixture::parse(ra_fixture);
521        host.raw_database_mut().apply_change(change_fixture.change);
522        let (file_id, range_or_offset) =
523            change_fixture.file_position.expect("expected a marker ()");
524        let offset = range_or_offset.expect_offset();
525        (host, FilePosition { file_id: file_id.into(), offset })
526    }
527
528    /// If expected == "", then assert that there are no symbols (this is basically local symbol)
529    #[track_caller]
530    fn check_symbol(#[ra_ap_rust_analyzer::rust_fixture] ra_fixture: &str, expected: &str) {
531        let (host, position) = position(ra_fixture);
532
533        let analysis = host.analysis();
534        let si = StaticIndex::compute(
535            &analysis,
536            VendoredLibrariesConfig::Included {
537                workspace_root: &VfsPath::new_virtual_path("/workspace".to_owned()),
538            },
539        );
540
541        let FilePosition { file_id, offset } = position;
542
543        let mut found_symbol = None;
544        for file in &si.files {
545            if file.file_id != file_id {
546                continue;
547            }
548            for &(range, id) in &file.tokens {
549                // check if cursor is within token, ignoring token for the module defined by the file (whose range is the whole file)
550                if range.start() != TextSize::from(0) && range.contains(offset - TextSize::from(1))
551                {
552                    let token = si.tokens.get(id).unwrap();
553                    found_symbol = match token.moniker.as_ref() {
554                        None => None,
555                        Some(MonikerResult::Moniker(moniker)) => {
556                            Some(scip::symbol::format_symbol(moniker_to_symbol(moniker)))
557                        }
558                        Some(MonikerResult::Local { enclosing_moniker: Some(moniker) }) => {
559                            Some(format!(
560                                "local enclosed by {}",
561                                scip::symbol::format_symbol(moniker_to_symbol(moniker))
562                            ))
563                        }
564                        Some(MonikerResult::Local { enclosing_moniker: None }) => {
565                            Some("unenclosed local".to_owned())
566                        }
567                    };
568                    break;
569                }
570            }
571        }
572
573        if expected.is_empty() {
574            assert!(found_symbol.is_none(), "must have no symbols {found_symbol:?}");
575            return;
576        }
577
578        assert!(found_symbol.is_some(), "must have one symbol {found_symbol:?}");
579        assert_eq!(found_symbol.unwrap(), expected);
580    }
581
582    #[test]
583    fn basic() {
584        check_symbol(
585            r#"
586//- /workspace/lib.rs crate:main deps:foo
587use foo::example_mod::func;
588fn main() {
589    func$0();
590}
591//- /foo/lib.rs crate:foo@0.1.0,https://a.b/foo.git library
592pub mod example_mod {
593    pub fn func() {}
594}
595"#,
596            "rust-analyzer cargo foo 0.1.0 example_mod/func().",
597        );
598    }
599
600    #[test]
601    fn symbol_for_trait() {
602        check_symbol(
603            r#"
604//- /foo/lib.rs crate:foo@0.1.0,https://a.b/foo.git library
605pub mod module {
606    pub trait MyTrait {
607        pub fn func$0() {}
608    }
609}
610"#,
611            "rust-analyzer cargo foo 0.1.0 module/MyTrait#func().",
612        );
613    }
614
615    #[test]
616    fn symbol_for_trait_alias() {
617        check_symbol(
618            r#"
619//- /foo/lib.rs crate:foo@0.1.0,https://a.b/foo.git library
620#![feature(trait_alias)]
621pub mod module {
622    pub trait MyTrait {}
623    pub trait MyTraitAlias$0 = MyTrait;
624}
625"#,
626            "rust-analyzer cargo foo 0.1.0 module/MyTraitAlias#",
627        );
628    }
629
630    #[test]
631    fn symbol_for_trait_constant() {
632        check_symbol(
633            r#"
634    //- /foo/lib.rs crate:foo@0.1.0,https://a.b/foo.git library
635    pub mod module {
636        pub trait MyTrait {
637            const MY_CONST$0: u8;
638        }
639    }
640    "#,
641            "rust-analyzer cargo foo 0.1.0 module/MyTrait#MY_CONST.",
642        );
643    }
644
645    #[test]
646    fn symbol_for_trait_type() {
647        check_symbol(
648            r#"
649    //- /foo/lib.rs crate:foo@0.1.0,https://a.b/foo.git library
650    pub mod module {
651        pub trait MyTrait {
652            type MyType$0;
653        }
654    }
655    "#,
656            "rust-analyzer cargo foo 0.1.0 module/MyTrait#MyType#",
657        );
658    }
659
660    #[test]
661    fn symbol_for_trait_impl_function() {
662        check_symbol(
663            r#"
664    //- /foo/lib.rs crate:foo@0.1.0,https://a.b/foo.git library
665    pub mod module {
666        pub trait MyTrait {
667            pub fn func() {}
668        }
669
670        struct MyStruct {}
671
672        impl MyTrait for MyStruct {
673            pub fn func$0() {}
674        }
675    }
676    "#,
677            "rust-analyzer cargo foo 0.1.0 module/impl#[MyStruct][MyTrait]func().",
678        );
679    }
680
681    #[test]
682    fn symbol_for_field() {
683        check_symbol(
684            r#"
685    //- /workspace/lib.rs crate:main deps:foo
686    use foo::St;
687    fn main() {
688        let x = St { a$0: 2 };
689    }
690    //- /foo/lib.rs crate:foo@0.1.0,https://a.b/foo.git library
691    pub struct St {
692        pub a: i32,
693    }
694    "#,
695            "rust-analyzer cargo foo 0.1.0 St#a.",
696        );
697    }
698
699    #[test]
700    fn symbol_for_param() {
701        check_symbol(
702            r#"
703//- /workspace/lib.rs crate:main deps:foo
704use foo::example_mod::func;
705fn main() {
706    func(42);
707}
708//- /foo/lib.rs crate:foo@0.1.0,https://a.b/foo.git library
709pub mod example_mod {
710    pub fn func(x$0: usize) {}
711}
712"#,
713            "local enclosed by rust-analyzer cargo foo 0.1.0 example_mod/func().",
714        );
715    }
716
717    #[test]
718    fn symbol_for_closure_param() {
719        check_symbol(
720            r#"
721//- /workspace/lib.rs crate:main deps:foo
722use foo::example_mod::func;
723fn main() {
724    func();
725}
726//- /foo/lib.rs crate:foo@0.1.0,https://a.b/foo.git library
727pub mod example_mod {
728    pub fn func() {
729        let f = |x$0: usize| {};
730    }
731}
732"#,
733            "local enclosed by rust-analyzer cargo foo 0.1.0 example_mod/func().",
734        );
735    }
736
737    #[test]
738    fn local_symbol_for_local() {
739        check_symbol(
740            r#"
741    //- /workspace/lib.rs crate:main deps:foo
742    use foo::module::func;
743    fn main() {
744        func();
745    }
746    //- /foo/lib.rs crate:foo@0.1.0,https://a.b/foo.git library
747    pub mod module {
748        pub fn func() {
749            let x$0 = 2;
750        }
751    }
752    "#,
753            "local enclosed by rust-analyzer cargo foo 0.1.0 module/func().",
754        );
755    }
756
757    #[test]
758    fn global_symbol_for_pub_struct() {
759        check_symbol(
760            r#"
761    //- /workspace/lib.rs crate:main
762    mod foo;
763
764    fn main() {
765        let _bar = foo::Bar { i: 0 };
766    }
767    //- /workspace/foo.rs
768    pub struct Bar$0 {
769        pub i: i32,
770    }
771    "#,
772            "rust-analyzer cargo main . foo/Bar#",
773        );
774    }
775
776    #[test]
777    fn global_symbol_for_pub_struct_reference() {
778        check_symbol(
779            r#"
780    //- /workspace/lib.rs crate:main
781    mod foo;
782
783    fn main() {
784        let _bar = foo::Bar$0 { i: 0 };
785    }
786    //- /workspace/foo.rs
787    pub struct Bar {
788        pub i: i32,
789    }
790    "#,
791            "rust-analyzer cargo main . foo/Bar#",
792        );
793    }
794
795    #[test]
796    fn symbol_for_type_alias() {
797        check_symbol(
798            r#"
799    //- /workspace/lib.rs crate:main
800    pub type MyTypeAlias$0 = u8;
801    "#,
802            "rust-analyzer cargo main . MyTypeAlias#",
803        );
804    }
805
806    // FIXME: This test represents current misbehavior.
807    #[test]
808    fn symbol_for_nested_function() {
809        check_symbol(
810            r#"
811    //- /workspace/lib.rs crate:main
812    pub fn func() {
813       pub fn inner_func$0() {}
814    }
815    "#,
816            "rust-analyzer cargo main . inner_func().",
817            // FIXME: This should be a local:
818            // "local enclosed by rust-analyzer cargo main . func().",
819        );
820    }
821
822    // FIXME: This test represents current misbehavior.
823    #[test]
824    fn symbol_for_struct_in_function() {
825        check_symbol(
826            r#"
827    //- /workspace/lib.rs crate:main
828    pub fn func() {
829       struct SomeStruct$0 {}
830    }
831    "#,
832            "rust-analyzer cargo main . SomeStruct#",
833            // FIXME: This should be a local:
834            // "local enclosed by rust-analyzer cargo main . func().",
835        );
836    }
837
838    // FIXME: This test represents current misbehavior.
839    #[test]
840    fn symbol_for_const_in_function() {
841        check_symbol(
842            r#"
843    //- /workspace/lib.rs crate:main
844    pub fn func() {
845       const SOME_CONST$0: u32 = 1;
846    }
847    "#,
848            "rust-analyzer cargo main . SOME_CONST.",
849            // FIXME: This should be a local:
850            // "local enclosed by rust-analyzer cargo main . func().",
851        );
852    }
853
854    // FIXME: This test represents current misbehavior.
855    #[test]
856    fn symbol_for_static_in_function() {
857        check_symbol(
858            r#"
859    //- /workspace/lib.rs crate:main
860    pub fn func() {
861       static SOME_STATIC$0: u32 = 1;
862    }
863    "#,
864            "rust-analyzer cargo main . SOME_STATIC.",
865            // FIXME: This should be a local:
866            // "local enclosed by rust-analyzer cargo main . func().",
867        );
868    }
869
870    #[test]
871    fn documentation_matches_doc_comment() {
872        let s = "/// foo\nfn bar() {}";
873
874        let mut host = AnalysisHost::default();
875        let change_fixture = ChangeFixture::parse(s);
876        host.raw_database_mut().apply_change(change_fixture.change);
877
878        let analysis = host.analysis();
879        let si = StaticIndex::compute(
880            &analysis,
881            VendoredLibrariesConfig::Included {
882                workspace_root: &VfsPath::new_virtual_path("/workspace".to_owned()),
883            },
884        );
885
886        let file = si.files.first().unwrap();
887        let (_, token_id) = file.tokens.get(1).unwrap(); // first token is file module, second is `bar`
888        let token = si.tokens.get(*token_id).unwrap();
889
890        assert_eq!(token.documentation.as_ref().map(|d| d.as_str()), Some("foo"));
891    }
892}