Skip to content

Instantly share code, notes, and snippets.

@kabergstrom
Created May 26, 2025 20:01
Show Gist options
  • Save kabergstrom/ee8b6884a92e5893a88f5ff3dbefa3e3 to your computer and use it in GitHub Desktop.
Save kabergstrom/ee8b6884a92e5893a88f5ff3dbefa3e3 to your computer and use it in GitHub Desktop.
use env_logger::Builder;
use hydrate_schema::{
Schema, SchemaDefRecordMarkup, SchemaFingerprint, SchemaRecord, SchemaRecordField,
};
use ra_ap_base_db::{FileId, SourceDatabase, SourceDatabaseExt, Upcast};
use ra_ap_hir::{db::HirDatabase, Crate, HasSource, HasVisibility, HirDisplay};
use ra_ap_ide_db::{symbol_index::SymbolsDatabase, RootDatabase};
use ra_ap_load_cargo::{load_workspace, LoadCargoConfig, ProcMacroServerChoice};
use ra_ap_paths::{AbsPath, AbsPathBuf, Utf8PathBuf};
use ra_ap_project_model::{CargoConfig, ProjectManifest, ProjectWorkspace, RustLibSource};
use ra_ap_syntax::ast::AstNode;
use ra_ap_text_edit::TextEdit;
use std::{
borrow::Borrow,
collections::HashMap,
iter::Map,
ops::ControlFlow,
path::{Path, PathBuf},
};
use uuid::Uuid;
pub type Semantics<'db> = ra_ap_hir::Semantics<'db, ra_ap_ide_db::RootDatabase>;
use log::{debug, info, trace, LevelFilter};
use std::fs::{read_to_string, write};
mod preloader;
struct TestThing {
val: bool,
f: f32,
}
fn main() {
let mut builder = Builder::from_default_env();
builder.filter_level(LevelFilter::Info).init();
run(
"/Users/karl/Perforce/BergstromK_Laptop/rust/card-engine",
"",
);
}
pub(crate) fn normalize(name: &str) -> String {
name.replace("-", "_")
}
pub fn run(root: &str, dep: &str) {
info!("Workspace root: {}", Path::new(root).display());
// Loading project
let root: Utf8PathBuf = root.into();
let manifest = ProjectManifest::discover_single(&AbsPathBuf::assert(root.clone())).unwrap();
let no_progress = &|_| {};
let mut cargo_config = CargoConfig::default();
cargo_config.sysroot = Some(RustLibSource::Discover);
let mut workspace = ProjectWorkspace::load(manifest, &cargo_config, no_progress).unwrap();
let bs = workspace
.run_build_scripts(&cargo_config, no_progress)
.unwrap();
workspace.set_build_scripts(bs);
let load_cargo_config = LoadCargoConfig {
load_out_dirs_from_check: true,
prefill_caches: false,
with_proc_macro_server: ProcMacroServerChoice::Sysroot,
};
let (host, vfs, _) =
load_workspace(workspace, &Default::default(), &load_cargo_config).unwrap();
// Preparing running wrapper
let db: &RootDatabase = &host;
let semantics = Semantics::new(db);
let mut changes = HashMap::<FileId, TextEdit>::new();
// Run init hook
let mut preloader = preloader::Preloader::default();
debug!("Crate graph: {:#?}", db.crate_graph());
let peers = ["card_engine"];
// Loop to find and eager load the dep we are upgrading
for krate in Crate::all(db) {
if let Some(name) = krate.display_name(db) {
debug!("Checking if we need to preload: {}", name);
if let Some(peer) = peers
.iter()
.find(|x| **x == normalize(&format!("{}", name)))
{
// preloader.load(&normalize(&format!("{}", name)), db, &krate);
for decl in krate.root_module().declarations(db) {
process_decl(decl, db);
}
for decl in krate.root_module().declarations(db) {
info!("crate {} has decl {:?}", name, decl);
match decl {
ra_ap_hir::ModuleDef::Function(f) => {
info!("crate {} has function {:?}", name, f.name(db))
}
_ => {}
}
}
} else {
println!("skipping {name}");
}
}
}
let root_abs: AbsPathBuf = AbsPathBuf::assert(root.clone());
// Actual loop to walk through the source code
for source_root_id in db.local_roots().iter() {
let source_root = db.source_root(*source_root_id);
let krates = db.source_root_crates(*source_root_id);
// Get all crates for this source root and skip if no root files of those crates
// are in the root path we are upgrading.
if !krates
.iter()
.filter_map(|crate_id| {
let krate: Crate = (*crate_id).into();
source_root.path_for_file(&krate.root_file(db))
})
.filter_map(|path| path.as_path())
.any(|path: &AbsPath| {
debug!("Checking if path in workspace: {}", path);
path.starts_with(&root_abs)
})
{
continue;
}
for file_id in source_root.iter() {
let file = vfs.file_path(file_id);
info!("Walking: {}", file.as_path().unwrap());
let source_file = semantics.parse(file_id);
trace!("Syntax: {:#?}", source_file.syntax());
// context.walk(source_file.syntax());
// let edit = context.upgrader.finish();
// debug!("Changes to be made: {:#?}", edit);
// changes.insert(file_id, edit);
}
}
// Apply changes
for (file_id, edit) in changes {
let full_path = vfs.file_path(file_id);
let full_path = full_path.as_path().unwrap();
let mut file_text = read_to_string(&full_path).unwrap();
edit.apply(&mut file_text);
// write(&full_path, file_text)?;
}
// TODO: Modify Cargo.toml
}
fn process_decl(decl: ra_ap_hir::ModuleDef, db: &RootDatabase) {
match decl {
ra_ap_hir::ModuleDef::Module(module) => {
for decl in module.declarations(db) {
process_decl(decl, db);
}
}
ra_ap_hir::ModuleDef::Adt(adt) => match adt {
ra_ap_hir::Adt::Struct(s) => {
let has_generics = s.ty(db).generic_parameters(db).next().is_some();
println!(
"FOUND {} STRUCT {:?} {:?}",
if has_generics { "GENERIC" } else { "" },
s,
s.name(db)
);
// let mut fields = Vec::new();
for field in s.fields(db) {
println!(
"{:?} {:?} {}",
field,
field.name(db),
field.ty(db).display(db)
);
if let Some(schema) = get_field_schema(field, db) {
} else {
continue;
}
// SchemaRecordField::new(field.name(db), Uuid::nil(), [].into())
}
let record = SchemaRecord::new(
s.name(db).as_str().unwrap().to_string(),
Uuid::nil(),
SchemaFingerprint::from_uuid(Uuid::nil()),
[].into(),
Vec::new(),
SchemaDefRecordMarkup::default(),
);
}
ra_ap_hir::Adt::Union(_) => todo!(),
ra_ap_hir::Adt::Enum(e) => {
println!("FOUND ENUM {:?}", e.name(db));
for variant in e.variants(db) {
match variant.kind(db) {
ra_ap_hir::StructKind::Tuple => {
println!("enum tuple variant {:?} {:?}", e.name(db), variant.name(db));
}
ra_ap_hir::StructKind::Record => {
println!(
"enum record variant {:?} {:?}",
e.name(db),
variant.name(db)
);
for field in variant.fields(db) {
get_field_schema(field, db);
}
}
ra_ap_hir::StructKind::Unit => {
println!("enum unit variant {:?} {:?}", e.name(db), variant.name(db));
}
}
}
}
},
_ => {}
}
}
fn get_field_schema(field: ra_ap_hir::Field, db: &RootDatabase) -> Option<Schema> {
let ty = field.ty(db);
let field_schema = if let Some(builtin) = ty.as_builtin() {
if builtin.is_int() {
match builtin.ty(db).layout(db).unwrap().size() {
4 => Schema::I32,
8 => Schema::I64,
_ => panic!("unhandled int size"),
}
} else if builtin.is_f32() {
Schema::F32
} else if builtin.is_f64() {
Schema::F64
} else if builtin.is_bool() {
Schema::Boolean
} else if builtin.is_uint() {
match builtin.ty(db).layout(db).unwrap().size() {
1 => Schema::U8,
2 => Schema::U16,
4 => Schema::U32,
8 => Schema::U64,
16 => Schema::U128,
_ => panic!("unhandled uint size"),
}
} else {
println!("unhandled builtin{:?}", builtin);
return None;
}
} else if ty.is_tuple() {
println!("tuple: {:?}", field.name(db),);
return None;
} else if ty.is_array() {
println!("found array: {:?} {:?}", field.name(db), field.ty(db));
return None;
} else if ty.is_scalar() {
println!("scalar: {:?}", field.name(db),);
return None;
} else if ty.is_fn() {
println!("found function: {:?} {:?}", field.name(db), field.ty(db));
return None;
} else if ty.is_reference() || ty.is_mutable_reference() {
println!("found reference: {:?} {:?}", field.name(db), field.ty(db));
return None;
} else if ty.is_raw_ptr() {
println!("found raw pointer: {:?} {:?}", field.name(db), field.ty(db));
return None;
} else if ty.is_tuple() {
println!("found tuple: {:?} {:?}", field.name(db), field.ty(db));
return None;
} else {
if let Some(adt) = ty.as_adt() {
println!("found {:?}: {:?}", adt, field.name(db));
} else {
println!("unhandled: {:?}", field.name(db),);
}
return None;
// todo!("not a builtin: {:?}", field);
};
Some(field_schema)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment