add in memory AST

This commit is contained in:
nora 2023-12-31 17:01:16 +01:00
parent 307cd52178
commit cf39338b30
5 changed files with 127 additions and 69 deletions

View file

@ -1,7 +1,12 @@
use std::collections::HashMap; use std::collections::HashMap;
use anyhow::Context;
use genemichaels::FormatConfig; use genemichaels::FormatConfig;
pub fn format(file: syn::File) -> anyhow::Result<String> { pub fn format(file: syn::File) -> anyhow::Result<String> {
Ok(genemichaels::format_ast(file, &FormatConfig::default(), HashMap::new())?.rendered) Ok(
genemichaels::format_ast(file, &FormatConfig::default(), HashMap::new())
.context("formatting source file")?
.rendered,
)
} }

View file

@ -166,7 +166,7 @@ impl PassController {
self.state = PassControllerState::Success; self.state = PassControllerState::Success;
} }
PassControllerState::Bisecting { current, .. } => { PassControllerState::Bisecting { current, .. } => {
unreachable!("No change while bisecting, current was empty somehow: {current:?}"); unreachable!("Pass said it didn't change anything in the bisection phase, nils forgot what this means: {current:?}");
} }
PassControllerState::Success { .. } => {} PassControllerState::Success { .. } => {}
} }

View file

