Skip to content

Commit

Permalink
Scaffold search scopes mechanism
Browse files Browse the repository at this point in the history
commit-id:69f01fb6
  • Loading branch information
mkaput committed Jan 16, 2025
1 parent 68c7789 commit e9b7845
Show file tree
Hide file tree
Showing 3 changed files with 72 additions and 21 deletions.
7 changes: 7 additions & 0 deletions src/lang/inspect/defs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ use tracing::error;

use crate::lang::db::{AnalysisDatabase, LsSemanticGroup, LsSyntaxGroup};
use crate::lang::inspect::usages::FindUsages;
use crate::lang::inspect::usages::search_scope::SearchScope;

/// Keeps information about the symbol that is being searched for/inspected.
///
Expand Down Expand Up @@ -144,6 +145,12 @@ impl SymbolDef {
}
}

/// Builds a search scope for finding usages of this symbol.
pub fn search_scope(&self, db: &AnalysisDatabase) -> SearchScope {
// TODO(mkaput): Narrow down the scope as much as possible for particular symbol kinds.
SearchScope::everything(db)
}

/// Starts a find-usages search for this symbol.
pub fn usages<'a>(&'a self, db: &'a AnalysisDatabase) -> FindUsages<'a> {
FindUsages::new(self, db)
Expand Down
33 changes: 12 additions & 21 deletions src/lang/inspect/usages.rs → src/lang/inspect/usages/mod.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
use std::collections::HashMap;
use std::ops::ControlFlow;
use std::sync::Arc;

use cairo_lang_defs::db::DefsGroup;
use cairo_lang_filesystem::db::FilesGroup;
use cairo_lang_filesystem::ids::FileId;
use cairo_lang_filesystem::span::{TextOffset, TextSpan, TextWidth};
use cairo_lang_syntax::node::ast::TerminalIdentifier;
Expand All @@ -15,6 +11,8 @@ use smol_str::format_smolstr;
use crate::lang::db::{AnalysisDatabase, LsSyntaxGroup};
use crate::lang::inspect::defs::SymbolDef;

pub mod search_scope;

macro_rules! flow {
($expr:expr) => {
let ControlFlow::Continue(()) = $expr else {
Expand All @@ -23,7 +21,6 @@ macro_rules! flow {
};
}

// TODO(mkaput): Implement search scopes: for example, variables will never be used in other files.
// TODO(mkaput): Deal with `crate` keyword.
/// An implementation of the find-usages functionality.
///
Expand Down Expand Up @@ -61,6 +58,10 @@ impl<'a> FindUsages<'a> {
pub fn search(self, sink: &mut dyn FnMut(FoundUsage) -> ControlFlow<(), ()>) {
let db = self.db;

// TODO(mkaput): When needed, allow setting search scope externally, via a field in
// FindUsages and set_scope/in_scope methods like RA does.
let search_scope = self.symbol.search_scope(db);

let needle = match self.symbol {
// Small optimisation for inline macros: we can be sure that any usages will have a `!`
// at the end, so we do not need to search for occurrences without it.
Expand All @@ -70,9 +71,9 @@ impl<'a> FindUsages<'a> {

let finder = Finder::new(needle.as_bytes());

for (file, text) in Self::scope_files(db) {
for (file, text, search_span) in search_scope.file_contents(db) {
// Search occurrences of the symbol's name.
for offset in Self::match_offsets(&finder, &text) {
for offset in Self::match_offsets(&finder, &text, search_span) {
if let Some(node) = db.find_syntax_node_at_offset(file, offset) {
if let Some(identifier) = TerminalIdentifier::cast_token(db.upcast(), node) {
flow!(self.found_identifier(identifier, sink));
Expand All @@ -82,27 +83,17 @@ impl<'a> FindUsages<'a> {
}
}

fn scope_files(db: &AnalysisDatabase) -> impl Iterator<Item = (FileId, Arc<str>)> + '_ {
let mut files = HashMap::new();
for crate_id in db.crates() {
for &module_id in db.crate_modules(crate_id).iter() {
if let Ok(file_id) = db.module_main_file(module_id) {
if let Some(text) = db.file_content(file_id) {
files.insert(file_id, text);
}
}
}
}
files.into_iter()
}

fn match_offsets<'b>(
finder: &'b Finder<'b>,
text: &'b str,
search_span: Option<TextSpan>,
) -> impl Iterator<Item = TextOffset> + use<'b> {
finder
.find_iter(text.as_bytes())
.map(|offset| TextWidth::at(text, offset).as_offset())
.filter(move |&offset| {
search_span.is_none_or(|span| span.start <= offset && offset <= span.end)
})
.filter(|offset| {
// Reject matches that are not at word boundaries - for example, an identifier
// `core` will never be a direct usage of a needle `or`.
Expand Down
53 changes: 53 additions & 0 deletions src/lang/inspect/usages/search_scope.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
use std::collections::HashMap;
use std::sync::Arc;

use cairo_lang_defs::db::DefsGroup;
use cairo_lang_filesystem::db::FilesGroup;
use cairo_lang_filesystem::ids::FileId;
use cairo_lang_filesystem::span::TextSpan;

use crate::lang::db::AnalysisDatabase;

#[derive(Clone, Default)]
pub struct SearchScope {
/// A collection of all files constituting this search scope, with optional text spans to
/// narrow down searching ranges.
entries: HashMap<FileId, Option<TextSpan>>,
}

impl SearchScope {
/// Builds a new empty search scope.
pub fn empty() -> Self {
Self::default()
}

/// Builds a search scope spanning an entire set of analysed crates.
#[tracing::instrument(skip_all)]
pub fn everything(db: &AnalysisDatabase) -> Self {
let mut this = Self::empty();
for crate_id in db.crates() {
for &module_id in db.crate_modules(crate_id).iter() {
if let Ok(file_id) = db.module_main_file(module_id) {
this.entries.insert(file_id, None);
}
}
}
this
}

/// Creates an iterator over all files and the optional search scope text spans.
pub fn files_and_spans(&self) -> impl Iterator<Item = (FileId, Option<TextSpan>)> + use<'_> {
self.entries.iter().map(|(&file, &span)| (file, span))
}

/// Creates an iterator over all files, their contents and the optional search scope text spans.
pub fn file_contents<'a, 'b>(
&'a self,
db: &'b AnalysisDatabase,
) -> impl Iterator<Item = (FileId, Arc<str>, Option<TextSpan>)> + use<'a, 'b> {
self.files_and_spans().filter_map(move |(file, span)| {
let text = db.file_content(file)?;
Some((file, text, span))
})
}
}

0 comments on commit e9b7845

Please sign in to comment.