1use std::collections::HashMap;
12
13use super::Language;
14use super::diagnostics::{Result, type_error, unsupported};
15use super::ir::{Binder, Ctor, Expr, Field, Hints, Node, Plan, PlanCase, Proof, Prop, Split};
16use super::surface::{SProof, SSplit, SStep};
17use super::types::Type;
18
19#[derive(Clone, Debug, PartialEq, Eq)]
21pub enum ProofName {
22 Function(String),
23 Theorem(String),
24 Other,
25}
26
27pub trait ProofContext {
29 fn lookup(&self, name: &str) -> Result<Option<ProofName>>;
34
35 fn ctors(&self, data: &str) -> Vec<Ctor>;
37}
38
39pub struct TheoremHead<'a> {
41 pub full_name: &'a str,
42 pub binders: &'a [Binder],
43}
44
45pub fn normalise_proof(
50 proof: &SProof,
51 theorem: &TheoremHead<'_>,
52 context: &dyn ProofContext,
53) -> Result<Proof> {
54 let mut hints = Hints::default();
55 let locals: HashMap<String, Type> = theorem
56 .binders
57 .iter()
58 .map(|binder| (binder.name.clone(), binder.ty.clone()))
59 .collect();
60 let normaliser = Normaliser {
61 theorem,
62 context,
63 language: proof.source_language,
64 };
65 let plan = normaliser.sequence(&proof.steps, &mut hints, &locals)?;
66 Ok(Proof {
67 plan,
68 source: proof.source.clone().unwrap_or_default(),
69 source_language: proof.source_language,
70 })
71}
72
73fn merge_hints(into: &mut Hints, from: &Hints) {
74 let merge = |into: &mut Vec<String>, from: &[String]| {
75 for value in from {
76 if !into.contains(value) {
77 into.push(value.clone());
78 }
79 }
80 };
81 merge(&mut into.unfold, &from.unfold);
82 merge(&mut into.lemmas, &from.lemmas);
83 merge(&mut into.hyps, &from.hyps);
84 merge(&mut into.library, &from.library);
85 into.arith |= from.arith;
86 into.compute |= from.compute;
87}
88
89struct Normaliser<'a> {
90 theorem: &'a TheoremHead<'a>,
91 context: &'a dyn ProofContext,
92 language: Language,
93}
94
95impl Normaliser<'_> {
96 fn sequence(
97 &self,
98 steps: &[SStep],
99 hints: &mut Hints,
100 locals: &HashMap<String, Type>,
101 ) -> Result<Plan> {
102 for (index, step) in steps.iter().enumerate() {
103 match step {
104 SStep::Intro { names } => {
105 if names.iter().any(|name| !locals.contains_key(name)) {
108 return Err(unsupported(
109 "intro",
110 "introducing hypotheses beyond the theorem binders is outside the portable proof model",
111 None,
112 ));
113 }
114 }
115 SStep::Unfold { names, .. } => {
116 for name in names {
117 self.add_rule(hints, name, locals, false)?;
118 }
119 }
120 SStep::Rewrite { rules, .. } => {
121 for rule in rules {
122 self.add_rule(hints, &rule.name, locals, rule.reverse)?;
123 }
124 }
125 SStep::Simp { rules, .. } => {
126 for rule in rules {
127 self.add_rule(hints, &rule.name, locals, rule.reverse)?;
128 }
129 hints.compute = true;
130 }
131 SStep::Compute { .. } => hints.compute = true,
132 SStep::Arith { .. } => hints.arith = true,
133 SStep::Induction(split) => {
134 return self.split(split, true, &steps[index + 1..], hints, locals);
135 }
136 SStep::Cases(split) => {
137 return self.split(split, false, &steps[index + 1..], hints, locals);
138 }
139 }
140 }
141 Ok(Plan::Close {
142 hints: hints.clone(),
143 })
144 }
145
146 fn add_rule(
147 &self,
148 hints: &mut Hints,
149 name: &str,
150 locals: &HashMap<String, Type>,
151 reverse: bool,
152 ) -> Result<()> {
153 let push = |list: &mut Vec<String>, value: &str| {
154 if !list.iter().any(|item| item == value) {
155 list.push(value.to_owned());
156 }
157 };
158 if locals.contains_key(name) {
159 push(&mut hints.hyps, name);
160 return Ok(());
161 }
162 match self.context.lookup(name)? {
163 Some(ProofName::Function(full_name)) => {
164 push(&mut hints.unfold, &full_name);
165 return Ok(());
166 }
167 Some(ProofName::Theorem(full_name)) => {
168 if full_name == self.theorem.full_name {
169 return Err(type_error(format!("{name} uses itself"), None));
170 }
171 push(&mut hints.lemmas, &full_name);
172 return Ok(());
173 }
174 _ => {}
175 }
176 if !hints.library.iter().any(|item| item == name) {
180 hints.library.push(if reverse {
181 format!("<-{name}")
182 } else {
183 name.to_owned()
184 });
185 }
186 hints.arith = true;
187 Ok(())
188 }
189
190 fn split(
191 &self,
192 step: &SSplit,
193 induction: bool,
194 trailing: &[SStep],
195 hints: &Hints,
196 locals: &HashMap<String, Type>,
197 ) -> Result<Plan> {
198 let tactic = if induction { "induction" } else { "cases" };
199 let Some(ty) = locals.get(&step.variable) else {
200 return Err(unsupported(
201 &format!("{tactic} on {}", step.variable),
202 "only theorem binders can be split",
203 None,
204 ));
205 };
206 let Some(ctors) = self.constructors_of(ty) else {
207 return Err(unsupported(
208 &format!("{tactic} on {}", ty.key()),
209 "only natural numbers and data types can be split",
210 None,
211 ));
212 };
213 if step.positional && step.cases.len() > ctors.len() {
216 return Err(type_error(
217 format!(
218 "{tactic} on {} has {} cases but {} has {} constructors",
219 step.variable,
220 step.cases.len(),
221 ty.key(),
222 ctors.len()
223 ),
224 None,
225 ));
226 }
227 let mut cases = Vec::new();
228 for (ctor_index, ctor) in ctors.iter().enumerate() {
229 let written = if step.positional {
230 step.cases.get(ctor_index)
231 } else {
232 step.cases
233 .iter()
234 .find(|kase| canonical_ctor(kase.ctor.as_deref(), ty) == ctor.name)
235 };
236 let recursive: Vec<bool> = ctor
237 .fields
238 .iter()
239 .map(|field| field.ty.key() == ty.key())
240 .collect();
241 let binds = written.map_or(&[][..], |kase| &kase.binds[..]);
242 let (fields, ihs) = assign_binds(binds, ctor, &recursive, induction, self.language)?;
243 let mut inner = locals.clone();
244 for (field, name) in ctor.fields.iter().zip(&fields) {
245 inner.insert(name.clone(), field.ty.clone());
246 }
247 for ih in &ihs {
248 inner.insert(ih.clone(), Type::Hypothesis);
249 }
250 let mut case_hints = Hints::default();
251 merge_hints(&mut case_hints, hints);
252 let mut steps: Vec<SStep> = written.map(|kase| kase.steps.clone()).unwrap_or_default();
253 steps.extend(trailing.iter().cloned());
254 let plan = self.sequence(&steps, &mut case_hints, &inner)?;
255 cases.push(PlanCase {
256 ctor: ctor.name.clone(),
257 fields,
258 ihs,
259 recursive,
260 plan,
261 });
262 }
263 if !step.positional {
264 for kase in &step.cases {
265 let name = canonical_ctor(kase.ctor.as_deref(), ty);
266 if !ctors.iter().any(|ctor| ctor.name == name) {
267 return Err(type_error(
268 format!(
269 "{} has no constructor {}",
270 ty.key(),
271 kase.ctor.as_deref().unwrap_or("undefined")
272 ),
273 None,
274 ));
275 }
276 }
277 }
278 let split = Split {
279 variable: step.variable.clone(),
280 ty: ty.clone(),
281 cases,
282 };
283 Ok(if induction {
284 Plan::Induction(split)
285 } else {
286 Plan::Cases(split)
287 })
288 }
289
290 fn constructors_of(&self, ty: &Type) -> Option<Vec<Ctor>> {
291 match ty {
292 Type::Nat => Some(vec![
293 Ctor {
294 name: "zero".to_owned(),
295 fields: Vec::new(),
296 },
297 Ctor {
298 name: "succ".to_owned(),
299 fields: vec![Field {
300 name: "pred".to_owned(),
301 ty: ty.clone(),
302 }],
303 },
304 ]),
305 Type::Data { name } => Some(self.context.ctors(name)),
306 _ => None,
307 }
308 }
309}
310
311fn canonical_ctor(name: Option<&str>, ty: &Type) -> String {
312 let name = name.unwrap_or("undefined");
313 if matches!(ty, Type::Nat) {
314 return match name {
315 "zero" | "O" | "Nat.zero" => "zero",
316 "succ" | "S" | "Nat.succ" => "succ",
317 other => other,
318 }
319 .to_owned();
320 }
321 name.rsplit('.').next().unwrap_or(name).to_owned()
322}
323
324fn assign_binds(
327 binds: &[Option<String>],
328 ctor: &Ctor,
329 recursive: &[bool],
330 induction: bool,
331 language: Language,
332) -> Result<(Vec<String>, Vec<String>)> {
333 let mut fields = Vec::new();
334 let mut ihs = Vec::new();
335 let mut cursor = 0;
336 let mut fresh = 0;
337 let mut take = |prefix: &str| {
338 fresh += 1;
339 let fallback = format!("ml_{prefix}{fresh}");
340 let name = binds.get(cursor).cloned().flatten();
341 cursor += 1;
342 match name {
343 Some(name) if !name.is_empty() && name != "_" => name,
344 _ => fallback,
345 }
346 };
347 if language == Language::Rocq {
348 for &is_recursive in recursive.iter().take(ctor.fields.len()) {
349 fields.push(take("x"));
350 if induction && is_recursive {
351 ihs.push(take("ih"));
352 }
353 }
354 } else {
355 for _ in &ctor.fields {
356 fields.push(take("x"));
357 }
358 if induction {
359 for &is_recursive in recursive {
360 if is_recursive {
361 ihs.push(take("ih"));
362 }
363 }
364 }
365 }
366 if cursor < binds.len() {
367 return Err(type_error(
368 format!("{} case names {} variables", ctor.name, binds.len()),
369 None,
370 ));
371 }
372 Ok((fields, ihs))
373}
374
375#[must_use]
377pub fn prop_functions(prop: &Prop, mut into: Vec<String>) -> Vec<String> {
378 fn visit(expr: &Expr, into: &mut Vec<String>) {
379 if let Node::Call { func, .. } = &expr.node
380 && !into.contains(func)
381 {
382 into.push(func.clone());
383 }
384 for child in expr.children() {
385 visit(child, into);
386 }
387 }
388 fn visit_prop(prop: &Prop, into: &mut Vec<String>) {
389 match prop {
390 Prop::Forall { body, .. } => visit_prop(body, into),
391 Prop::And { left, right }
392 | Prop::Or { left, right }
393 | Prop::Implies { left, right } => {
394 visit_prop(left, into);
395 visit_prop(right, into);
396 }
397 Prop::Not { arg } => visit_prop(arg, into),
398 Prop::Bool { expr } => visit(expr, into),
399 other => {
400 if let Some((_, comparison)) = other.comparison() {
401 visit(&comparison.left, into);
402 visit(&comparison.right, into);
403 }
404 }
405 }
406 }
407 visit_prop(prop, &mut into);
408 into
409}