@ -1,13 +1,90 @@
use anyhow::{Context, Result}; use anyhow::Result;
use std::{ use std::{fs, path::Path};
fmt::Debug,
fs,
path::{Path, PathBuf},
};
#[derive(PartialEq, Eq, Clone, Hash)] pub(crate) use self::file::SourceFile;
pub(crate) struct SourceFile {
pub(crate) path: PathBuf, mod file {
use anyhow::{Context, Result};
use std::{
cell::RefCell,
path::{Path, PathBuf},
};
use super::{Changes, FileChange};
/// The representation of a source file, with the cached AST.
/// IMPORTANT INVARIANT: All file system operations MUST go through this type.
/// This also shouldn't be `Clone`, so the cache is always representative of the file system state.
/// It is inteded for the "cache" to be the source of truth.
pub(crate) struct SourceFile {
path: PathBuf,
content_str: RefCell<String>,
content: RefCell<syn::File>,
}
impl SourceFile {
pub(crate) fn open(path: PathBuf) -> Result<Self> {
let string = std::fs::read_to_string(&path)
.with_context(|| format!("reading file {}", path.display()))?;
let content = syn::parse_file(&string)
.with_context(|| format!("parsing file {}", path.display()))?;
Ok(SourceFile {
path,
content_str: RefCell::new(string),
content: RefCell::new(content),
})
}
pub(crate) fn write(&self, new: syn::File) -> Result<()> {
let string = crate::formatting::format(new.clone())?;
std::fs::write(&self.path, &string)
.with_context(|| format!("writing file {}", self.path.display()))?;
*self.content_str.borrow_mut() = string;
*self.content.borrow_mut() = new;
Ok(())
}
pub(crate) fn path_no_fs_interact(&self) -> &Path {
&self.path
}
}
impl PartialEq for SourceFile {
fn eq(&self, other: &Self) -> bool {
self.path == other.path
}
}
impl std::hash::Hash for SourceFile {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.path.hash(state);
}
}
impl Eq for SourceFile {}
impl std::fmt::Debug for SourceFile {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.path.display())
}
}
impl SourceFile {
pub(crate) fn try_change<'file, 'change>(
&'file self,
changes: &'change mut Changes,
) -> Result<FileChange<'file, 'change>> {
let path = &self.path;
Ok(FileChange {
path,
source_file: self,
changes,
has_written_change: false,
before_content_str: self.content_str.borrow().clone(),
before_content: self.content.borrow().clone(),
})
}
}
} }
#[derive(Default)] #[derive(Default)]
@ -17,26 +94,29 @@ pub(crate) struct Changes {
pub(crate) struct FileChange<'a, 'b> { pub(crate) struct FileChange<'a, 'b> {
pub(crate) path: &'a Path, pub(crate) path: &'a Path,
content: String, source_file: &'a SourceFile,
before_content_str: String,
before_content: syn::File,
changes: &'b mut Changes, changes: &'b mut Changes,
has_written_change: bool, has_written_change: bool,
} }
impl FileChange<'_, '_> { impl FileChange<'_, '_> {
pub(crate) fn before_content(&self) -> &str { pub(crate) fn before_content(&self) -> (&str, &syn::File) {
&self.content (&self.before_content_str, &self.before_content)
} }
pub(crate) fn write(&mut self, new: &str) -> Result<()> { pub(crate) fn write(&mut self, new: syn::File) -> Result<()> {
self.has_written_change = true; self.has_written_change = true;
fs::write(self.path, new).with_context(|| format!("writing file {}", self.path.display())) self.source_file.write(new)?;
Ok(())
} }
pub(crate) fn rollback(mut self) -> Result<()> { pub(crate) fn rollback(mut self) -> Result<()> {
assert!(self.has_written_change); assert!(self.has_written_change);
self.has_written_change = false; self.has_written_change = false;
fs::write(self.path, &self.content) self.source_file.write(self.before_content.clone())?;
.with_context(|| format!("writing file {}", self.path.display())) Ok(())
} }
pub(crate) fn commit(mut self) { pub(crate) fn commit(mut self) {
@ -49,7 +129,7 @@ impl FileChange<'_, '_> {
impl Drop for FileChange<'_, '_> { impl Drop for FileChange<'_, '_> {
fn drop(&mut self) { fn drop(&mut self) {
if self.has_written_change { if self.has_written_change {
fs::write(self.path, self.before_content()).ok(); fs::write(self.path, self.before_content().0).ok();
if !std::thread::panicking() { if !std::thread::panicking() {
panic!("File contains unsaved changes!"); panic!("File contains unsaved changes!");
} }
@ -57,30 +137,8 @@ impl Drop for FileChange<'_, '_> {
} }
} }
impl SourceFile {
pub(crate) fn try_change<'file, 'change>(
&'file self,
changes: &'change mut Changes,
) -> Result<FileChange<'file, 'change>> {
let path = &self.path;
Ok(FileChange {
path,
changes,
has_written_change: false,
content: fs::read_to_string(path)
.with_context(|| format!("opening file {}", path.display()))?,
})
}
}
impl Changes { impl Changes {
pub(crate) fn had_changes(&self) -> bool { pub(crate) fn had_changes(&self) -> bool {
self.any_change self.any_change
} }
} }
impl Debug for SourceFile {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
<Path as Debug>::fmt(&self.path, f)
}
}

View file

@ -98,13 +98,13 @@ impl Minimizer {
true true
} }
}) })
.map(|entry| SourceFile { .map(|entry| SourceFile::open(entry.into_path()))
path: entry.into_path(),
})
.inspect(|file| { .inspect(|file| {
info!("Collecting file: {}", file.path.display()); if let Ok(file) = file {
info!("Collecting file: {file:?}");
}
}) })
.collect::<Vec<_>>(); .collect::<Result<Vec<_>>>()?;
if files.is_empty() { if files.is_empty() {
bail!("Did not find any files for path {}", path.display()); bail!("Did not find any files for path {}", path.display());
@ -188,20 +188,17 @@ impl Minimizer {
let mut checker = PassController::new(self.options.clone()); let mut checker = PassController::new(self.options.clone());
loop { loop {
let file_display = file.path.display();
let mut change = file.try_change(changes)?; let mut change = file.try_change(changes)?;
let mut krate = syn::parse_file(change.before_content()) let (_, krate) = change.before_content();
.with_context(|| format!("parsing file {file_display}"))?; let mut krate = krate.clone();
let has_made_change = pass.process_file(&mut krate, file, &mut checker); let has_made_change = pass.process_file(&mut krate, file, &mut checker);
match has_made_change { match has_made_change {
ProcessState::Changed | ProcessState::FileInvalidated => { ProcessState::Changed | ProcessState::FileInvalidated => {
let result = crate::formatting::format(krate)?; change.write(krate)?;
change.write(&result)?;
let after = self.build.build()?; let after = self.build.build()?;
info!("{file_display}: After {}: {after}", pass.name()); info!("{file:?}: After {}: {after}", pass.name());
if after.reproduces_issue() { if after.reproduces_issue() {
change.commit(); change.commit();
@ -217,13 +214,9 @@ impl Minimizer {
} }
ProcessState::NoChange => { ProcessState::NoChange => {
if self.options.no_color { if self.options.no_color {
info!("{file_display}: After {}: no changes", pass.name()); info!("{file:?}: After {}: no changes", pass.name());
} else { } else {
info!( info!("{file:?}: After {}: {}", pass.name(), "no changes".yellow());
"{file_display}: After {}: {}",
pass.name(),
"no changes".yellow()
);
} }
checker.no_change(); checker.no_change();
} }

View file

@ -10,7 +10,7 @@ use rustfix::{diagnostics::Diagnostic, Suggestion};
use std::{ use std::{
collections::{HashMap, HashSet}, collections::{HashMap, HashSet},
ops::Range, ops::Range,
path::Path, path::{Path, PathBuf},
}; };
use syn::{visit_mut::VisitMut, ImplItem, Item}; use syn::{visit_mut::VisitMut, ImplItem, Item};
@ -63,7 +63,8 @@ impl Minimizer {
) -> Result<()> { ) -> Result<()> {
for (sugg_file, suggestions) in suggestions { for (sugg_file, suggestions) in suggestions {
let Some(file) = self.files.iter().find(|source| { let Some(file) = self.files.iter().find(|source| {
source.path.ends_with(sugg_file) || sugg_file.ends_with(&source.path) source.path_no_fs_interact().ends_with(sugg_file)
|| sugg_file.ends_with(source.path_no_fs_interact())
}) else { }) else {
continue; continue;
}; };
@ -79,13 +80,14 @@ impl Minimizer {
.cloned() .cloned()
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let result = rustfix::apply_suggestions(change.before_content(), &desired_suggestions)?; let result =
rustfix::apply_suggestions(change.before_content().0, &desired_suggestions)?;
change.write(&result)?; let result = syn::parse_file(&result).context("parsing file after rustfix")?;
change.write(result)?;
let after = self.build.build()?; let after = self.build.build()?;
info!("{}: After reaper: {after}", file.path.display()); info!("{file:?}: After reaper: {after}");
if after.reproduces_issue() { if after.reproduces_issue() {
change.commit(); change.commit();
@ -101,7 +103,7 @@ impl Minimizer {
struct DeleteUnusedFunctions { struct DeleteUnusedFunctions {
diags: Vec<Diagnostic>, diags: Vec<Diagnostic>,
build: Build, build: Build,
invalid: HashSet<SourceFile>, invalid: HashSet<PathBuf>,
} }
impl DeleteUnusedFunctions { impl DeleteUnusedFunctions {
@ -129,7 +131,7 @@ impl Pass for DeleteUnusedFunctions {
checker: &mut super::PassController, checker: &mut super::PassController,
) -> ProcessState { ) -> ProcessState {
assert!( assert!(
!self.invalid.contains(file), !self.invalid.contains(file.path_no_fs_interact()),
"processing with invalid state" "processing with invalid state"
); );
@ -137,7 +139,7 @@ impl Pass for DeleteUnusedFunctions {
visitor.visit_file_mut(krate); visitor.visit_file_mut(krate);
if visitor.process_state == ProcessState::FileInvalidated { if visitor.process_state == ProcessState::FileInvalidated {
self.invalid.insert(file.clone()); self.invalid.insert(file.path_no_fs_interact().to_owned());
} }
visitor.process_state visitor.process_state
@ -203,7 +205,7 @@ impl<'a> FindUnusedFunction<'a> {
); );
// When the project directory is remapped, the path may be absolute or generally have some prefix. // When the project directory is remapped, the path may be absolute or generally have some prefix.
if !file.path.ends_with(&span.file_name) { if !file.path_no_fs_interact().ends_with(&span.file_name) {
return None; return None;
} }