use std::collections::BTreeMap;
use std::fs;
use std::path::PathBuf;
use std::sync::Arc;
use cairo_lang_utils::ordered_hash_map::OrderedHashMap;
use cairo_lang_utils::{LookupIntern, Upcast};
use semver::Version;
use serde::{Deserialize, Serialize};
use smol_str::SmolStr;
use crate::cfg::CfgSet;
use crate::flag::Flag;
use crate::ids::{
CodeMapping, CrateId, CrateLongId, Directory, FileId, FileLongId, FlagId, FlagLongId,
VirtualFile,
};
use crate::span::{FileSummary, TextOffset, TextSpan, TextWidth};
#[cfg(test)]
#[path = "db_test.rs"]
mod test;
pub const CORELIB_CRATE_NAME: &str = "core";
pub const CORELIB_VERSION: &str = env!("CARGO_PKG_VERSION");
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct CrateConfiguration {
pub root: Directory,
pub settings: CrateSettings,
}
impl CrateConfiguration {
pub fn default_for_root(root: Directory) -> Self {
Self { root, settings: CrateSettings::default() }
}
}
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct CrateSettings {
pub edition: Edition,
pub version: Option<Version>,
pub cfg_set: Option<CfgSet>,
#[serde(default)]
pub dependencies: BTreeMap<String, DependencySettings>,
#[serde(default)]
pub experimental_features: ExperimentalFeaturesConfig,
}
#[derive(Clone, Copy, Debug, Default, Hash, PartialEq, Eq, Serialize, Deserialize)]
pub enum Edition {
#[default]
#[serde(rename = "2023_01")]
V2023_01,
#[serde(rename = "2023_10")]
V2023_10,
#[serde(rename = "2023_11")]
V2023_11,
#[serde(rename = "2024_07")]
V2024_07,
}
impl Edition {
pub const fn latest() -> Self {
Self::V2024_07
}
pub fn prelude_submodule_name(&self) -> &str {
match self {
Self::V2023_01 => "v2023_01",
Self::V2023_10 | Self::V2023_11 => "v2023_10",
Self::V2024_07 => "v2024_07",
}
}
pub fn ignore_visibility(&self) -> bool {
match self {
Self::V2023_01 | Self::V2023_10 => true,
Self::V2023_11 | Self::V2024_07 => false,
}
}
}
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct DependencySettings {
pub discriminator: Option<SmolStr>,
}
#[derive(Clone, Debug, Default, Hash, PartialEq, Eq, Serialize, Deserialize)]
pub struct ExperimentalFeaturesConfig {
pub negative_impls: bool,
#[serde(default)]
pub coupons: bool,
}
pub trait ExternalFiles {
fn ext_as_virtual(&self, _external_id: salsa::InternId) -> VirtualFile {
panic!("Should not be called, unless specifically implemented!");
}
}
#[salsa::query_group(FilesDatabase)]
pub trait FilesGroup: ExternalFiles {
#[salsa::interned]
fn intern_crate(&self, crt: CrateLongId) -> CrateId;
#[salsa::interned]
fn intern_file(&self, file: FileLongId) -> FileId;
#[salsa::interned]
fn intern_flag(&self, flag: FlagLongId) -> FlagId;
#[salsa::input]
fn crate_configs(&self) -> Arc<OrderedHashMap<CrateId, CrateConfiguration>>;
#[salsa::input]
fn file_overrides(&self) -> Arc<OrderedHashMap<FileId, Arc<str>>>;
#[salsa::input]
fn flags(&self) -> Arc<OrderedHashMap<FlagId, Arc<Flag>>>;
#[salsa::input]
fn cfg_set(&self) -> Arc<CfgSet>;
fn crates(&self) -> Vec<CrateId>;
fn crate_config(&self, crate_id: CrateId) -> Option<CrateConfiguration>;
fn priv_raw_file_content(&self, file_id: FileId) -> Option<Arc<str>>;
fn file_content(&self, file_id: FileId) -> Option<Arc<str>>;
fn file_summary(&self, file_id: FileId) -> Option<Arc<FileSummary>>;
fn get_flag(&self, id: FlagId) -> Option<Arc<Flag>>;
}
pub fn init_files_group(db: &mut (dyn FilesGroup + 'static)) {
db.set_file_overrides(Arc::new(OrderedHashMap::default()));
db.set_crate_configs(Arc::new(OrderedHashMap::default()));
db.set_flags(Arc::new(OrderedHashMap::default()));
db.set_cfg_set(Arc::new(CfgSet::new()));
}
pub fn init_dev_corelib(db: &mut (dyn FilesGroup + 'static), core_lib_dir: PathBuf) {
db.set_crate_config(
CrateId::core(db),
Some(CrateConfiguration {
root: Directory::Real(core_lib_dir),
settings: CrateSettings {
edition: Edition::V2024_07,
version: Version::parse(CORELIB_VERSION).ok(),
cfg_set: Default::default(),
dependencies: Default::default(),
experimental_features: ExperimentalFeaturesConfig {
negative_impls: true,
coupons: true,
},
},
}),
);
}
impl AsFilesGroupMut for dyn FilesGroup {
fn as_files_group_mut(&mut self) -> &mut (dyn FilesGroup + 'static) {
self
}
}
pub trait FilesGroupEx: Upcast<dyn FilesGroup> + AsFilesGroupMut {
fn override_file_content(&mut self, file: FileId, content: Option<Arc<str>>) {
let mut overrides = Upcast::upcast(self).file_overrides().as_ref().clone();
match content {
Some(content) => overrides.insert(file, content),
None => overrides.swap_remove(&file),
};
self.as_files_group_mut().set_file_overrides(Arc::new(overrides));
}
fn set_crate_config(&mut self, crt: CrateId, root: Option<CrateConfiguration>) {
let mut crate_configs = Upcast::upcast(self).crate_configs().as_ref().clone();
match root {
Some(root) => crate_configs.insert(crt, root),
None => crate_configs.swap_remove(&crt),
};
self.as_files_group_mut().set_crate_configs(Arc::new(crate_configs));
}
fn set_flag(&mut self, id: FlagId, value: Option<Arc<Flag>>) {
let mut flags = Upcast::upcast(self).flags().as_ref().clone();
match value {
Some(value) => flags.insert(id, value),
None => flags.swap_remove(&id),
};
self.as_files_group_mut().set_flags(Arc::new(flags));
}
fn use_cfg(&mut self, cfg_set: &CfgSet) {
let existing = Upcast::upcast(self).cfg_set();
let merged = existing.union(cfg_set);
self.as_files_group_mut().set_cfg_set(Arc::new(merged));
}
}
impl<T: Upcast<dyn FilesGroup> + AsFilesGroupMut + ?Sized> FilesGroupEx for T {}
pub trait AsFilesGroupMut {
fn as_files_group_mut(&mut self) -> &mut (dyn FilesGroup + 'static);
}
fn crates(db: &dyn FilesGroup) -> Vec<CrateId> {
db.crate_configs().keys().copied().collect()
}
fn crate_config(db: &dyn FilesGroup, crt: CrateId) -> Option<CrateConfiguration> {
match crt.lookup_intern(db) {
CrateLongId::Real { .. } => db.crate_configs().get(&crt).cloned(),
CrateLongId::Virtual { name: _, file_id, settings } => Some(CrateConfiguration {
root: Directory::Virtual {
files: BTreeMap::from([("lib.cairo".into(), file_id)]),
dirs: Default::default(),
},
settings: toml::from_str(&settings).expect("Failed to parse virtual crate settings."),
}),
}
}
fn priv_raw_file_content(db: &dyn FilesGroup, file: FileId) -> Option<Arc<str>> {
match file.lookup_intern(db) {
FileLongId::OnDisk(path) => match fs::read_to_string(path) {
Ok(content) => Some(content.into()),
Err(_) => None,
},
FileLongId::Virtual(virt) => Some(virt.content),
FileLongId::External(external_id) => Some(db.ext_as_virtual(external_id).content),
}
}
fn file_content(db: &dyn FilesGroup, file: FileId) -> Option<Arc<str>> {
let overrides = db.file_overrides();
overrides.get(&file).cloned().or_else(|| db.priv_raw_file_content(file))
}
fn file_summary(db: &dyn FilesGroup, file: FileId) -> Option<Arc<FileSummary>> {
let content = db.file_content(file)?;
let mut line_offsets = vec![TextOffset::default()];
let mut offset = TextOffset::default();
for ch in content.chars() {
offset = offset.add_width(TextWidth::from_char(ch));
if ch == '\n' {
line_offsets.push(offset);
}
}
Some(Arc::new(FileSummary { line_offsets, last_offset: offset }))
}
fn get_flag(db: &dyn FilesGroup, id: FlagId) -> Option<Arc<Flag>> {
db.flags().get(&id).cloned()
}
pub fn get_originating_location(
db: &dyn FilesGroup,
mut file_id: FileId,
mut span: TextSpan,
) -> (FileId, TextSpan) {
while let Some((parent, code_mappings)) = get_parent_and_mapping(db, file_id) {
if let Some(origin) = code_mappings.iter().find_map(|mapping| mapping.translate(span)) {
span = origin;
file_id = parent;
} else {
break;
}
}
(file_id, span)
}
fn get_parent_and_mapping(
db: &dyn FilesGroup,
file_id: FileId,
) -> Option<(FileId, Arc<[CodeMapping]>)> {
let vf = match file_id.lookup_intern(db) {
FileLongId::OnDisk(_) => return None,
FileLongId::Virtual(vf) => vf,
FileLongId::External(id) => db.ext_as_virtual(id),
};
Some((vf.parent?, vf.code_mappings))
}