1use std::{collections::HashSet, fmt::Debug};
8
9use itertools::Itertools;
10
11use crate::{
12 diagnostics::Report, renamelist::{reorder_renames, RenameMoveaway, RenameOp, RenameTempAllocator}, uid::{RenameExt, RenameMap}, Diff
13};
14
15#[derive(Debug, PartialEq)]
16pub struct ChangeList<T: RenameExt> {
17 pub dropped: Vec<T>,
18 pub renamed: Vec<RenameOp<T>>,
19 pub moved_away: Vec<(T, RenameMoveaway)>,
20 pub updated: Vec<Diff<T>>,
21 pub created: Vec<T>,
22}
23impl<T: RenameExt> ChangeList<T> {
24 fn new() -> Self {
25 Self {
26 renamed: vec![],
27 created: vec![],
28 updated: vec![],
29 dropped: vec![],
30 moved_away: vec![],
31 }
32 }
33}
34
35pub trait IsCompatible {
36 fn is_compatible(&self, new: &Self, rn: &RenameMap, report_self: &mut Report, report_new: &mut Report) -> bool;
37}
38impl<T> IsCompatible for &T
39where
40 T: IsCompatible,
41{
42 fn is_compatible(&self, new: &Self, rn: &RenameMap, report_self: &mut Report, report_new: &mut Report) -> bool {
43 (**self).is_compatible(*new, rn, report_self, report_new)
44 }
45}
46
47pub trait IsIsomorph {
48 fn is_isomorph(&self, other: &Self, rn: &RenameMap, report_self: &mut Report, report_other: &mut Report) -> bool;
49}
50impl<T> IsIsomorph for &T
51where
52 T: IsIsomorph,
53{
54 fn is_isomorph(&self, new: &Self, rn: &RenameMap, report_self: &mut Report, report_other: &mut Report) -> bool {
55 (**self).is_isomorph(*new, rn, report_self, report_other)
56 }
57}
58#[macro_export]
59macro_rules! derive_is_isomorph_by_id_name {
60 ($t:ty) => {
61 impl $crate::IsIsomorph for $t {
62 fn is_isomorph(&self, other: &Self, _rn: &$crate::RenameMap, _report_self: &mut $crate::diagnostics::Report, _report_other: &mut $crate::diagnostics::Report) -> bool {
63 use $crate::HasIdent;
64 HasIdent::id(self).name() == HasIdent::id(other).name()
65 }
66 }
67 };
68}
69
70pub fn mk_change_list<T: RenameExt + Clone + Copy + Debug, V: IsCompatible + IsIsomorph>(
71 rn: &RenameMap,
72 old: &[T],
73 new: &[T],
74 mapper: impl Fn(T) -> V,
75 report_old: &mut Report,
76 report_new: &mut Report,
77) -> ChangeList<T> {
78 let mut out = <ChangeList<T>>::new();
79
80 let mut old_listed = HashSet::new();
81 let mut new_listed = HashSet::new();
82
83 let occupancy = new.iter().map(|n| n.db(rn)).collect::<HashSet<_>>();
85
86 for (oid, old) in old.iter().cloned().enumerate() {
87 let mut new_by_exact = new.iter().cloned().enumerate().filter(|(i, f)| {
88 !new_listed.contains(i)
89 && mapper(*f).is_isomorph(&mapper(old), rn, report_new, report_old)
90 && f.db(rn) == old.db(rn)
91 });
92 if let Some((nid, new)) = new_by_exact.next() {
93 {
94 let other = new_by_exact.next();
95 assert!(
96 other.is_none(),
97 "second exact match shouldn't be possible: {nid} {new:?} {other:?}"
98 );
99 }
100 old_listed.insert(oid);
101 new_listed.insert(nid);
102 out.updated.push(Diff { old, new });
103 continue;
104 }
105 }
106 for (oid, old) in old.iter().cloned().enumerate() {
107 if old_listed.contains(&oid) {
108 continue;
109 }
110 let mut new_by_code =
111 new.iter().cloned().enumerate().filter(|(i, f)| {
112 mapper(*f).is_isomorph(&mapper(old), rn, report_new, report_old) && !new_listed.contains(i)
113 });
114 if let Some((nid, new)) = new_by_code.next() {
115 assert!(new_by_code.next().is_none());
116 old_listed.insert(oid);
117 new_listed.insert(nid);
118 out.updated.push(Diff { old, new });
119 continue;
120 }
121 }
122
123 let mut out_dropped = Vec::new();
124
125 let mut allocator = RenameTempAllocator::default();
126 for (oid, old) in old.iter().cloned().enumerate() {
127 if old_listed.contains(&oid) {
128 continue;
129 }
130 let mut new_by_db = new
131 .iter()
132 .cloned()
133 .enumerate()
134 .filter(|(i, f)| f.db(rn) == old.db(rn) && !new_listed.contains(i));
135 if let Some((nid, new)) = new_by_db.next() {
136 assert!(new_by_db.next().is_none());
137 old_listed.insert(oid);
138 new_listed.insert(nid);
139 out.updated.push(Diff { old, new });
140 continue;
141 }
142 let tmp = if occupancy.contains(&old.db(rn)) {
143 Some(allocator.next_moveaway())
144 } else {
145 None
146 };
147 out_dropped.push((old, tmp));
148 }
149
150 for recreated in out
151 .updated
152 .iter()
153 .filter(|diff| !mapper(diff.old).is_compatible(&mapper(diff.new), rn, report_old, report_new))
154 .collect::<Vec<_>>()
155 {
156 out.created.push(recreated.new);
157 out_dropped.push((recreated.old, Some(allocator.next_moveaway())));
158 }
159 out.updated
160 .retain(|diff| mapper(diff.old).is_compatible(&mapper(diff.new), rn, report_old, report_new));
161
162 for (_, new) in new
163 .iter()
164 .cloned()
165 .enumerate()
166 .filter(|(i, _)| !new_listed.contains(i))
167 {
168 out.created.push(new);
169 }
170
171 let mut to_rename = vec![];
172 for updated in out.updated.iter() {
173 to_rename.push((updated.old, updated.new.db(rn), updated.old));
174 }
175 let mut moveaways = vec![];
176 for old_dropped in out_dropped.iter() {
177 if let Some(tmp) = old_dropped.1 {
178 moveaways.push((old_dropped.0, tmp));
179 } else if new.iter().any(|n| n.db(rn) == old_dropped.0.db(rn)) {
180 moveaways.push((old_dropped.0, allocator.next_moveaway()));
181 }
182 }
183 out.renamed = reorder_renames(rn, to_rename, moveaways.clone(), &mut allocator);
184 out.moved_away = moveaways;
185 out.dropped = out_dropped.into_iter().map(|(v, _)| v).collect_vec();
186
187 out
188}
189
190#[cfg(all(test, any()))]
192mod tests {
193 use crate::{
194 changelist::{ChangeList, IsCompatible},
195 ids::{in_allocator, DbIdent, Ident},
196 mk_change_list,
197 names::{DefName, TypeKind},
198 renamelist::{RenameOp, RenameTempAllocator},
199 span::{register_source, SimpleSpan},
200 uid::{next_uid, HasUid, OwnUid, RenameMap, Uid},
201 HasDefaultDbName, HasIdent,
202 };
203 #[test]
204 fn changelist_conflict() {
205 tracing_subscriber::fmt().init();
206 in_allocator(|| {
207 #[derive(Debug, PartialEq)]
208 struct P(OwnUid, DefName<TypeKind>);
209 derive_is_isomorph_by_id_name!(P);
210 impl IsCompatible for P {
211 fn is_compatible(&self, _new: &Self, _rn: &RenameMap) -> bool {
212 true
213 }
214 }
215 impl HasDefaultDbName for P {
216 type Kind = TypeKind;
217 fn default_db(&self) -> Option<DbIdent<Self::Kind>> {
218 self.1.default_db()
219 }
220 }
221 impl HasIdent for P {
222 type Kind = TypeKind;
223 fn id(&self) -> Ident<Self::Kind> {
224 self.1.id()
225 }
226 }
227 impl HasUid for P {
228 fn uid(&self) -> Uid {
229 self.0.uid()
230 }
231 }
232 fn p(a: &'static str, b: &'static str) -> P {
233 let s = a.to_string();
234 let s = register_source(s);
235 P(
236 next_uid(),
237 DefName::alloc((SimpleSpan::new(s, 0, a.len() as u32), a, Some(b))),
238 )
239 }
240 fn i(n: &'static str) -> DbIdent<TypeKind> {
241 DbIdent::new(n)
242 }
243 macro_rules! diff {
244 ($a:expr, $b:expr) => {
245 crate::Diff { old: $a, new: $b }
246 };
247 }
248 let mut ren = RenameTempAllocator::default();
249 let ren1 = ren.next_temp();
250
251 assert_eq!(
252 mk_change_list(
253 &RenameMap::default(),
254 &[p("A", "D"), p("C", "B")],
255 &[p("C", "D"), p("A", "B")]
256 ),
257 ChangeList {
258 renamed: vec![
259 RenameOp::Store(p("A", "D"), ren1),
260 RenameOp::Rename(p("C", "B"), i("D")),
261 RenameOp::Restore(ren1, i("B"))
262 ],
263 updated: vec![
264 diff!(p("A", "D"), p("A", "B")),
265 diff!(p("C", "B"), p("C", "D"))
266 ],
267
268 created: vec![],
269 dropped: vec![],
270 moved_away: vec![],
271 }
272 );
273 assert_eq!(
274 mk_change_list(
275 &RenameMap::default(),
276 &[p("A", "B")],
277 &[p("A", "C"), p("D", "B")]
278 ),
279 ChangeList {
280 renamed: vec![RenameOp::Rename(p("A", "B"), i("C"))],
281 updated: vec![diff!(p("A", "B"), p("A", "C"))],
282 created: vec![p("D", "B")],
283 moved_away: vec![],
284
285 dropped: vec![],
286 }
287 );
288 assert_eq!(
289 mk_change_list(
290 &RenameMap::default(),
291 &[p("D", "B"), p("A", "C")],
292 &[p("A", "B")]
293 ),
294 ChangeList {
295 renamed: vec![
296 RenameOp::Store(p("D", "B"), ren1),
297 RenameOp::Rename(p("A", "C"), i("B"))
298 ],
299 updated: vec![diff!(p("A", "C"), p("A", "B")),],
300 dropped: vec![p("D", "B")],
301 moved_away: vec![(p("D", "B"), ren1)],
302
303 created: vec![],
304 }
305 );
306 });
307 }
308}