use std::{
fmt::Display,
ops::{Deref, Range},
};
use syn::{ItemStruct, Visibility, spanned::Spanned};
use crate::{
ByteRange, Change, Code, Diff, ExistenceChange, SourceFile, View, ViewableDiff, ViewableDiffs,
VisDiff, collect_src_maps, overview::fields::FieldDiff,
};
use super::{
fields::{Fields, FieldsDiff},
generics::{Generics, GenericsDiff},
};
const NO_SRC_ERROR: &str = "No source text for struct, was parse logic changed?";
#[derive(Debug)]
pub struct Structs(pub Vec<Struct>);
impl Structs {
pub fn from(mut structs: Vec<Struct>) -> Self {
structs.sort_by(|s1, s2| s1.name().cmp(s2.name()));
structs.dedup_by(|s1, s2| s1.name() == s2.name());
Structs(structs)
}
}
impl Diff for Structs {
type Diff = StructsDiff;
fn diff_with(&self, other: &Self) -> Self::Diff {
debug_assert!(self.0.is_sorted_by(|s1, s2| s1.name() <= s2.name()));
debug_assert!(other.0.is_sorted_by(|s1, s2| s1.name() <= s2.name()));
let mut struct_diffs = Vec::new();
for struct_ in &self.0 {
match other.0.binary_search_by(|s| s.name().cmp(struct_.name())) {
Ok(s) => {
if let Some(diff) = struct_.diff_with(&other.0[s]) {
struct_diffs.push(diff);
}
}
Err(_e) => {
let sdiff = StructDiff {
name: struct_.name().to_string(),
change: Change::Existence(ExistenceChange::Deleted),
old: Some(struct_.original.clone()),
old_src: Some(struct_.source.clone()),
..Default::default()
};
struct_diffs.push(sdiff);
}
}
}
for struct_ in &other.0 {
if let Err(_e) = self.0.binary_search_by(|s| s.name().cmp(struct_.name())) {
let sdiff = StructDiff {
name: struct_.name().to_string(),
change: Change::Existence(ExistenceChange::Added),
new: Some(struct_.original.clone()),
new_src: Some(struct_.source.clone()),
..Default::default()
};
struct_diffs.push(sdiff);
}
}
struct_diffs.sort_by(|d1, d2| d1.name.cmp(&d2.name));
StructsDiff {
structs: struct_diffs,
}
}
}
impl Deref for Structs {
type Target = [Struct];
fn deref(&self) -> &Self::Target {
&self.0
}
}
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
pub struct Struct {
name: String,
vis: Visibility,
fields: Fields,
generics: Generics,
original: ItemStruct,
source: SourceFile,
}
impl Struct {
pub fn new(s: ItemStruct, source: SourceFile) -> Self {
let original = s.clone();
let vis = s.vis;
let name = s.ident.to_string();
let fields = s.fields.clone();
let fields = Fields(fields);
let generics = Generics::from(s.generics.clone());
Self {
name,
vis,
fields,
generics,
original,
source,
}
}
pub fn name(&self) -> &str {
&self.name
}
}
impl Diff for Struct {
type Diff = Option<StructDiff>;
fn diff_with(&self, other: &Self) -> Self::Diff {
let self_name = self.name();
let other_name = other.name();
if self == other {
return None;
}
if self_name != other_name {
return None;
}
let vis_diff = self.vis.diff_with(&other.vis);
let fields_diff = self.fields.diff_with(&other.fields);
let generics_diff = self.generics.diff_with(&other.generics);
if vis_diff.is_none() && fields_diff.is_none() && generics_diff.is_none() {
return None;
}
let (old_src_map, new_src_map) = collect_src_maps!(vis_diff, fields_diff, generics_diff,);
Some(StructDiff {
name: self_name.to_string(),
change: Change::Modified,
old: Some(self.original.clone()),
new: Some(other.original.clone()),
vis_diff,
fields_diff,
generics_diff,
old_src: Some(self.source.clone()),
new_src: Some(other.source.clone()),
old_src_map,
new_src_map,
})
}
}
impl Display for Struct {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let vis = self.vis.span().source_text().unwrap();
write!(f, "{vis} struct {}", self.name)
}
}
pub struct StructsDiff {
structs: Vec<StructDiff>,
}
impl StructsDiff {
pub(crate) fn is_empty(&self) -> bool {
self.structs.is_empty()
}
}
impl View for StructsDiff {
fn as_viewable(&self) -> ViewableDiffs {
let ex_diffs = self
.structs
.iter()
.filter(|diff| matches!(diff.change, Change::Existence(_)));
let mut viewables = ViewableDiffs::empty();
for ex_diff in ex_diffs {
viewables.append(ex_diff.as_viewable());
}
viewables.collapse();
let mod_diffs = self
.structs
.iter()
.filter(|diff| matches!(diff.change, Change::Modified));
for mod_diff in mod_diffs {
viewables.append(mod_diff.as_viewable());
}
viewables
}
}
#[derive(Debug, Default)]
#[allow(unused)]
pub struct StructDiff {
name: String,
change: Change,
old: Option<ItemStruct>,
new: Option<ItemStruct>,
vis_diff: Option<VisDiff>,
fields_diff: Option<FieldsDiff>,
generics_diff: Option<GenericsDiff>,
old_src: Option<SourceFile>,
new_src: Option<SourceFile>,
old_src_map: Vec<Range<usize>>,
new_src_map: Vec<Range<usize>>,
}
impl View for StructDiff {
fn as_viewable(&self) -> ViewableDiffs {
if let Change::Existence(ex) = self.change {
return collect_existence_diff_changes(&self.old, &self.new, ex);
}
let old_diff = collect_struct_diff_changes(
self.old.as_ref().unwrap(),
self.old_src.as_ref().unwrap().0.as_bytes(),
&self.old_src_map,
&self.fields_diff,
ExistenceChange::Deleted,
);
let new_diff = collect_struct_diff_changes(
self.new.as_ref().unwrap(),
self.new_src.as_ref().unwrap().0.as_bytes(),
&self.new_src_map,
&self.fields_diff,
ExistenceChange::Added,
);
ViewableDiffs::new(vec![ViewableDiff {
old: Some(old_diff),
new: Some(new_diff),
}])
}
}
fn collect_existence_diff_changes(
old: &Option<ItemStruct>,
new: &Option<ItemStruct>,
ex: ExistenceChange,
) -> ViewableDiffs {
let struct_ = match (&old, &new) {
(Some(_), Some(_)) => panic!("old and new structs were both Some"),
(None, None) => panic!("old and new structs were both None"),
(Some(_struct), None) | (None, Some(_struct)) => _struct,
};
let source = struct_.span().source_text().expect(NO_SRC_ERROR);
let diff_changes = vec![(Some(ex), Code(format!("{source}\n")))];
match ex {
ExistenceChange::Deleted => ViewableDiffs::new(vec![ViewableDiff {
old: Some(diff_changes),
new: None,
}]),
ExistenceChange::Added => ViewableDiffs::new(vec![ViewableDiff {
old: None,
new: Some(diff_changes),
}]),
}
}
fn collect_struct_diff_changes(
struct_: &ItemStruct,
source_code: &[u8],
source_map: &[Range<usize>],
field_diffs: &Option<FieldsDiff>,
ex: ExistenceChange,
) -> Vec<(Option<ExistenceChange>, Code)> {
let source_range = struct_.span().byte_range();
let decl_start = source_range.start;
let sig_end = end_index_of_signature(struct_);
let fields_end = end_index_of_fields(struct_);
let mut diff_changes =
crate::collect_diff_changes(source_code, source_map, decl_start, sig_end, ex);
if let Some(field_diffs) = field_diffs {
let (get_orig_field, get_sub_diff): (
Box<dyn Fn(&FieldDiff) -> Option<&syn::Field>>,
Box<dyn Fn(ViewableDiff) -> Option<Vec<(Option<ExistenceChange>, Code)>>>,
);
match ex {
ExistenceChange::Added => {
get_orig_field = Box::new(|fd: &FieldDiff| fd.new());
get_sub_diff = Box::new(|vd: ViewableDiff| vd.new);
}
ExistenceChange::Deleted => {
get_orig_field = Box::new(|fd: &FieldDiff| fd.old());
get_sub_diff = Box::new(|vd: ViewableDiff| vd.old);
}
};
let diffs_as_changes = collect_field_diffs(
source_code,
struct_,
sig_end,
field_diffs,
get_orig_field,
get_sub_diff,
);
diff_changes.extend(diffs_as_changes);
}
let code = String::from_utf8(source_code[fields_end..source_range.end].to_vec())
.expect("Off a code boundary");
diff_changes.push((None, Code(code)));
diff_changes
}
fn collect_field_diffs(
source_code: &[u8],
struct_: &ItemStruct,
sig_end: usize,
fds: &FieldsDiff,
get_original_field: Box<dyn Fn(&FieldDiff) -> Option<&syn::Field>>,
get_sub_diffs: Box<dyn Fn(ViewableDiff) -> Option<Vec<(Option<ExistenceChange>, Code)>>>,
) -> Vec<(Option<ExistenceChange>, Code)> {
let mut diffs = Vec::new();
let mut i = sig_end;
let struct_range = struct_.span().byte_range();
if struct_.fields.len() > fds.len() {
let elided_whitespace =
crate::collect_elided_whitespace(sig_end, source_code, struct_range.end);
diffs.push((None, Code(elided_whitespace)));
}
while i < struct_range.end {
let maybe_item_diff = fds.diffs().iter().find(|d| {
get_original_field(d)
.as_ref()
.map(|field| field.span().byte_range().contains(&i))
.unwrap_or(false)
});
match maybe_item_diff {
Some(id) => {
let item_diff_range = get_original_field(id).as_ref().unwrap().span().byte_range();
let item_diff_start = item_diff_range.start;
let item_diff_end = item_diff_range.end;
let whitespace = crate::collect_preceding_whitespace(source_code, item_diff_start);
diffs.push((None, Code(whitespace)));
let viewable = id.as_viewable();
for diff in viewable.vds {
if let Some(sub_diff) = get_sub_diffs(diff) {
diffs.extend(sub_diff);
}
}
i = item_diff_end;
}
None => {
i += 1;
}
}
}
if diffs.is_empty() && struct_.fields.is_empty() {
let elided_whitespace =
crate::collect_elided_whitespace(sig_end, source_code, struct_range.end);
diffs.push((None, Code(elided_whitespace)));
}
diffs
}
fn end_index_of_signature(struct_: &ItemStruct) -> usize {
match &struct_.fields {
syn::Fields::Named(fields_named) => {
fields_named.brace_token.span.span().byte_range().start + 1
}
syn::Fields::Unnamed(fields_unnamed) => {
fields_unnamed.paren_token.span.span().byte_range().start + 1
}
syn::Fields::Unit => struct_.span().byte_range().end,
}
}
fn end_index_of_fields(struct_: &ItemStruct) -> usize {
match &struct_.fields {
syn::Fields::Named(fields_named) => fields_named.named.span().byte_range().end,
syn::Fields::Unnamed(fields_unnamed) => fields_unnamed.unnamed.span().byte_range().end,
syn::Fields::Unit => struct_.span().byte_range().end,
}
}