1use std::collections::{BTreeMap, HashSet};
3use std::ops::Range;
4use std::path::PathBuf;
5
6use zincite_lint::{
7 BindingFacts, BindingResolution, DeclarationId, DeclarationRole, Domain, DomainFacts, EditPart,
8 ExpressionType, ModelContext, SourceLocation, TextEdit, TypeInst, TypeKind, resolve_bindings,
9 resolve_domains, resolve_source_callables,
10};
11use zincite_syntax::{FileMode, NodeKind, ParsedFile, SyntaxNode, TokenKind};
12
13use crate::language::Retention;
14use crate::structured::{self, ArrayIndexing, Key};
15use crate::{ErrorLocation, Input, Limits, QueryError, Work, assignment_rhs, identifier_identity};
16
17#[derive(Debug)]
18pub struct UnresolvedDependency {
19 pub location: SourceLocation,
20 pub message: String,
21}
22
23#[derive(Debug)]
25pub struct ReductionResult {
26 candidate: Box<Input>,
27 unresolved: Vec<UnresolvedDependency>,
28}
29
30impl ReductionResult {
31 pub fn candidate(&self) -> &Input {
32 &self.candidate
33 }
34 pub fn unresolved(&self) -> &[UnresolvedDependency] {
35 &self.unresolved
36 }
37 pub fn is_complete(&self) -> bool {
38 self.unresolved.is_empty()
39 }
40}
41
42pub(crate) struct Shape {
45 pub(crate) ty: TypeInst,
46 pub(crate) indices: Vec<Domain>,
47 pub(crate) components: Vec<(Option<String>, Shape)>,
48}
49
50impl Shape {
51 pub(crate) fn contains_enum(&self, target: Option<DeclarationId>) -> bool {
52 target.is_some_and(|target| match &self.ty.kind {
53 TypeKind::Enum(id) => *id == target,
54 TypeKind::Array { indices, .. } => {
55 indices.iter().any(|ty| ty.kind == TypeKind::Enum(target))
56 }
57 _ => false,
58 }) || self
59 .components
60 .iter()
61 .any(|(_, shape)| shape.contains_enum(target))
62 }
63}
64
65fn declaration_node<'a>(
66 model: &'a ModelContext,
67 bindings: &BindingFacts,
68 id: DeclarationId,
69) -> Option<&'a SyntaxNode> {
70 let declaration = &bindings.declarations[id.0];
71 model.files[declaration.file]
72 .parsed
73 .tree()
74 .child_nodes()
75 .nth(declaration.item)
76}
77
78struct ShapeReader<'a, 'w, 'b> {
79 model: &'a ModelContext,
80 bindings: &'a BindingFacts,
81 domains: &'a DomainFacts,
82 work: &'w mut Work<'b>,
83 range: &'w Range<usize>,
84 limits: Limits,
85}
86
87impl ShapeReader<'_, '_, '_> {
88 fn shape(
89 &mut self,
90 ty: &TypeInst,
91 file: usize,
92 node: Option<&SyntaxNode>,
93 depth: usize,
94 ) -> Result<Shape, QueryError> {
95 let model = self.model;
96 let bindings = self.bindings;
97 let domains = self.domains;
98 self.work.visit(self.range)?;
99 if depth >= self.limits.nesting.min(64) {
100 return Err(QueryError::query(
101 self.range.clone(),
102 "model shape nesting limit exceeded",
103 ));
104 }
105 let mut shape = Shape {
106 ty: ty.clone(),
107 indices: Vec::new(),
108 components: Vec::new(),
109 };
110 if let Some(node) = node
111 && node.kind() == NodeKind::DomainType
112 {
113 let location = model.files[file].location(node.range());
114 self.work.spend(bindings.references.len(), self.range)?;
115 if let Some(id) = bindings.references.iter().find_map(|reference| {
116 if reference.file == file
117 && location.range.start <= reference.location.range.start
118 && reference.location.range.end <= location.range.end
119 && let BindingResolution::Resolved(id) = reference.resolution
120 {
121 return (bindings.declarations[id.0].role == DeclarationRole::TypeAlias)
122 .then_some(id);
123 }
124 None
125 }) {
126 let alias = &bindings.declarations[id.0];
127 let node = declaration_node(model, bindings, id).and_then(|node| {
128 node.child_nodes()
129 .find(|node| node.kind() != NodeKind::Annotation)
130 });
131 return self.shape(ty, alias.file, node, depth + 1);
132 }
133 }
134 if let Some(node) = node {
135 self.work.spend(node.children().len(), self.range)?;
136 if node.child_nodes().count() > self.limits.collection {
137 return Err(QueryError::query(
138 self.range.clone(),
139 "query collection limit exceeded",
140 ));
141 }
142 }
143 let children: Vec<_> = node.into_iter().flat_map(SyntaxNode::child_nodes).collect();
144 match &ty.kind {
145 TypeKind::Array { indices, element } => {
146 for (axis, _) in indices.iter().enumerate() {
147 self.work.spend(domains.array_indices.len(), self.range)?;
148 let domain = children
149 .get(axis)
150 .and_then(|node| {
151 let location = model.files[file].location(node.range());
152 domains.array_indices.iter().find(|index| {
153 index.file == file && index.location.range == location.range
154 })
155 })
156 .map_or(Domain::Unknown, |index| index.domain.clone());
157 shape.indices.push(domain);
158 }
159 shape.components.push((
160 None,
161 self.shape(element, file, children.last().copied(), depth + 1)?,
162 ));
163 }
164 TypeKind::Record(fields) => {
165 for (name, ty) in fields {
166 self.work.spend(
167 children.iter().map(|node| node.children().len()).sum(),
168 self.range,
169 )?;
170 let field = children.iter().copied().find(|field| {
171 let source = &model.files[file].parsed;
172 field
173 .children()
174 .iter()
175 .filter_map(|child| match child {
176 zincite_syntax::SyntaxElement::Token(index) => {
177 Some(&source.tokens()[*index])
178 }
179 _ => None,
180 })
181 .any(|token| {
182 matches!(
183 token.kind,
184 TokenKind::Identifier | TokenKind::QuotedIdentifier
185 ) && identifier_identity(&source.source()[token.range.clone()])
186 == name
187 })
188 });
189 shape.components.push((
190 Some(name.clone()),
191 self.shape(
192 ty,
193 file,
194 field.and_then(|field| field.child_nodes().next()),
195 depth + 1,
196 )?,
197 ));
198 }
199 }
200 TypeKind::Tuple(fields) => {
201 for (index, ty) in fields.iter().enumerate() {
202 shape.components.push((
203 None,
204 self.shape(ty, file, children.get(index).copied(), depth + 1)?,
205 ));
206 }
207 }
208 TypeKind::Set(element) => shape.components.push((
209 None,
210 self.shape(element, file, children.last().copied(), depth + 1)?,
211 )),
212 _ => {}
213 }
214 Ok(shape)
215 }
216}
217pub(crate) fn reduce(
218 input: &Input,
219 model: Option<&ModelContext>,
220 name: &str,
221 retention: &Retention,
222 range: &Range<usize>,
223 work: &mut Work,
224 limits: Limits,
225) -> Result<ReductionResult, QueryError> {
226 if input.mode != FileMode::Data {
227 return Err(QueryError::query(
228 range.clone(),
229 "reduce_enum requires data mode",
230 ));
231 }
232 let name = identifier_identity(name);
233 let items = crate::collect_items(input, limits.collection, work, range, true)?;
234 let targets: Vec<_> = items
235 .iter()
236 .filter(|item| item.name == Some(name))
237 .collect();
238 if targets.len() != 1 {
239 return Err(QueryError::input(
240 targets.first().map_or(0..0, |item| item.range.clone()),
241 "reduce_enum requires one named enum assignment",
242 ));
243 }
244 let target = targets[0];
245 let rhs = assignment_rhs(target.node).unwrap();
246 let members_node = structured::unwrapped(rhs);
247 if members_node.kind() != NodeKind::SetLiteral {
248 return Err(QueryError::input(
249 input.original_range(rhs.range()),
250 "enum reduction requires an explicit member set; constructed and anonymous enums are unsupported",
251 ));
252 }
253 work.spend(members_node.children().len(), range)?;
254 if members_node.child_nodes().count() > limits.collection {
255 return Err(QueryError::query(
256 range.clone(),
257 "query collection limit exceeded",
258 ));
259 }
260 let member_nodes: Vec<_> = members_node.child_nodes().collect();
261 let mut members = Vec::new();
262 let mut identities = HashSet::new();
263 for node in &member_nodes {
264 crate::inspection::check_collection(members.len(), limits.collection, range)?;
265 work.visit(range)?;
266 if let Some(member) = member(input, node) {
267 if !identities.insert(member) {
268 return Err(QueryError::input(
269 input.original_range(node.range()),
270 "enum member identities must be unique",
271 ));
272 }
273 members.push(member);
274 } else {
275 return Err(QueryError::input(
276 input.original_range(node.range()),
277 "enum reduction requires named explicit members",
278 ));
279 }
280 }
281 let retained = match retention {
282 Retention::First(count) => (0..members.len())
283 .map(|index| index < *count)
284 .collect::<Vec<_>>(),
285 Retention::Keep(names) => {
286 let mut requested_members = HashSet::new();
287 for (requested, requested_range) in names {
288 work.visit(range)?;
289 requested_members.insert(identifier_identity(requested));
290 if !identities.contains(identifier_identity(requested)) {
291 return Err(QueryError::query(
292 requested_range.clone(),
293 format!("unknown requested enum member '{requested}'"),
294 ));
295 }
296 }
297 members
298 .iter()
299 .map(|member| requested_members.contains(member))
300 .collect()
301 }
302 };
303 if let Some(error) = model.and_then(|model| model.errors.first()) {
304 return Err(QueryError {
305 location: ErrorLocation::Model(error.location.clone()),
306 range: error.location.range.clone(),
307 message: error.message.clone(),
308 });
309 }
310 if retained.iter().all(|keep| *keep) {
311 return Ok(ReductionResult {
312 candidate: Box::new(crate::finish_candidate(input, &[], &[], work, range)?),
313 unresolved: Vec::new(),
314 });
315 }
316 let removed: HashSet<_> = members
317 .iter()
318 .zip(&retained)
319 .filter_map(|(member, keep)| (!keep).then_some(*member))
320 .collect();
321 let mut dependent_names = HashSet::new();
322 if model.is_none() {
323 for item in &items {
324 if item.range == target.range {
325 continue;
326 }
327 if let Some(name) = item.name
328 && target_keyed(input, item.node, &identities, work, range, limits)?
329 {
330 dependent_names.insert(name.to_owned());
331 }
332 }
333 }
334 let mut reducer = Reducer {
335 input,
336 name,
337 members,
338 removed,
339 target: None,
340 model_supplied: model.is_some(),
341 full_domain_names: HashSet::new(),
342 shapes: BTreeMap::new(),
343 assignments: BTreeMap::new(),
344 array_keys: BTreeMap::new(),
345 groups: BTreeMap::new(),
346 changed_declarations: HashSet::new(),
347 discovering: false,
348 dependent_names,
349 unresolved: Vec::new(),
350 work,
351 range,
352 limits,
353 visited: 0,
354 };
355 let mut model_facts = None;
356 if let Some(model) = model {
357 reducer.work.spend(
358 model
359 .files
360 .iter()
361 .map(|file| file.parsed.tokens().len())
362 .sum(),
363 range,
364 )?;
365 let bindings = resolve_bindings(model);
366 let calls = resolve_source_callables(model, &bindings);
367 let domains = resolve_domains(model, &bindings);
368 reducer.full_domain_names = bindings
369 .declarations
370 .iter()
371 .filter(|declaration| {
372 matches!(
373 declaration.role,
374 DeclarationRole::Enum | DeclarationRole::TypeAlias
375 )
376 })
377 .map(|declaration| declaration.id.0)
378 .collect();
379 let types: BTreeMap<_, _> = calls
380 .declarations
381 .iter()
382 .map(|ty| (ty.declaration.0, &ty.ty))
383 .collect();
384 let mut named_declarations = BTreeMap::<&str, Vec<_>>::new();
385 for declaration in &bindings.declarations {
386 reducer.work.visit(range)?;
387 if declaration.top_level
388 && matches!(
389 declaration.role,
390 DeclarationRole::Value | DeclarationRole::Enum
391 )
392 {
393 named_declarations
394 .entry(&declaration.name)
395 .or_default()
396 .push(declaration);
397 }
398 }
399 let enums: Vec<_> = bindings
400 .declarations
401 .iter()
402 .filter(|declaration| {
403 declaration.top_level
404 && declaration.name == name
405 && declaration.role == DeclarationRole::Enum
406 })
407 .collect();
408 reducer.target = (enums.len() == 1).then(|| enums[0].id);
409 if reducer.target.is_none() {
410 reducer.unresolved(rhs, "model enum association is unavailable or ambiguous");
411 }
412 for item in &items {
413 let Some(name) = item.name else {
414 continue;
415 };
416 let Some(declarations) = named_declarations.get(name) else {
417 continue;
418 };
419 if declarations.len() != 1 {
420 continue;
421 }
422 let declaration = declarations[0];
423 reducer.work.spend(items.len(), range)?;
424 if items
425 .iter()
426 .filter(|other| other.name == Some(name))
427 .count()
428 != 1
429 {
430 continue;
431 }
432 reducer.assignments.insert(
433 name.to_owned(),
434 (declaration.id, assignment_rhs(item.node).unwrap()),
435 );
436 if let Some(ty) = types.get(&declaration.id.0) {
437 let node = declaration_node(model, &bindings, declaration.id).and_then(|node| {
438 node.child_nodes()
439 .find(|node| node.kind() != NodeKind::Annotation)
440 });
441 let shape = ShapeReader {
442 model,
443 bindings: &bindings,
444 domains: &domains,
445 work: reducer.work,
446 range,
447 limits,
448 }
449 .shape(ty, declaration.file, node, 0)?;
450 if shape.contains_enum(reducer.target) {
451 reducer.dependent_names.insert(name.to_owned());
452 }
453 reducer.shapes.insert(name.to_owned(), shape);
454 }
455 }
456 for reference in &bindings.references {
457 reducer.work.visit(range)?;
458 if !reducer.removed.contains(reference.name.as_str()) {
459 continue;
460 }
461 let relevant = match reference.resolution {
462 BindingResolution::Resolved(id) => types.get(&id.0).is_some_and(
463 |ty| matches!(ty.kind, TypeKind::Enum(owner) if Some(owner) == reducer.target),
464 ),
465 BindingResolution::Unresolved | BindingResolution::Ambiguous(_) => true,
466 BindingResolution::Overloads(_) => false,
467 };
468 if relevant {
469 reducer.unresolved.push(UnresolvedDependency {
470 location: reference.location.clone(),
471 message: format!(
472 "removed enum member '{}' remains in the read-only model",
473 reference.name
474 ),
475 });
476 }
477 }
478 model_facts = Some((bindings, calls));
479 }
480 let mut edits = vec![structured::collection_edit(
481 input,
482 members_node,
483 &member_nodes,
484 &retained,
485 TokenKind::Comma,
486 reducer.work,
487 range,
488 )?];
489 let shapes = std::mem::take(&mut reducer.shapes);
492 reducer.discovering = true;
495 for item in &items {
496 if item.range == target.range {
497 continue;
498 }
499 let rhs = assignment_rhs(item.node).unwrap();
500 reducer.walk(rhs, item.name.and_then(|name| shapes.get(name)), 0)?;
501 if reducer
502 .groups
503 .range(rhs.range().start..rhs.range().end)
504 .any(|(_, mapping)| mapping.retained.iter().any(|keep| !keep))
505 && let Some((id, _)) = item.name.and_then(|name| reducer.assignments.get(name))
506 {
507 reducer.changed_declarations.insert(id.0);
508 }
509 }
510 reducer.discovering = false;
511 reducer.visited = 0;
512 for item in &items {
513 if item.range == target.range {
514 continue;
515 }
516 let rhs = assignment_rhs(item.node).unwrap();
517 edits.extend(reducer.walk(rhs, item.name.and_then(|name| shapes.get(name)), 0)?);
518 }
519 if let Some((bindings, calls)) = model_facts {
520 reducer.model_accesses(model.unwrap(), &bindings, &calls.expressions)?;
521 }
522 let candidate = crate::finish_candidate(input, &edits, &[], reducer.work, range)?;
523 Ok(ReductionResult {
524 candidate: Box::new(candidate),
525 unresolved: reducer.unresolved,
526 })
527}
528
529fn member<'a>(input: &'a Input, node: &SyntaxNode) -> Option<&'a str> {
530 let node = structured::unwrapped(node);
531 if node.kind() != NodeKind::Expression {
532 return None;
533 }
534 let index = structured::direct_token(input, node)?;
535 matches!(
536 input.syntax().tokens()[index].kind,
537 TokenKind::Identifier | TokenKind::QuotedIdentifier
538 )
539 .then(|| identifier_identity(std::str::from_utf8(input.token_bytes(index)).unwrap()))
540}
541
542fn target_keyed(
545 input: &Input,
546 node: &SyntaxNode,
547 members: &HashSet<&str>,
548 work: &mut Work,
549 range: &Range<usize>,
550 limits: Limits,
551) -> Result<bool, QueryError> {
552 let mut pending = vec![(node, false)];
553 while let Some((node, key)) = pending.pop() {
554 work.spend(node.children().len().max(1), range)?;
555 if node.child_nodes().count() > limits.collection {
556 return Err(QueryError::query(
557 range.clone(),
558 "query collection limit exceeded",
559 ));
560 }
561 if key && member(input, node).is_some_and(|name| members.contains(name)) {
562 return Ok(true);
563 }
564 for (index, child) in node.child_nodes().enumerate() {
565 let written_key = match node.kind() {
566 NodeKind::IndexedArrayEntry => index == 0,
567 NodeKind::MatrixColumnIndices => true,
568 NodeKind::MatrixRow => index == 0 && structured::has_colon(input, node),
569 _ => false,
570 };
571 pending.push((child, key || written_key));
572 }
573 }
574 Ok(false)
575}
576
577struct Reducer<'a, 'w, 'b> {
578 input: &'a Input,
579 name: &'a str,
580 members: Vec<&'a str>,
581 removed: HashSet<&'a str>,
582 target: Option<DeclarationId>,
583 model_supplied: bool,
584 full_domain_names: HashSet<usize>,
585 shapes: BTreeMap<String, Shape>,
586 assignments: BTreeMap<String, (DeclarationId, &'a SyntaxNode)>,
587 array_keys: BTreeMap<usize, Vec<Vec<Key>>>,
588 groups: BTreeMap<usize, GroupMapping>,
589 changed_declarations: HashSet<usize>,
590 discovering: bool,
591 dependent_names: HashSet<String>,
592 unresolved: Vec<UnresolvedDependency>,
593 work: &'w mut Work<'b>,
594 range: &'w Range<usize>,
595 limits: Limits,
596 visited: usize,
597}
598
599struct GroupMapping {
600 retained: Vec<bool>,
601 indices: Vec<Option<i64>>,
602}
603
604struct LiteralPath {
605 name: String,
606 start: usize,
607 steps: Vec<PathStep>,
608}
609
610enum PathStep {
611 Field(Key),
612 Index(Vec<Key>),
613}
614
615impl<'a> Reducer<'a, '_, '_> {
616 fn model_accesses(
617 &mut self,
618 model: &ModelContext,
619 bindings: &BindingFacts,
620 expressions: &[ExpressionType],
621 ) -> Result<(), QueryError> {
622 let Some(target) = self.target else {
623 return Ok(());
624 };
625 self.work.spend(
626 expressions.len().saturating_add(bindings.references.len()),
627 self.range,
628 )?;
629 let types: BTreeMap<_, _> = expressions
630 .iter()
631 .map(|expression| {
632 (
633 (
634 expression.file,
635 expression.location.range.start,
636 expression.location.range.end,
637 ),
638 &expression.ty,
639 )
640 })
641 .collect();
642 let references: BTreeMap<_, _> = bindings
643 .references
644 .iter()
645 .map(|reference| ((reference.file, reference.location.range.start), reference))
646 .collect();
647 let members: HashSet<_> = self.members.iter().copied().collect();
648 let mut literal_groups = HashSet::new();
652 for (id, rhs) in self.assignments.values() {
653 let rhs = structured::unwrapped(rhs);
654 if !self.groups.contains_key(&rhs.range().start) {
655 continue;
656 }
657 let mut literal = true;
658 for group in rhs.child_nodes() {
659 self.work.visit(self.range)?;
660 let group = structured::unwrapped(group);
661 if group.kind() != NodeKind::SetLiteral {
662 literal = false;
663 break;
664 }
665 for value in group.child_nodes() {
666 self.work.visit(self.range)?;
667 if !member(self.input, value).is_some_and(|value| members.contains(value)) {
668 literal = false;
669 break;
670 }
671 }
672 if !literal {
673 break;
674 }
675 }
676 if literal {
677 literal_groups.insert(id.0);
678 }
679 }
680 self.work.spend(bindings.declarations.len(), self.range)?;
681 let mut generator_bindings = BTreeMap::<_, Vec<_>>::new();
682 for declaration in &bindings.declarations {
683 if declaration.role == DeclarationRole::Generator {
684 generator_bindings
685 .entry((
686 declaration.file,
687 declaration.syntax_range.start,
688 declaration.syntax_range.end,
689 ))
690 .or_default()
691 .push(declaration.id.0);
692 }
693 }
694 for (file, source) in model.files.iter().enumerate() {
695 let mut pending = vec![source.parsed.tree()];
696 let mut accesses = Vec::new();
697 let mut generators = Vec::new();
698 while let Some(node) = pending.pop() {
699 self.work.spend(node.children().len().max(1), self.range)?;
700 if node.child_nodes().count() > self.limits.collection {
701 return Err(QueryError::query(
702 self.range.clone(),
703 "query collection limit exceeded",
704 ));
705 }
706 if node.kind() == NodeKind::Generator
707 && node.children().iter().any(|child| {
708 matches!(child, zincite_syntax::SyntaxElement::Token(index)
709 if source.parsed.tokens()[*index].kind == TokenKind::In)
710 })
711 {
712 let generator_source =
713 structured::unwrapped(node.child_nodes().next().unwrap());
714 let location = source.location(generator_source.range());
715 if generator_source.kind() == NodeKind::Expression
716 && let Some(reference) = references.get(&(file, location.range.start))
717 && let BindingResolution::Resolved(id) = reference.resolution
718 && let Some(bound) =
719 generator_bindings.get(&(file, node.range().start, node.range().end))
720 {
721 generators.push((id, bound));
722 }
723 }
724 if node.kind() == NodeKind::ArrayAccessExpression {
725 accesses.push(node);
726 }
727 pending.extend(node.child_nodes());
728 }
729 let mut enum_generators = HashSet::new();
733 let mut group_generators = HashSet::new();
734 for (source, bound) in &generators {
735 self.work.spend(bound.len(), self.range)?;
736 if *source == target {
737 enum_generators.extend(bound.iter().copied());
738 } else if literal_groups.contains(&source.0) {
739 group_generators.extend(bound.iter().copied());
740 }
741 }
742 for (source, bound) in &generators {
743 self.work.spend(bound.len(), self.range)?;
744 if group_generators.contains(&source.0) {
745 enum_generators.extend(bound.iter().copied());
746 }
747 }
748 for node in accesses {
749 let mut children = node.child_nodes();
750 let subject = children.next().unwrap();
751 let location = source.location(subject.range());
752 let affected = references.range((file, location.range.start)..(file, location.range.end)).any(|(_, reference)| {
753 matches!(reference.resolution, BindingResolution::Resolved(id) if self.changed_declarations.contains(&id.0))
754 });
755 let may_be_group = types.get(&(file, location.range.start, location.range.end)).is_none_or(|ty| {
756 !type_known(ty) || matches!(&ty.kind, TypeKind::Array { indices, element }
757 if indices.len() == 1 && indices[0].kind == TypeKind::Int
758 && matches!(&element.kind, TypeKind::Set(member) if member.kind == TypeKind::Enum(target)))
759 });
760 if affected && may_be_group {
761 let mut group_problem = true;
762 if let Some(path) = self.literal_path(subject, &source.parsed)?
763 && let Some((id, _)) = self.assignments.get(&path.name)
764 && references
765 .get(&(file, path.start + source.byte_offset))
766 .is_some_and(|reference| {
767 reference.resolution == BindingResolution::Resolved(*id)
768 })
769 && references
770 .range((file, location.range.start)..(file, location.range.end))
771 .all(|(_, reference)| {
772 reference.location.range.start == path.start + source.byte_offset
773 || match reference.resolution {
774 BindingResolution::Resolved(id) => {
775 bindings.declarations[id.0].role
776 == DeclarationRole::EnumMember
777 }
778 BindingResolution::Unresolved => {
779 members.contains(reference.name.as_str())
780 }
781 _ => false,
782 }
783 })
784 && let Some(collection) = self.resolve_literal(&path)?
785 {
786 group_problem = false;
787 if self.groups.contains_key(&collection.range().start) {
788 let selectors: Vec<_> = node.child_nodes().skip(1).collect();
789 let key = if selectors.len() == 1 {
790 self.literal_key(selectors[0], &source.parsed, 0)?
791 } else {
792 None
793 };
794 group_problem = match key.as_deref() {
795 Some([Key::Integer(index)]) => {
796 index
797 .checked_sub(1)
798 .and_then(|index| usize::try_from(index).ok())
799 .and_then(|index| {
800 self.groups[&collection.range().start]
801 .indices
802 .get(index)
803 })
804 .copied()
805 .flatten()
806 != Some(*index)
807 }
808 _ => true,
809 };
810 }
811 }
812 if group_problem {
813 self.unresolved.push(UnresolvedDependency {
814 location: source.location(node.range()),
815 message: "group access requires an index repair or unavailable collection relationship in the read-only model".into(),
816 });
817 }
818 }
819 if let Some(TypeInst {
820 kind: TypeKind::Array { indices, .. },
821 ..
822 }) = types.get(&(file, location.range.start, location.range.end))
823 {
824 let mut computed = false;
825 for (selector, index) in children.zip(indices) {
826 if index.kind != TypeKind::Enum(target) {
827 continue;
828 }
829 let selector = structured::unwrapped(selector);
830 let identity_selector = selector.kind() == NodeKind::Expression
831 && selector.children().iter().any(|child| {
832 let zincite_syntax::SyntaxElement::Token(index) = child else {
833 return false;
834 };
835 let token = &source.parsed.tokens()[*index];
836 if !matches!(
837 token.kind,
838 TokenKind::Identifier | TokenKind::QuotedIdentifier
839 ) {
840 return false;
841 }
842 let member_name = members.contains(identifier_identity(
843 &source.parsed.source()[token.range.clone()],
844 ));
845 match references
846 .get(&(file, token.range.start + source.byte_offset))
847 .map(|reference| &reference.resolution)
848 {
849 Some(BindingResolution::Resolved(id))
850 if enum_generators.contains(&id.0) =>
851 {
852 true
853 }
854 Some(BindingResolution::Unresolved) => member_name,
855 Some(BindingResolution::Resolved(id))
856 if bindings.declarations[id.0].role
857 == DeclarationRole::EnumMember
858 && member_name =>
859 {
860 let location = source.location(selector.range());
861 types
862 .get(&(file, location.range.start, location.range.end))
863 .is_some_and(|ty| ty.kind == TypeKind::Enum(target))
864 }
865 _ => false,
866 }
867 });
868 computed |= !identity_selector;
869 }
870 if computed {
871 self.unresolved.push(UnresolvedDependency {
872 location: source.location(node.range()),
873 message: "computed enum-indexed access remains in the read-only model"
874 .into(),
875 });
876 }
877 }
878 }
879 }
880 Ok(())
881 }
882
883 fn unresolved(&mut self, node: &SyntaxNode, message: impl Into<String>) {
884 if self.discovering {
885 return;
886 }
887 self.unresolved.push(UnresolvedDependency {
888 location: SourceLocation::from_bytes(
889 PathBuf::from(self.input.file()),
890 self.input.source_bytes(),
891 self.input.original_range(node.range()),
892 0,
893 ),
894 message: message.into(),
895 });
896 }
897
898 fn walk(
899 &mut self,
900 node: &SyntaxNode,
901 shape: Option<&Shape>,
902 depth: usize,
903 ) -> Result<Vec<TextEdit>, QueryError> {
904 if depth >= self.limits.nesting.min(64) {
905 return Err(QueryError::query(
906 self.range.clone(),
907 "reduction nesting limit exceeded",
908 ));
909 }
910 self.work.visit(self.range)?;
911 crate::inspection::check_collection(self.visited, self.limits.collection, self.range)?;
912 self.visited += 1;
913 let node = structured::unwrapped(node);
914 self.work.spend(node.children().len(), self.range)?;
915 if node.child_nodes().count() > self.limits.collection {
916 return Err(QueryError::query(
917 self.range.clone(),
918 "query collection limit exceeded",
919 ));
920 }
921 if matches!(
922 node.kind(),
923 NodeKind::ArrayLiteral | NodeKind::MatrixLiteral
924 ) && shape
925 .is_some_and(|shape| !shape.contains_enum(self.target) && type_known(&shape.ty))
926 && !self.references(node)?
927 {
928 return Ok(Vec::new());
929 }
930 match node.kind() {
931 NodeKind::ArrayLiteral => self.array(node, shape, depth),
932 NodeKind::MatrixLiteral => self.matrix(node, shape, depth),
933 NodeKind::RecordLiteral => {
934 let mut edits = Vec::new();
935 for field in node.child_nodes() {
936 if let Some(shape) = shape {
937 self.work.spend(shape.components.len(), self.range)?;
938 }
939 let index = structured::direct_token(self.input, field).unwrap();
940 let label = identifier_identity(
941 std::str::from_utf8(self.input.token_bytes(index)).unwrap(),
942 );
943 let field_shape = shape
944 .and_then(|shape| {
945 shape
946 .components
947 .iter()
948 .find(|(name, _)| name.as_deref() == Some(label))
949 })
950 .map(|(_, shape)| shape);
951 edits.extend(self.walk(
952 field.child_nodes().next().unwrap(),
953 field_shape,
954 depth + 1,
955 )?);
956 }
957 Ok(edits)
958 }
959 NodeKind::TupleLiteral => {
960 let mut edits = Vec::new();
961 for (index, child) in node.child_nodes().enumerate() {
962 edits.extend(
963 self.walk(
964 child,
965 shape
966 .and_then(|shape| shape.components.get(index))
967 .map(|(_, shape)| shape),
968 depth + 1,
969 )?,
970 );
971 }
972 Ok(edits)
973 }
974 NodeKind::SetLiteral => {
975 let retained = self.set_mask(node, shape)?;
976 if self.discovering {
977 return Ok(Vec::new());
978 }
979 let entries: Vec<_> = node.child_nodes().collect();
980 Ok(vec![structured::collection_edit(
981 self.input,
982 node,
983 &entries,
984 &retained,
985 TokenKind::Comma,
986 self.work,
987 self.range,
988 )?])
989 }
990 NodeKind::ArrayAccessExpression if !self.discovering => self.data_access(node, shape),
991 NodeKind::Expression
992 if member(self.input, node).is_some_and(|name| self.removed.contains(name)) =>
993 {
994 self.unresolved(node, "removed enum member remains as a scalar value");
995 Ok(Vec::new())
996 }
997 NodeKind::Expression => Ok(Vec::new()),
998 _ => {
999 if !self.discovering && self.relevant_computed(node, shape)? {
1000 self.unresolved(
1001 node,
1002 "computed enum value, access or index dependence is unsupported",
1003 );
1004 }
1005 Ok(Vec::new())
1006 }
1007 }
1008 }
1009
1010 fn literal_path(
1011 &mut self,
1012 mut node: &SyntaxNode,
1013 parsed: &ParsedFile,
1014 ) -> Result<Option<LiteralPath>, QueryError> {
1015 let mut steps = Vec::new();
1016 loop {
1017 self.work.spend(node.children().len().max(1), self.range)?;
1018 if steps.len() >= self.limits.nesting.min(64) {
1019 return Err(QueryError::query(
1020 self.range.clone(),
1021 "reduction access nesting limit exceeded",
1022 ));
1023 }
1024 node = structured::unwrapped(node);
1025 match node.kind() {
1026 NodeKind::Expression => {
1027 let Some(index) = structured::syntax_token(parsed, node) else {
1028 return Ok(None);
1029 };
1030 let token = &parsed.tokens()[index];
1031 if !matches!(
1032 token.kind,
1033 TokenKind::Identifier | TokenKind::QuotedIdentifier
1034 ) {
1035 return Ok(None);
1036 }
1037 steps.reverse();
1038 return Ok(Some(LiteralPath {
1039 name: identifier_identity(&parsed.source()[token.range.clone()]).into(),
1040 start: token.range.start,
1041 steps,
1042 }));
1043 }
1044 NodeKind::FieldAccessExpression => {
1045 let Some(token) = node.children().iter().find_map(|child| match child {
1046 zincite_syntax::SyntaxElement::Token(index)
1047 if matches!(
1048 parsed.tokens()[*index].kind,
1049 TokenKind::Identifier
1050 | TokenKind::QuotedIdentifier
1051 | TokenKind::IntegerLiteral
1052 ) =>
1053 {
1054 Some(&parsed.tokens()[*index])
1055 }
1056 _ => None,
1057 }) else {
1058 return Ok(None);
1059 };
1060 let text = &parsed.source()[token.range.clone()];
1061 let field = if token.kind == TokenKind::IntegerLiteral {
1062 let Ok(structured::Scalar::Integer(index)) = structured::parse_number(text)
1063 else {
1064 return Ok(None);
1065 };
1066 Key::Integer(index)
1067 } else {
1068 Key::Member(identifier_identity(text).into())
1069 };
1070 steps.push(PathStep::Field(field));
1071 node = node.child_nodes().next().unwrap();
1072 }
1073 NodeKind::ArrayAccessExpression => {
1074 let mut children = node.child_nodes();
1075 let subject = children.next().unwrap();
1076 let mut keys = Vec::new();
1077 for selector in children {
1078 let Some(parts) = self.literal_key(selector, parsed, 0)? else {
1079 return Ok(None);
1080 };
1081 keys.extend(parts);
1082 crate::inspection::check_collection(
1083 keys.len(),
1084 self.limits.collection,
1085 self.range,
1086 )?;
1087 }
1088 steps.push(PathStep::Index(keys));
1089 node = subject;
1090 }
1091 _ => return Ok(None),
1092 }
1093 }
1094 }
1095
1096 fn literal_key(
1097 &mut self,
1098 node: &SyntaxNode,
1099 parsed: &ParsedFile,
1100 depth: usize,
1101 ) -> Result<Option<Vec<Key>>, QueryError> {
1102 self.work.spend(node.children().len().max(1), self.range)?;
1103 if depth >= self.limits.nesting.min(64)
1104 || node.child_nodes().count() > self.limits.collection
1105 {
1106 return Err(QueryError::query(
1107 self.range.clone(),
1108 "reduction selector limit exceeded",
1109 ));
1110 }
1111 let node = structured::unwrapped(node);
1112 match node.kind() {
1113 NodeKind::Expression => {
1114 let Some(index) = structured::syntax_token(parsed, node) else {
1115 return Ok(None);
1116 };
1117 let token = &parsed.tokens()[index];
1118 let text = &parsed.source()[token.range.clone()];
1119 self.work.spend(text.len(), self.range)?;
1120 let key = match token.kind {
1121 TokenKind::Identifier | TokenKind::QuotedIdentifier => {
1122 Key::Member(identifier_identity(text).into())
1123 }
1124 TokenKind::IntegerLiteral => {
1125 let Ok(structured::Scalar::Integer(value)) = structured::parse_number(text)
1126 else {
1127 return Ok(None);
1128 };
1129 Key::Integer(value)
1130 }
1131 _ => return Ok(None),
1132 };
1133 Ok(Some(vec![key]))
1134 }
1135 NodeKind::UnaryExpression => {
1136 let Some(mut key) =
1137 self.literal_key(node.child_nodes().next().unwrap(), parsed, depth + 1)?
1138 else {
1139 return Ok(None);
1140 };
1141 let [Key::Integer(value)] = key.as_mut_slice() else {
1142 return Ok(None);
1143 };
1144 let operator =
1145 structured::syntax_token(parsed, node).map(|index| parsed.tokens()[index].kind);
1146 match operator {
1147 Some(TokenKind::Plus) => {}
1148 Some(TokenKind::Minus) => {
1149 let Some(negative) = value.checked_neg() else {
1150 return Ok(None);
1151 };
1152 *value = negative;
1153 }
1154 _ => return Ok(None),
1155 }
1156 Ok(Some(key))
1157 }
1158 NodeKind::TupleLiteral | NodeKind::IndexTuple => {
1159 let mut keys = Vec::new();
1160 for child in node.child_nodes() {
1161 let Some(parts) = self.literal_key(child, parsed, depth + 1)? else {
1162 return Ok(None);
1163 };
1164 keys.extend(parts);
1165 crate::inspection::check_collection(
1166 keys.len(),
1167 self.limits.collection,
1168 self.range,
1169 )?;
1170 }
1171 Ok(Some(keys))
1172 }
1173 _ => Ok(None),
1174 }
1175 }
1176
1177 fn resolve_literal(
1178 &mut self,
1179 path: &LiteralPath,
1180 ) -> Result<Option<&'a SyntaxNode>, QueryError> {
1181 let Some((_, mut node)) = self.assignments.get(&path.name).copied() else {
1182 return Ok(None);
1183 };
1184 for step in &path.steps {
1185 node = structured::unwrapped(node);
1186 self.work.spend(node.children().len().max(1), self.range)?;
1187 node = match (step, node.kind()) {
1188 (PathStep::Field(Key::Member(name)), NodeKind::RecordLiteral) => {
1189 let Some(field) = node.child_nodes().find(|field| {
1190 let index = structured::direct_token(self.input, field).unwrap();
1191 identifier_identity(
1192 &self.input.syntax().source()
1193 [self.input.syntax().tokens()[index].range.clone()],
1194 ) == name
1195 }) else {
1196 return Ok(None);
1197 };
1198 field.child_nodes().next().unwrap()
1199 }
1200 (PathStep::Field(Key::Integer(index)), NodeKind::TupleLiteral) => {
1201 let Some(field) = index
1202 .checked_sub(1)
1203 .and_then(|index| usize::try_from(index).ok())
1204 .and_then(|index| node.child_nodes().nth(index))
1205 else {
1206 return Ok(None);
1207 };
1208 field
1209 }
1210 (PathStep::Index(keys), NodeKind::ArrayLiteral) => {
1211 let Some(identities) = self.array_keys.get(&node.range().start) else {
1212 return Ok(None);
1213 };
1214 self.work.spend(identities.len(), self.range)?;
1215 let Some(index) = identities.iter().position(|identity| identity == keys)
1216 else {
1217 return Ok(None);
1218 };
1219 let entry = node.child_nodes().nth(index).unwrap();
1220 if structured::array_indexing(node) == ArrayIndexing::Explicit {
1221 entry.child_nodes().nth(1).unwrap()
1222 } else {
1223 entry
1224 }
1225 }
1226 _ => return Ok(None),
1227 };
1228 }
1229 Ok(Some(structured::unwrapped(node)))
1230 }
1231
1232 fn data_access(
1233 &mut self,
1234 node: &SyntaxNode,
1235 shape: Option<&Shape>,
1236 ) -> Result<Vec<TextEdit>, QueryError> {
1237 let mut children = node.child_nodes();
1238 let subject = children.next().unwrap();
1239 if let Some(path) = self.literal_path(subject, self.input.syntax())?
1240 && let Some(collection) = self.resolve_literal(&path)?
1241 && self.groups.contains_key(&collection.range().start)
1242 {
1243 let selectors: Vec<_> = children.collect();
1244 let key = if selectors.len() == 1 {
1245 self.literal_key(selectors[0], self.input.syntax(), 0)?
1246 } else {
1247 None
1248 };
1249 let Some(key) = key else {
1250 self.unresolved(node, "computed group selector is unsupported");
1251 return Ok(Vec::new());
1252 };
1253 let [Key::Integer(index)] = key.as_slice() else {
1254 self.unresolved(node, "group selector requires an established integer index");
1255 return Ok(Vec::new());
1256 };
1257 let mapped = index
1258 .checked_sub(1)
1259 .and_then(|index| usize::try_from(index).ok())
1260 .and_then(|index| self.groups[&collection.range().start].indices.get(index))
1261 .copied()
1262 .flatten();
1263 let Some(mapped) = mapped else {
1264 self.unresolved(
1265 node,
1266 "group access selects a removed or out-of-coverage group",
1267 );
1268 return Ok(Vec::new());
1269 };
1270 if mapped == *index {
1271 return Ok(Vec::new());
1272 }
1273 let mut selector = structured::unwrapped(selectors[0]);
1274 while selector.kind() == NodeKind::UnaryExpression {
1275 selector = structured::unwrapped(selector.child_nodes().next().unwrap());
1276 }
1277 let token = structured::direct_token(self.input, selector).unwrap();
1278 return Ok(vec![TextEdit {
1279 range: self.input.token_range(token),
1280 replacement: vec![EditPart::Text(mapped.to_string())],
1281 }]);
1282 }
1283 if self.relevant_computed(node, shape)? {
1284 self.unresolved(
1285 node,
1286 "computed enum value, access or index dependence is unsupported",
1287 );
1288 }
1289 Ok(Vec::new())
1290 }
1291
1292 fn set_mask(
1293 &mut self,
1294 node: &SyntaxNode,
1295 shape: Option<&Shape>,
1296 ) -> Result<Vec<bool>, QueryError> {
1297 let element = shape
1298 .and_then(|shape| shape.components.first())
1299 .map(|(_, shape)| shape);
1300 let unrelated = element
1301 .is_some_and(|element| type_known(&element.ty) && !element.contains_enum(self.target));
1302 let mut retained = Vec::new();
1303 for entry in node.child_nodes() {
1304 self.work.visit(self.range)?;
1305 let direct = member(self.input, entry);
1306 retained.push(unrelated || direct.is_none_or(|name| !self.removed.contains(name)));
1307 if !self.discovering
1308 && !unrelated
1309 && direct.is_none()
1310 && self.relevant_computed(entry, element)?
1311 {
1312 self.unresolved(entry, "computed set dependence is unsupported");
1313 }
1314 }
1315 Ok(retained)
1316 }
1317
1318 fn references(&mut self, node: &SyntaxNode) -> Result<bool, QueryError> {
1319 let mut pending = vec![node];
1320 while let Some(node) = pending.pop() {
1321 self.work.spend(node.children().len(), self.range)?;
1322 for child in node.children().iter().rev() {
1323 match child {
1324 zincite_syntax::SyntaxElement::Node(child) => pending.push(child),
1325 zincite_syntax::SyntaxElement::Token(index) => {
1326 if matches!(
1327 node.kind(),
1328 NodeKind::RecordLiteralField | NodeKind::FieldAccessExpression
1329 ) {
1330 continue;
1331 }
1332 let token = &self.input.syntax().tokens()[*index];
1333 if !matches!(
1334 token.kind,
1335 TokenKind::Identifier | TokenKind::QuotedIdentifier
1336 ) {
1337 continue;
1338 }
1339 let identity =
1340 identifier_identity(&self.input.syntax().source()[token.range.clone()]);
1341 if self.removed.contains(identity)
1342 || identity == self.name
1343 || self.dependent_names.contains(identity)
1344 {
1345 return Ok(true);
1346 }
1347 }
1348 }
1349 }
1350 }
1351 Ok(false)
1352 }
1353
1354 fn relevant_computed(
1355 &mut self,
1356 node: &SyntaxNode,
1357 shape: Option<&Shape>,
1358 ) -> Result<bool, QueryError> {
1359 Ok(shape.is_some_and(|shape| shape.contains_enum(self.target)) || self.references(node)?)
1360 }
1361
1362 fn keys(
1363 &mut self,
1364 nodes: &[&SyntaxNode],
1365 owner: &SyntaxNode,
1366 ) -> Result<Option<Vec<Vec<Key>>>, QueryError> {
1367 let mut keys = Vec::new();
1368 for node in nodes {
1369 match structured::read_key(self.input, node, self.work, self.range, self.limits) {
1370 Ok(key) => keys.push(key),
1371 Err(error) if error.location == ErrorLocation::Input => {
1372 self.unresolved(
1373 node,
1374 "computed or unsupported array index cannot be aligned",
1375 );
1376 return Ok(None);
1377 }
1378 Err(error) => return Err(error),
1379 }
1380 }
1381 if let Err(error) = structured::validate_keys(
1382 &keys,
1383 &self.input.original_range(owner.range()),
1384 self.work,
1385 self.range,
1386 ) {
1387 if error.location != ErrorLocation::Input {
1388 return Err(error);
1389 }
1390 self.unresolved(owner, error.message);
1391 return Ok(None);
1392 }
1393 Ok(Some(keys))
1394 }
1395
1396 fn full_target_domain(&self, domain: &Domain) -> bool {
1397 match domain {
1398 Domain::Enum(id) => Some(*id) == self.target,
1399 Domain::Named {
1400 declaration,
1401 domain,
1402 } if self.full_domain_names.contains(&declaration.0) => self.full_target_domain(domain),
1403 _ => false,
1404 }
1405 }
1406
1407 fn mask(
1408 &mut self,
1409 owner: &SyntaxNode,
1410 keys: Option<&[Vec<Key>]>,
1411 count: usize,
1412 axis: usize,
1413 shape: Option<&Shape>,
1414 ) -> Option<Vec<bool>> {
1415 let index_type = shape.and_then(|shape| match &shape.ty.kind {
1416 TypeKind::Array { indices, .. } => indices.get(axis),
1417 _ => None,
1418 });
1419 let target_axis = index_type
1420 .is_some_and(|ty| matches!(ty.kind, TypeKind::Enum(id) if Some(id) == self.target));
1421 if let Some(keys) = keys {
1422 if target_axis
1423 && !shape
1424 .and_then(|shape| shape.indices.get(axis))
1425 .is_some_and(|domain| self.full_target_domain(domain))
1426 {
1427 self.unresolved(owner, "enum index subset coverage is unavailable; explicit keys are reduced without assuming full-domain alignment");
1428 }
1429 if keys.iter().any(|key| key.len() <= axis) {
1430 self.unresolved(
1431 owner,
1432 "array key arity does not match its declared dimensions",
1433 );
1434 return None;
1435 }
1436 let identities: HashSet<_> = keys.iter().map(|key| &key[axis]).collect();
1437 if target_axis
1438 && shape
1439 .and_then(|shape| shape.indices.get(axis))
1440 .is_some_and(|domain| self.full_target_domain(domain))
1441 && (identities.len() != self.members.len()
1442 || self
1443 .members
1444 .iter()
1445 .any(|member| !identities.contains(&Key::Member((*member).into()))))
1446 {
1447 self.unresolved(
1448 owner,
1449 "enum-indexed keys do not cover the original enum membership",
1450 );
1451 return None;
1452 }
1453 return Some(keys.iter().map(|key| {
1454 let established = match index_type.map(|ty| &ty.kind) {
1455 Some(TypeKind::Enum(id)) => Some(*id) == self.target,
1456 Some(TypeKind::Int | TypeKind::Bool | TypeKind::Float | TypeKind::String) => false,
1457 _ => true,
1458 };
1459 !(established && matches!(&key[axis], Key::Member(name) if self.removed.contains(name.as_str())))
1460 }).collect());
1461 }
1462 if target_axis {
1463 if count != self.members.len()
1464 || !shape
1465 .and_then(|shape| shape.indices.get(axis))
1466 .is_some_and(|domain| self.full_target_domain(domain))
1467 {
1468 self.unresolved(owner, "positional enum alignment requires the full original enum domain and exact coverage");
1469 return None;
1470 }
1471 return Some(
1472 self.members
1473 .iter()
1474 .map(|member| !self.removed.contains(member))
1475 .collect(),
1476 );
1477 }
1478 if index_type.is_none()
1479 || index_type.is_some_and(|ty| {
1480 matches!(ty.kind, TypeKind::Unknown(_) | TypeKind::Variable { .. })
1481 })
1482 {
1483 self.unresolved(owner, "positional array alignment is unavailable; supply an unambiguous model declaration");
1484 return None;
1485 }
1486 Some(vec![true; count])
1487 }
1488
1489 fn array(
1490 &mut self,
1491 node: &SyntaxNode,
1492 shape: Option<&Shape>,
1493 depth: usize,
1494 ) -> Result<Vec<TextEdit>, QueryError> {
1495 let entries: Vec<_> = node.child_nodes().collect();
1496 let indexing = structured::array_indexing(node);
1497 if indexing == ArrayIndexing::StartingKey {
1498 self.unresolved(node, "starting-key array alignment is unsupported");
1499 return Ok(Vec::new());
1500 }
1501 let keys = if indexing == ArrayIndexing::Explicit {
1502 let nodes: Vec<_> = entries
1503 .iter()
1504 .map(|entry| entry.child_nodes().next().unwrap())
1505 .collect();
1506 let Some(keys) = self.keys(&nodes, node)? else {
1507 return Ok(Vec::new());
1508 };
1509 Some(keys)
1510 } else {
1511 None
1512 };
1513 let arity = keys
1514 .as_ref()
1515 .and_then(|keys| keys.first())
1516 .map_or(1, Vec::len);
1517 if let Some(Shape {
1518 ty:
1519 TypeInst {
1520 kind: TypeKind::Array { indices, .. },
1521 ..
1522 },
1523 ..
1524 }) = shape
1525 && indices.len() != arity
1526 {
1527 self.unresolved(
1528 node,
1529 "array literal dimensions do not match the model declaration",
1530 );
1531 return Ok(Vec::new());
1532 }
1533 let member_identities: HashSet<_> = self.members.iter().copied().collect();
1534 self.work.spend(
1535 entries
1536 .len()
1537 .saturating_mul(arity)
1538 .saturating_add(self.members.len().saturating_mul(arity)),
1539 self.range,
1540 )?;
1541 if self.model_supplied
1542 && shape.is_none_or(|shape| match &shape.ty.kind {
1543 TypeKind::Array { indices, .. } => indices.iter().any(|ty| !type_known(ty)),
1544 _ => true,
1545 })
1546 && keys.as_ref().is_some_and(|keys| {
1547 keys.iter().flatten().any(
1548 |key| matches!(key, Key::Member(name) if member_identities.contains(name.as_str())),
1549 )
1550 })
1551 {
1552 self.unresolved(node, "model assignment/type association is unavailable or ambiguous for enum-indexed data");
1553 }
1554 let mut retained = vec![true; entries.len()];
1555 for axis in 0..arity {
1556 let Some(mask) = self.mask(node, keys.as_deref(), entries.len(), axis, shape) else {
1557 let element = shape
1560 .and_then(|shape| shape.components.first())
1561 .map(|(_, shape)| shape);
1562 let mut children = Vec::new();
1563 for entry in &entries {
1564 let value = if indexing == ArrayIndexing::Explicit {
1565 entry.child_nodes().nth(1).unwrap()
1566 } else {
1567 entry
1568 };
1569 children.extend(self.walk(value, element, depth + 1)?);
1570 }
1571 return Ok(children);
1572 };
1573 for (keep, mask) in retained.iter_mut().zip(mask) {
1574 *keep &= mask;
1575 }
1576 }
1577 if arity > 1 && !retained.iter().any(|keep| *keep) && !entries.is_empty() {
1578 self.unresolved(
1579 node,
1580 "empty tuple-key data cannot preserve the established multidimensional axes",
1581 );
1582 return Ok(Vec::new());
1583 }
1584 if let Some(keys) = &keys {
1585 let kept: Vec<_> = keys
1586 .iter()
1587 .zip(&retained)
1588 .filter_map(|(key, keep)| keep.then_some(key.clone()))
1589 .collect();
1590 if let Err(error) = structured::validate_keys(
1591 &kept,
1592 &self.input.original_range(node.range()),
1593 self.work,
1594 self.range,
1595 ) {
1596 if error.location != ErrorLocation::Input {
1597 return Err(error);
1598 }
1599 self.unresolved(node, error.message);
1600 return Ok(Vec::new());
1601 }
1602 }
1603 let element = shape
1604 .and_then(|shape| shape.components.first())
1605 .map(|(_, shape)| shape);
1606 if self.discovering {
1607 let identities = keys.clone().or_else(|| {
1608 let shape = shape?;
1609 if arity != 1 {
1610 return None;
1611 }
1612 if shape
1613 .indices
1614 .first()
1615 .is_some_and(|domain| self.full_target_domain(domain))
1616 {
1617 Some(
1618 self.members
1619 .iter()
1620 .map(|name| vec![Key::Member((*name).into())])
1621 .collect(),
1622 )
1623 } else if matches!(shape.indices.first(), Some(Domain::UnconstrainedInt))
1624 || matches!(shape.indices.first(), Some(Domain::Range {
1625 lower: zincite_lint::NumericBound::Integer(1),
1626 upper: zincite_lint::NumericBound::Integer(upper),
1627 }) if *upper == entries.len() as i64)
1628 {
1629 Some(
1630 (1..=entries.len() as i64)
1631 .map(|index| vec![Key::Integer(index)])
1632 .collect(),
1633 )
1634 } else {
1635 None
1636 }
1637 });
1638 if let Some(identities) = identities {
1639 self.array_keys.insert(node.range().start, identities);
1640 }
1641 let variable_groups = indexing == ArrayIndexing::Positional
1642 && shape.is_some_and(|shape| shape.indices == [Domain::UnconstrainedInt])
1643 && element.is_some_and(|element| matches!(&element.ty.kind,
1644 TypeKind::Set(member) if matches!(member.kind, TypeKind::Enum(id) if Some(id) == self.target)));
1645 if variable_groups {
1646 let mut group_mask = vec![true; entries.len()];
1647 for (index, entry) in entries.iter().enumerate() {
1648 let entry = structured::unwrapped(entry);
1649 if entry.kind() == NodeKind::SetLiteral && entry.child_nodes().next().is_some()
1650 {
1651 group_mask[index] = self.set_mask(entry, element)?.iter().any(|keep| *keep);
1652 }
1653 }
1654 let mut next = 0;
1655 let indices = group_mask
1656 .iter()
1657 .map(|keep| {
1658 keep.then(|| {
1659 next += 1;
1660 next
1661 })
1662 })
1663 .collect();
1664 self.groups.insert(
1665 node.range().start,
1666 GroupMapping {
1667 retained: group_mask,
1668 indices,
1669 },
1670 );
1671 }
1672 }
1673 if let Some(mapping) = self.groups.get(&node.range().start) {
1674 for (keep, group) in retained.iter_mut().zip(&mapping.retained) {
1675 *keep &= group;
1676 }
1677 }
1678 let mut children = Vec::new();
1679 for (entry, keep) in entries.iter().zip(&retained) {
1680 if *keep {
1681 let value = if indexing == ArrayIndexing::Explicit {
1682 entry.child_nodes().nth(1).unwrap()
1683 } else {
1684 entry
1685 };
1686 children.extend(self.walk(value, element, depth + 1)?);
1687 }
1688 }
1689 if self.discovering {
1690 return Ok(Vec::new());
1691 }
1692 let parent = structured::collection_edit(
1693 self.input,
1694 node,
1695 &entries,
1696 &retained,
1697 TokenKind::Comma,
1698 self.work,
1699 self.range,
1700 )?;
1701 Ok(vec![compose(parent, children)])
1702 }
1703
1704 fn matrix(
1705 &mut self,
1706 node: &SyntaxNode,
1707 shape: Option<&Shape>,
1708 depth: usize,
1709 ) -> Result<Vec<TextEdit>, QueryError> {
1710 let header = node
1711 .child_nodes()
1712 .find(|node| node.kind() == NodeKind::MatrixColumnIndices);
1713 let columns: Vec<_> = header
1714 .into_iter()
1715 .flat_map(SyntaxNode::child_nodes)
1716 .collect();
1717 let rows: Vec<_> = node
1718 .child_nodes()
1719 .filter(|node| node.kind() == NodeKind::MatrixRow)
1720 .collect();
1721 let keyed = rows
1722 .first()
1723 .is_some_and(|row| structured::has_colon(self.input, row));
1724 let width = rows.first().map_or(columns.len(), |row| {
1725 row.child_nodes().count() - usize::from(keyed)
1726 });
1727 if rows.iter().any(|row| {
1728 structured::has_colon(self.input, row) != keyed
1729 || row.child_nodes().count() - usize::from(keyed) != width
1730 }) || (!columns.is_empty() && columns.len() != width)
1731 {
1732 self.unresolved(
1733 node,
1734 "matrix rows and columns do not form a rectangular shape",
1735 );
1736 return Ok(Vec::new());
1737 }
1738 if let Some(Shape {
1739 ty:
1740 TypeInst {
1741 kind: TypeKind::Array { indices, .. },
1742 ..
1743 },
1744 ..
1745 }) = shape
1746 && indices.len() != 2
1747 {
1748 self.unresolved(node, "matrix requires a two-dimensional declaration");
1749 return Ok(Vec::new());
1750 }
1751 let row_keys = if keyed {
1752 let keys: Vec<_> = rows
1753 .iter()
1754 .map(|row| row.child_nodes().next().unwrap())
1755 .collect();
1756 let Some(keys) = self.keys(&keys, node)? else {
1757 return Ok(Vec::new());
1758 };
1759 Some(keys)
1760 } else {
1761 None
1762 };
1763 let column_keys = if columns.is_empty() {
1764 None
1765 } else {
1766 let Some(keys) = self.keys(&columns, node)? else {
1767 return Ok(Vec::new());
1768 };
1769 Some(keys)
1770 };
1771 if row_keys
1772 .iter()
1773 .chain(column_keys.iter())
1774 .flatten()
1775 .any(|key| key.len() != 1)
1776 {
1777 self.unresolved(node, "matrix axis keys must be scalar identities");
1778 return Ok(Vec::new());
1779 }
1780 self.work.spend(
1781 rows.len()
1782 .saturating_add(width)
1783 .saturating_add(self.members.len().saturating_mul(2)),
1784 self.range,
1785 )?;
1786 if self.model_supplied && shape.is_none() {
1787 self.unresolved(
1788 node,
1789 "model assignment/type association is unavailable or ambiguous for matrix data",
1790 );
1791 }
1792 let Some(row_mask) = self.mask(node, row_keys.as_deref(), rows.len(), 0, shape) else {
1794 return Ok(Vec::new());
1795 };
1796 let adjusted_columns = column_keys.as_ref().map(|keys| {
1797 keys.iter()
1798 .map(|key| vec![Key::Integer(0), key[0].clone()])
1799 .collect::<Vec<_>>()
1800 });
1801 let Some(column_mask) = self.mask(node, adjusted_columns.as_deref(), width, 1, shape)
1802 else {
1803 return Ok(Vec::new());
1804 };
1805 if !row_mask.iter().any(|keep| *keep) || !column_mask.iter().any(|keep| *keep) {
1806 self.unresolved(
1807 node,
1808 "empty matrix data cannot preserve the established row and column axes",
1809 );
1810 return Ok(Vec::new());
1811 }
1812 let element = shape
1813 .and_then(|shape| shape.components.first())
1814 .map(|(_, shape)| shape);
1815 let mut children = Vec::new();
1816 if let Some(header) = header
1817 && !self.discovering
1818 {
1819 children.push(structured::collection_edit(
1820 self.input,
1821 header,
1822 &columns,
1823 &column_mask,
1824 TokenKind::Colon,
1825 self.work,
1826 self.range,
1827 )?);
1828 }
1829 for (row, keep) in rows.iter().zip(&row_mask) {
1830 if !keep {
1831 continue;
1832 }
1833 let cells: Vec<_> = row.child_nodes().skip(usize::from(keyed)).collect();
1834 let mut cell_edits = Vec::new();
1835 for (cell, keep) in cells.iter().zip(&column_mask) {
1836 if *keep {
1837 cell_edits.extend(self.walk(cell, element, depth + 1)?);
1838 }
1839 }
1840 if !self.discovering {
1841 children.push(compose(
1842 structured::collection_edit(
1843 self.input,
1844 row,
1845 &cells,
1846 &column_mask,
1847 TokenKind::Comma,
1848 self.work,
1849 self.range,
1850 )?,
1851 cell_edits,
1852 ));
1853 }
1854 }
1855 if self.discovering {
1856 return Ok(Vec::new());
1857 }
1858 Ok(vec![compose(
1859 structured::collection_edit(
1860 self.input,
1861 node,
1862 &rows,
1863 &row_mask,
1864 TokenKind::Pipe,
1865 self.work,
1866 self.range,
1867 )?,
1868 children,
1869 )])
1870 }
1871}
1872
1873fn compose(mut parent: TextEdit, mut children: Vec<TextEdit>) -> TextEdit {
1876 children.sort_by_key(|edit| edit.range.start);
1877 let mut replacement = Vec::new();
1878 for part in parent.replacement {
1879 let EditPart::Original(span) = part else {
1880 replacement.push(part);
1881 continue;
1882 };
1883 let mut cursor = span.start;
1884 for child in &children {
1885 if child.range.start < span.start || child.range.end > span.end {
1886 continue;
1887 }
1888 if cursor < child.range.start {
1889 replacement.push(EditPart::Original(cursor..child.range.start));
1890 }
1891 replacement.extend(child.replacement.clone());
1892 cursor = child.range.end;
1893 }
1894 if cursor < span.end {
1895 replacement.push(EditPart::Original(cursor..span.end));
1896 }
1897 }
1898 parent.replacement = replacement;
1899 parent
1900}
1901
1902fn type_known(ty: &TypeInst) -> bool {
1903 match &ty.kind {
1904 TypeKind::Unknown(_) | TypeKind::Variable { .. } => false,
1905 TypeKind::Array { indices, element } => {
1906 indices.iter().all(type_known) && type_known(element)
1907 }
1908 TypeKind::Tuple(fields) => fields.iter().all(type_known),
1909 TypeKind::Record(fields) => fields.iter().all(|(_, field)| type_known(field)),
1910 TypeKind::Set(element) => type_known(element),
1911 _ => true,
1912 }
1913}
1914
1915pub(crate) fn declaration_shape(
1916 facts: &zincite_lint::ModelFacts<'_>,
1917 id: DeclarationId,
1918 work: &mut Work<'_>,
1919 range: &Range<usize>,
1920 limits: Limits,
1921) -> Result<Shape, QueryError> {
1922 let declaration = &facts.bindings().declarations[id.0];
1923 let ty = crate::comprehension::declaration_type(facts, id, work.budget, range)?;
1924 let node = declaration_node(facts.model(), facts.bindings(), id).and_then(|node| {
1925 node.child_nodes()
1926 .find(|node| node.kind() != NodeKind::Annotation)
1927 });
1928 ShapeReader {
1929 model: facts.model(),
1930 bindings: facts.bindings(),
1931 domains: facts.domains(),
1932 work,
1933 range,
1934 limits,
1935 }
1936 .shape(ty, declaration.file, node, 0)
1937}