Skip to main content

zincite_query/
reduction.rs

1//! Bounded enum membership reduction over original data and read-only model facts.
2use 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/// An inspectable, reparsed candidate. Only complete candidates may be written.
24#[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
42// Written domains accompany expanded types: an enum element type alone does
43// not establish the membership of an array's index subset.
44pub(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    // Shapes are immutable request-local facts. Removing them here lets the walk
490    // borrow the reducer mutably without cloning the expanded type tree.
491    let shapes = std::mem::take(&mut reducer.shapes);
492    // Discover original group identities before checking any dependent access.
493    // Use the same coverage checks and retained-entry traversal as the edit pass.
494    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
542// Written keys can establish a dependency without model declarations. Gather
543// names before walking values so computed uses do not depend on item order.
544fn 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        // Group mappings identify the positional arrays whose sets the reducer
649        // prunes. Require literal members throughout before using them as a
650        // source of retained enum identities in the read-only model.
651        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            // Resolve the two supported steps separately: arrays yield sets,
730            // then those bound sets yield members. Binding identities preserve
731            // lexical scope even when CST visitation encounters a body first.
732            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                // Missing axis coverage prevents slicing, but does not prevent
1558                // independent literal set repairs inside the preserved cells.
1559                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        // Each written matrix axis is scalar; mask receives its own component.
1793        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
1873// Splice child edits into the parent's retained original spans. Removed entries
1874// are never walked, and the final batch contains only disjoint outer edits.
1875fn 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}