| 1 |
|
| 2 |
|
| 3 |
|
| 4 |
|
| 5 |
|
| 6 |
|
| 7 |
|
| 8 |
|
| 9 |
|
| 10 |
|
| 11 |
|
| 12 |
|
| 13 |
|
| 14 |
|
| 15 |
|
| 16 |
|
| 17 |
|
| 18 |
|
| 19 |
|
| 20 |
|
| 21 |
|
| 22 |
|
| 23 |
|
| 24 |
|
| 25 |
|
| 26 |
|
| 27 |
|
| 28 |
|
| 29 |
|
| 30 |
|
| 31 |
|
| 32 |
|
| 33 |
|
| 34 |
use std::collections::BTreeMap; |
| 35 |
|
| 36 |
use syn::Result; |
| 37 |
|
| 38 |
use crate::ast::{Arg, Emission, Guard, Hole, HoleRoot, Interpolated, Item, Source, Step}; |
| 39 |
|
| 40 |
|
| 41 |
pub fn check(items: &[Item]) -> Result<()> { |
| 42 |
let mut guarded: Vec<(&Guard, &Emission)> = Vec::new(); |
| 43 |
|
| 44 |
for item in items { |
| 45 |
match item { |
| 46 |
Item::Emit(Emission::Guarded { guard, inner }) => guarded.push((guard, inner)), |
| 47 |
Item::For { body, .. } => check(body)?, |
| 48 |
_ => {} |
| 49 |
} |
| 50 |
if let Item::Emit(emission) = item { |
| 51 |
check(bodies(emission))?; |
| 52 |
} |
| 53 |
} |
| 54 |
|
| 55 |
for (at, (guard, inner)) in guarded.iter().enumerate() { |
| 56 |
for (other, later) in guarded.iter().skip(at + 1) { |
| 57 |
if !complementary(guard, other) { |
| 58 |
continue; |
| 59 |
} |
| 60 |
let mine = moved(inner); |
| 61 |
let theirs = moved(later); |
| 62 |
if let Some(name) = mine.keys().find(|name| theirs.contains_key(*name)) { |
| 63 |
let span = mine[name]; |
| 64 |
return Err(syn::Error::new( |
| 65 |
span, |
| 66 |
format!( |
| 67 |
"`{name}` is moved by both arms of a complementary pair. A guarded \ |
| 68 |
member is built whether or not it is placed, so `when` and `unless` \ |
| 69 |
on one predicate move it twice. Say it as one `given` over the same \ |
| 70 |
predicate with `true` and `false` arms, which are exclusive." |
| 71 |
), |
| 72 |
)); |
| 73 |
} |
| 74 |
} |
| 75 |
} |
| 76 |
Ok(()) |
| 77 |
} |
| 78 |
|
| 79 |
|
| 80 |
fn bodies(emission: &Emission) -> &[Item] { |
| 81 |
match emission { |
| 82 |
Emission::Simple { body, .. } |
| 83 |
| Emission::Chip { body, .. } |
| 84 |
| Emission::Screen { body, .. } |
| 85 |
| Emission::Row { body, .. } |
| 86 |
| Emission::Form { body, .. } |
| 87 |
| Emission::Field { body, .. } |
| 88 |
| Emission::Act { body, .. } |
| 89 |
| Emission::Offers { body, .. } |
| 90 |
| Emission::Offering { body, .. } |
| 91 |
| Emission::Removes { body, .. } |
| 92 |
| Emission::Repeats { body, .. } |
| 93 |
| Emission::Column { body, .. } |
| 94 |
| Emission::Cell { body, .. } |
| 95 |
| Emission::Region { body, .. } |
| 96 |
| Emission::Across { body, .. } => body, |
| 97 |
Emission::List(body) | Emission::Table(body) | Emission::Cells(body) => body, |
| 98 |
Emission::Beside { inner, .. } |
| 99 |
| Emission::Framed { inner, .. } |
| 100 |
| Emission::At { inner, .. } => bodies(inner), |
| 101 |
Emission::Guarded { inner, .. } => bodies(inner), |
| 102 |
Emission::Include(_) |
| 103 |
| Emission::IncludeEach(_) |
| 104 |
| Emission::Link { .. } |
| 105 |
| Emission::Activate(_) |
| 106 |
| Emission::Given { .. } => &[], |
| 107 |
} |
| 108 |
} |
| 109 |
|
| 110 |
|
| 111 |
fn complementary(one: &Guard, other: &Guard) -> bool { |
| 112 |
one.negated != other.negated && key(one) == key(other) |
| 113 |
} |
| 114 |
|
| 115 |
|
| 116 |
|
| 117 |
|
| 118 |
|
| 119 |
fn key(guard: &Guard) -> String { |
| 120 |
let bare = Guard { |
| 121 |
negated: false, |
| 122 |
predicate: guard.predicate.clone(), |
| 123 |
span: guard.span, |
| 124 |
}; |
| 125 |
crate::emit::predicate(&bare).map_or_else(|_| String::new(), |tokens| tokens.to_string()) |
| 126 |
} |
| 127 |
|
| 128 |
|
| 129 |
|
| 130 |
|
| 131 |
|
| 132 |
|
| 133 |
fn moved(emission: &Emission) -> BTreeMap<String, proc_macro2::Span> { |
| 134 |
let mut found = BTreeMap::new(); |
| 135 |
walk_emission(emission, &mut found); |
| 136 |
found |
| 137 |
} |
| 138 |
|
| 139 |
fn note(hole: &Hole, found: &mut BTreeMap<String, proc_macro2::Span>) { |
| 140 |
let HoleRoot::Binding(name) = &hole.root else { |
| 141 |
return; |
| 142 |
}; |
| 143 |
if hole |
| 144 |
.steps |
| 145 |
.iter() |
| 146 |
.any(|step| matches!(step, Step::Method { .. })) |
| 147 |
{ |
| 148 |
return; |
| 149 |
} |
| 150 |
found.entry(name.to_string()).or_insert_with(|| name.span()); |
| 151 |
} |
| 152 |
|
| 153 |
fn walk_arg(arg: &Arg, found: &mut BTreeMap<String, proc_macro2::Span>) { |
| 154 |
match arg { |
| 155 |
Arg::Hole(hole) => note(hole, found), |
| 156 |
Arg::Str(text) => walk_str(text, found), |
| 157 |
Arg::List(items) => items.iter().for_each(|item| walk_arg(item, found)), |
| 158 |
|
| 159 |
|
| 160 |
Arg::Borrow(_) => {} |
| 161 |
Arg::Int(_) | Arg::Bool(_) => {} |
| 162 |
} |
| 163 |
} |
| 164 |
|
| 165 |
|
| 166 |
fn walk_str(_text: &Interpolated, _found: &mut BTreeMap<String, proc_macro2::Span>) {} |
| 167 |
|
| 168 |
fn walk_items(items: &[Item], found: &mut BTreeMap<String, proc_macro2::Span>) { |
| 169 |
for item in items { |
| 170 |
match item { |
| 171 |
Item::Bind { source, .. } => { |
| 172 |
if let Source::Hole(hole) = source { |
| 173 |
note(hole, found); |
| 174 |
} |
| 175 |
} |
| 176 |
Item::Attribute { args, .. } => args.iter().for_each(|arg| walk_arg(arg, found)), |
| 177 |
Item::For { body, .. } | Item::Marked { body, .. } => walk_items(body, found), |
| 178 |
Item::Emit(emission) => walk_emission(emission, found), |
| 179 |
} |
| 180 |
} |
| 181 |
} |
| 182 |
|
| 183 |
fn walk_emission(emission: &Emission, found: &mut BTreeMap<String, proc_macro2::Span>) { |
| 184 |
match emission { |
| 185 |
Emission::Simple { args, .. } | Emission::Screen { args, .. } => { |
| 186 |
for arg in args { |
| 187 |
walk_arg(arg, found); |
| 188 |
} |
| 189 |
} |
| 190 |
Emission::Cell { value, .. } => walk_arg(value, found), |
| 191 |
Emission::Row { primary, .. } => walk_arg(primary, found), |
| 192 |
Emission::Column { name, .. } => walk_arg(name, found), |
| 193 |
Emission::Chip { value, .. } => walk_arg(value, found), |
| 194 |
Emission::Act { label, .. } |
| 195 |
| Emission::Offers { label, .. } |
| 196 |
| Emission::Offering { label, .. } |
| 197 |
| Emission::Removes { label, .. } => walk_arg(label, found), |
| 198 |
Emission::Include(hole) | Emission::IncludeEach(hole) => note(hole, found), |
| 199 |
Emission::Beside { inner, .. } |
| 200 |
| Emission::Framed { inner, .. } |
| 201 |
| Emission::At { inner, .. } => { |
| 202 |
walk_emission(inner, found); |
| 203 |
} |
| 204 |
Emission::Guarded { inner, .. } => walk_emission(inner, found), |
| 205 |
_ => {} |
| 206 |
} |
| 207 |
walk_items(bodies(emission), found); |
| 208 |
} |
| 209 |
|