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 anyhow::Context;
use genemichaels::FormatConfig;
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;
}
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 { .. } => {}
}

View file

@ -1,13 +1,90 @@
use anyhow::Result;
use std::{fs, path::Path};
pub(crate) use self::file::SourceFile;
mod file {
use anyhow::{Context, Result};
use std::{
fmt::Debug,
fs,
cell::RefCell,
path::{Path, PathBuf},
};
#[derive(PartialEq, Eq, Clone, Hash)]
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 {
pub(crate) path: PathBuf,
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)]
@ -17,26 +94,29 @@ pub(crate) struct Changes {
pub(crate) struct FileChange<'a, 'b> {
pub(crate) path: &'a Path,
content: String,
source_file: &'a SourceFile,
before_content_str: String,
before_content: syn::File,
changes: &'b mut Changes,
has_written_change: bool,
}
impl FileChange<'_, '_> {
pub(crate) fn before_content(&self) -> &str {
&self.content
pub(crate) fn before_content(&self) -> (&str, &syn::File) {
(&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;
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<()> {
assert!(self.has_written_change);
self.has_written_change = false;
fs::write(self.path, &self.content)
.with_context(|| format!("writing file {}", self.path.display()))
self.source_file.write(self.before_content.clone())?;
Ok(())
}
pub(crate) fn commit(mut self) {
@ -49,7 +129,7 @@ impl FileChange<'_, '_> {
impl Drop for FileChange<'_, '_> {
fn drop(&mut self) {
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() {
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 {
pub(crate) fn had_changes(&self) -> bool {
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
}
})
.map(|entry| SourceFile {
path: entry.into_path(),
})
.map(|entry| SourceFile::open(entry.into_path()))
.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() {
bail!("Did not find any files for path {}", path.display());
@ -188,20 +188,17 @@ impl Minimizer {
let mut checker = PassController::new(self.options.clone());
loop {
let file_display = file.path.display();
let mut change = file.try_change(changes)?;
let mut krate = syn::parse_file(change.before_content())
.with_context(|| format!("parsing file {file_display}"))?;
let (_, krate) = change.before_content();
let mut krate = krate.clone();
let has_made_change = pass.process_file(&mut krate, file, &mut checker);
match has_made_change {
ProcessState::Changed | ProcessState::FileInvalidated => {
let result = crate::formatting::format(krate)?;
change.write(&result)?;
change.write(krate)?;
let after = self.build.build()?;
info!("{file_display}: After {}: {after}", pass.name());
info!("{file:?}: After {}: {after}", pass.name());
if after.reproduces_issue() {
change.commit();
@ -217,13 +214,9 @@ impl Minimizer {
}
ProcessState::NoChange => {
if self.options.no_color {
info!("{file_display}: After {}: no changes", pass.name());
info!("{file:?}: After {}: no changes", pass.name());
} else {
info!(
"{file_display}: After {}: {}",
pass.name(),
"no changes".yellow()
);
info!("{file:?}: After {}: {}", pass.name(), "no changes".yellow());
}
checker.no_change();
}

View file

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