#[cfg(feature = "semantic")]
use std::cell::Cell;
#[cfg(feature = "semantic")]
use std::collections::BTreeMap;
#[cfg(feature = "semantic")]
use std::path::Path;
#[cfg(feature = "semantic")]
use std::sync::{Arc, RwLock};
#[cfg(feature = "semantic")]
use ra_ap_ide::AnalysisHost;
#[cfg(feature = "semantic")]
use ra_ap_load_cargo::{LoadCargoConfig, ProcMacroServerChoice, load_workspace_at};
#[cfg(feature = "semantic")]
use ra_ap_project_model::{CargoConfig, RustLibSource};
#[cfg(feature = "semantic")]
use ra_ap_vfs::{AbsPathBuf, Vfs, VfsPath};
#[cfg(feature = "semantic")]
use crate::observe::{self, Observation};
#[cfg(feature = "semantic")]
use super::common::with_parsed_file;
#[cfg(feature = "semantic")]
use super::edges::{self, SemanticDefinitionTargets};
#[cfg(feature = "semantic")]
use super::file_analysis::SemanticFileAnalysis;
#[cfg(feature = "semantic")]
use super::snapshot::{self, SemanticSnapshotClaim, SemanticSnapshotMismatch, VerifiedSnapshot};
#[cfg(feature = "semantic")]
pub struct SemanticContext {
pub(super) host: AnalysisHost,
pub(super) vfs: Vfs,
pub(super) root: Box<Path>,
file_cache: RwLock<BTreeMap<Box<str>, Arc<SemanticFileAnalysis>>>,
pub(super) file_setup_count: Cell<usize>,
}
#[cfg(not(feature = "semantic"))]
pub struct SemanticContext(());
#[cfg(not(feature = "semantic"))]
pub struct SemanticFileAnalysis(());
#[cfg(feature = "semantic")]
const COPY_PRIMITIVES: &[&str] = &[
"bool", "char", "f32", "f64", "i8", "i16", "i32", "i64", "i128", "isize", "u8", "u16", "u32",
"u64", "u128", "usize",
];
#[cfg(feature = "semantic")]
impl SemanticContext {
pub fn load(workspace_root: &Path) -> Option<Self> {
let root = workspace_root.canonicalize().ok()?;
let cargo_config = cargo_config_minimal(&root)?;
let load_config = load_config_minimal();
let (db, vfs, _proc_macro) =
load_workspace_at(&root, &cargo_config, &load_config, &|_| {}).ok()?;
observe::record(Observation::SemanticWorkspaceLoad);
let host = AnalysisHost::with_database(db);
Some(Self {
host,
vfs,
root: root.into_boxed_path(),
file_cache: RwLock::new(BTreeMap::new()),
file_setup_count: Cell::new(0),
})
}
pub(super) fn absolute(&self, relative: &str) -> Box<str> {
Box::from(self.root.join(relative).to_string_lossy().as_ref())
}
pub(super) fn relative_path(&self, file: ra_ap_ide::FileId) -> Option<Box<str>> {
let path = self.vfs.file_path(file).as_path()?;
crate::resolution::path_normalization::relative_text(
self.root.as_ref(),
Path::new(path.as_str()),
)
.ok()
}
pub(super) fn file_id(&self, absolute: &str) -> Option<ra_ap_ide::FileId> {
let path = VfsPath::from(AbsPathBuf::try_from(absolute).ok()?);
self.vfs.file_id(&path).map(|(file, _)| file)
}
pub(crate) fn verify_snapshot(
&self,
claim: &SemanticSnapshotClaim,
) -> Result<VerifiedSnapshot, SemanticSnapshotMismatch> {
snapshot::verify(self, claim)
}
pub(crate) fn definition_targets(
&self,
verified: &VerifiedSnapshot,
) -> Result<SemanticDefinitionTargets, SemanticSnapshotMismatch> {
edges::collect(self, verified)
}
pub(super) fn analyze_snapshot_file(
&self,
relative: &str,
absolute: &str,
) -> Option<Arc<SemanticFileAnalysis>> {
observe::record(Observation::SemanticQuery(relative));
match self.cached(absolute) {
Some(cached) => Some(cached),
None => self.set_up_snapshot_file(relative, absolute),
}
}
fn set_up_snapshot_file(
&self,
relative: &str,
absolute: &str,
) -> Option<Arc<SemanticFileAnalysis>> {
let analysis = self.analyze_file(absolute)?;
observe::record(Observation::SemanticFileSetup(relative));
Some(analysis)
}
fn cached(&self, file: &str) -> Option<Arc<SemanticFileAnalysis>> {
let cache = match self.file_cache.read() {
Ok(cache) => cache,
Err(poisoned) => poisoned.into_inner(),
};
cache.get(file).map(Arc::clone)
}
pub fn file_setup_count(&self) -> usize {
self.file_setup_count.get()
}
pub fn analyze_file(&self, file: &str) -> Option<Arc<SemanticFileAnalysis>> {
let canonical = canonical_file_path(file)?;
match self.cached(&canonical) {
Some(cached) => Some(cached),
None => with_parsed_file(self, &canonical, |pf| self.cache_analysis(&canonical, pf)),
}
}
fn cache_analysis(
&self,
file: &str,
pf: &super::common::ParsedFile<'_>,
) -> Arc<SemanticFileAnalysis> {
if let Some(cached) = self.cached(file) {
return cached;
}
let arc = Arc::new(SemanticFileAnalysis::build(pf));
let mut cache = match self.file_cache.write() {
Ok(cache) => cache,
Err(poisoned) => poisoned.into_inner(),
};
cache.insert(Box::from(file), Arc::clone(&arc));
arc
}
pub fn resolve_type(&self, file: &str, line: usize, col: usize) -> Option<Box<str>> {
let analysis = self.analyze_file(file)?;
analysis.resolve_type(line, col).map(Box::from)
}
pub fn is_copy(type_name: &str) -> bool {
COPY_PRIMITIVES.contains(&type_name)
}
}
#[cfg(feature = "semantic")]
fn cargo_config_minimal(workspace_root: &Path) -> Option<CargoConfig> {
let target_path = workspace_root.join("target").join("pedant-semantic");
let workspace_target = target_path.to_str()?.to_owned();
Some(CargoConfig {
all_targets: false,
features: Default::default(),
target: None,
sysroot: Some(RustLibSource::Discover),
sysroot_src: None,
rustc_source: None,
extra_includes: Vec::new(),
cfg_overrides: Default::default(),
wrap_rustc_in_build_scripts: false,
run_build_script_command: None,
extra_args: Vec::new(),
extra_env: [(String::from("CARGO_TARGET_DIR"), Some(workspace_target))]
.into_iter()
.collect(),
invocation_strategy: Default::default(),
target_dir_config: Default::default(),
set_test: false,
no_deps: false,
metadata_extra_args: Vec::new(),
config_path: None,
})
}
#[cfg(feature = "semantic")]
fn canonical_file_path(file: &str) -> Option<Box<str>> {
let path = Path::new(file).canonicalize().ok()?;
Some(Box::from(path.to_str()?))
}
#[cfg(feature = "semantic")]
fn load_config_minimal() -> LoadCargoConfig {
LoadCargoConfig {
load_out_dirs_from_check: false,
with_proc_macro_server: ProcMacroServerChoice::None,
prefill_caches: false,
proc_macro_processes: 0,
num_worker_threads: 1,
}
}