1use std::fmt;
31
32use crate::context::{Context, StructInfo};
33use crate::term::{Term, Universe};
34
35#[derive(Debug, Clone, PartialEq, Eq)]
37pub enum StructureRefusal {
38 NotAnInductive { inductive: String },
40 AlreadyAStructure { inductive: String },
42 Sealed { inductive: String },
44 NotStructureShaped {
46 inductive: String,
47 constructors: usize,
48 indices: usize,
49 },
50 ShortConstructor { constructor: String, params: usize },
52 Recursive { inductive: String },
54 ProjectionCount {
56 inductive: String,
57 fields: usize,
58 projections: usize,
59 },
60 NotADefinition { projection: String },
62 UniverseArity {
64 projection: String,
65 inductive: String,
66 expected: usize,
67 found: usize,
68 },
69 DoesNotCompute {
71 projection: String,
72 field: usize,
73 inductive: String,
74 },
75 WrongFieldType {
77 projection: String,
78 field: usize,
79 inductive: String,
80 },
81}
82
83impl fmt::Display for StructureRefusal {
84 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
85 match self {
86 StructureRefusal::NotAnInductive { inductive } => {
87 write!(f, "structure η names `{inductive}`, which is not a declared inductive")
88 }
89 StructureRefusal::AlreadyAStructure { inductive } => {
90 write!(f, "`{inductive}` already has structure η")
91 }
92 StructureRefusal::Sealed { inductive } => write!(
93 f,
94 "`{inductive}` is sealed by a native nested positivity check that depends on it, so it cannot gain structure η"
95 ),
96 StructureRefusal::NotStructureShaped { inductive, constructors, indices } => write!(
97 f,
98 "`{inductive}` is not a structure: it has {constructors} constructors and {indices} indices, \
99 where a structure has exactly one constructor and no indices"
100 ),
101 StructureRefusal::ShortConstructor { constructor, params } => {
102 write!(f, "`{constructor}` binds fewer than its inductive's {params} parameters")
103 }
104 StructureRefusal::Recursive { inductive } => {
105 write!(f, "`{inductive}` is recursive, and structure η is for structures")
106 }
107 StructureRefusal::ProjectionCount { inductive, fields, projections } => {
108 write!(f, "`{inductive}` has {fields} fields but names {projections} projections")
109 }
110 StructureRefusal::NotADefinition { projection } => {
111 write!(f, "`{projection}` is not a transparent definition")
112 }
113 StructureRefusal::UniverseArity { projection, inductive, expected, found } => write!(
114 f,
115 "`{projection}` binds {found} universe parameters, not `{inductive}`'s {expected}"
116 ),
117 StructureRefusal::WrongFieldType { projection, field, inductive } => write!(
118 f,
119 "`{projection}` applied to a constructor of `{inductive}` does not have the type of field {field}"
120 ),
121 StructureRefusal::DoesNotCompute { projection, field, inductive } => {
122 write!(f, "`{projection}` does not compute field {field} of `{inductive}`")
123 }
124 }
125 }
126}
127
128impl std::error::Error for StructureRefusal {}
129
130impl Context {
131 pub fn register_structure_checked(
136 &mut self,
137 inductive: &str,
138 projections: &[String],
139 ) -> Result<(), StructureRefusal> {
140 let info = self.structure_eta_evidence(inductive, projections)?;
141 self.insert_struct_info(inductive, info);
142 Ok(())
143 }
144
145 pub(crate) fn block_is_recursive(&self, inductive: &str) -> bool {
151 let block: Vec<&str> = match self.mutual_block_of(inductive) {
152 Some(members) => members.iter().map(String::as_str).collect(),
153 None => vec![inductive],
154 };
155 block.iter().any(|member| {
156 self.get_constructors(member).into_iter().any(|(_, ty)| {
157 let mut rest = ty;
158 while let Term::Pi {
159 param_type,
160 body_type,
161 ..
162 } = rest
163 {
164 if crate::positivity::mentions_inductive_block(self, &block, param_type) {
165 return true;
166 }
167 rest = body_type;
168 }
169 false
170 })
171 })
172 }
173
174 pub(crate) fn structure_eta_evidence(
177 &self,
178 inductive: &str,
179 projections: &[String],
180 ) -> Result<StructInfo, StructureRefusal> {
181 let named = || inductive.to_string();
182 if !self.is_inductive(inductive) {
183 return Err(StructureRefusal::NotAnInductive { inductive: named() });
184 }
185 if self.struct_info(inductive).is_some() {
186 return Err(StructureRefusal::AlreadyAStructure { inductive: named() });
187 }
188 if self.check_native_dependency_mutation(inductive).is_err() {
189 return Err(StructureRefusal::Sealed { inductive: named() });
190 }
191 let constructors = self.get_constructors(inductive);
192 let indices = self.inductive_num_indices(inductive);
193 if constructors.len() != 1 || indices != 0 {
194 return Err(StructureRefusal::NotStructureShaped {
195 inductive: named(),
196 constructors: constructors.len(),
197 indices,
198 });
199 }
200 let (mk, mk_ty) = (constructors[0].0.to_string(), constructors[0].1.clone());
201 let levels: Vec<Universe> = self
202 .poly_declaration(inductive)
203 .map(|(params, _)| params.iter().cloned().map(Universe::Var).collect())
204 .unwrap_or_default();
205 let constant = |name: &str| {
206 if levels.is_empty() {
207 Term::Global(name.to_string())
208 } else {
209 Term::Const {
210 name: name.to_string(),
211 levels: levels.clone(),
212 }
213 }
214 };
215
216 let num_params = self.inductive_num_params(inductive);
217 let mut params = Vec::with_capacity(num_params);
218 let mut rest = mk_ty;
219 for i in 0..num_params {
220 let Term::Pi {
221 param,
222 param_type,
223 body_type,
224 } = rest
225 else {
226 return Err(StructureRefusal::ShortConstructor {
227 constructor: mk,
228 params: num_params,
229 });
230 };
231 let fresh = format!("structure.parameter.{i}");
232 rest = crate::substitute(&body_type, ¶m, &Term::Var(fresh.clone()));
233 params.push((fresh, *param_type));
234 }
235 let mut fields = Vec::new();
236 while let Term::Pi {
237 param,
238 param_type,
239 body_type,
240 } = rest
241 {
242 let fresh = format!("structure.field.{}", fields.len());
243 rest = crate::substitute(&body_type, ¶m, &Term::Var(fresh.clone()));
244 fields.push((fresh, *param_type));
245 }
246
247 if self.block_is_recursive(inductive) {
248 return Err(StructureRefusal::Recursive { inductive: named() });
249 }
250 if projections.len() != fields.len() {
251 return Err(StructureRefusal::ProjectionCount {
252 inductive: named(),
253 fields: fields.len(),
254 projections: projections.len(),
255 });
256 }
257
258 let mut local = self.clone();
259 for (name, ty) in params.iter().chain(fields.iter()) {
260 local = local.extend(name, ty.clone());
261 }
262 let apply_all = |head: Term, args: &mut dyn Iterator<Item = Term>| {
263 args.fold(head, |f, a| Term::App(Box::new(f), Box::new(a)))
264 };
265 let param_terms: Vec<Term> = params.iter().map(|(n, _)| Term::Var(n.clone())).collect();
266 let value = apply_all(
267 constant(&mk),
268 &mut param_terms
269 .iter()
270 .cloned()
271 .chain(fields.iter().map(|(n, _)| Term::Var(n.clone()))),
272 );
273 for (i, projection) in projections.iter().enumerate() {
274 let transparent = self.get_definition_body(projection).is_some()
275 || self.get_universe_poly(projection).is_some();
276 if !transparent {
277 return Err(StructureRefusal::NotADefinition {
278 projection: projection.clone(),
279 });
280 }
281 let arity = self
282 .poly_declaration(projection)
283 .map(|(p, _)| p.len())
284 .unwrap_or(0);
285 if arity != levels.len() {
286 return Err(StructureRefusal::UniverseArity {
287 projection: projection.clone(),
288 inductive: named(),
289 expected: levels.len(),
290 found: arity,
291 });
292 }
293 let applied = apply_all(
294 constant(projection),
295 &mut param_terms.iter().cloned().chain([value.clone()]),
296 );
297 if !crate::type_checker::def_eq(&local, &applied, &Term::Var(fields[i].0.clone())) {
298 return Err(StructureRefusal::DoesNotCompute {
299 projection: projection.clone(),
300 field: i,
301 inductive: named(),
302 });
303 }
304 let typed = crate::infer_type(&local, &applied)
305 .is_ok_and(|ty| crate::type_checker::def_eq(&local, &ty, &fields[i].1));
306 if !typed {
307 return Err(StructureRefusal::WrongFieldType {
308 projection: projection.clone(),
309 field: i,
310 inductive: named(),
311 });
312 }
313 }
314 Ok(StructInfo {
315 mk,
316 num_params,
317 projections: projections.to_vec(),
318 params,
319 fields,
320 parents: Vec::new(),
321 out_params: Vec::new(),
322 })
323 }
324}