Skip to content

Commit

Permalink
add in memory AST
Browse files Browse the repository at this point in the history
  • Loading branch information
Noratrieb committed Dec 31, 2023
1 parent 307cd52 commit cf39338
Show file tree
Hide file tree
Showing 5 changed files with 128 additions and 70 deletions.
7 changes: 6 additions & 1 deletion src/formatting.rs
Original file line number Diff line number Diff line change
@@ -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,
)
}
2 changes: 1 addition & 1 deletion src/processor/checker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 { .. } => {}
}
Expand Down
138 changes: 98 additions & 40 deletions src/processor/files.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,90 @@
use anyhow::{Context, Result};
use std::{
fmt::Debug,
fs,
path::{Path, PathBuf},
};

#[derive(PartialEq, Eq, Clone, Hash)]
pub(crate) struct SourceFile {
pub(crate) path: PathBuf,
use anyhow::Result;
use std::{fs, path::Path};

pub(crate) use self::file::SourceFile;

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)]
Expand All @@ -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) {
Expand All @@ -49,38 +129,16 @@ 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!");
}
}
}
}

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)
}
}
29 changes: 11 additions & 18 deletions src/processor/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand Down Expand Up @@ -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();
Expand All @@ -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();
}
Expand Down
22 changes: 12 additions & 10 deletions src/processor/reaper.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};

Expand Down Expand Up @@ -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;
};
Expand All @@ -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();
Expand All @@ -101,7 +103,7 @@ impl Minimizer {
struct DeleteUnusedFunctions {
diags: Vec<Diagnostic>,
build: Build,
invalid: HashSet<SourceFile>,
invalid: HashSet<PathBuf>,
}

impl DeleteUnusedFunctions {
Expand Down Expand Up @@ -129,15 +131,15 @@ 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"
);

let mut visitor = FindUnusedFunction::new(file, self.diags.iter(), checker);
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
Expand Down Expand Up @@ -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;
}

Expand Down

0 comments on commit cf39338

Please sign in to comment.