Skip to main content

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