1use super::*;
3use crate::data::charge;
4use crate::{LiteralKind as L, LiteralValue, reduction, structured};
5use std::collections::{BTreeSet, HashSet};
6use zincite_lint::{
7 DeclarationId, DeclarationRole, Domain, EditPart, Instantiation, TextEdit, TypeKind,
8};
9use zincite_syntax::{NodeKind as N, SyntaxNode, TokenKind as T};
10
11#[derive(Debug)]
12pub(super) struct ScalarArgument {
13 source: String,
14 range: Range<usize>,
15 names: Vec<Located<String>>,
16}
17
18#[derive(Debug)]
19pub(super) enum CopyStage {
20 Members(ScalarArgument, ScalarArgument),
21 Elements(Located<String>),
22 Repeat(ScalarArgument),
23}
24
25#[derive(Debug)]
28pub struct SourceCopies<'a> {
29 input: &'a Input,
30 owner: Rc<ModelFacts<'a>>,
31 parts: Vec<EditPart>,
32 kind: CopyKind,
33}
34#[derive(Debug)]
35enum CopyKind {
36 Members {
37 declaration: DeclarationId,
38 maps: Vec<BTreeMap<String, String>>,
39 },
40 Elements {
41 array: bool,
42 },
43}
44enum CopyRequest<'a, 'v> {
45 Members { count: usize, prefix: String },
46 Elements(&'v SourceCopies<'a>),
47 Repeat(usize),
48}
49impl Parser<'_> {
50 pub(super) fn parse_copy_stage(
51 &mut self,
52 reader: &mut zirql::ArgumentReader<'_>,
53 budget: &mut Budget,
54 ) -> Result<Stage, zirql::QueryError> {
55 let name = reader.word()?;
56 reader.expect('(')?;
57 let stage = if name.value == "clone_elements" {
58 let binding = reader.word()?;
59 if self.names.get(binding.value) != Some(&Kind::MemberCopies) {
60 return Err(zirql::QueryError::query(
61 binding.range,
62 "clone_elements requires a member-copy binding",
63 ));
64 }
65 CopyStage::Elements(Located::new(binding.value.to_owned(), binding.range))
66 } else {
67 let count = self.copy_argument(reader, budget)?;
68 if name.value == "clone_members" {
69 reader.expect(',')?;
70 CopyStage::Members(count, self.copy_argument(reader, budget)?)
71 } else {
72 CopyStage::Repeat(count)
73 }
74 };
75 reader.expect(')')?;
76 self.cursor.position = reader.position();
77 self.report_model_demand = true;
78 self.source_copy_range = Some(name.range.start..reader.position());
79 Ok(Stage::Copy(stage))
80 }
81 fn copy_argument(
82 &mut self,
83 reader: &mut zirql::ArgumentReader<'_>,
84 budget: &mut Budget,
85 ) -> Result<ScalarArgument, zirql::QueryError> {
86 self.cursor.position = reader.position();
87 let range = self
88 .cursor
89 .scalar_range()
90 .map_err(composition::query_error)?;
91 reader.advance(self.cursor.position - reader.position())?;
92 std::mem::swap(&mut self.preflight, budget);
93 let result = self.pure_value::<()>(range.clone());
94 std::mem::swap(&mut self.preflight, budget);
95 let (_, names) = result.map_err(composition::query_error)?;
96 budget.charge(range.len() + 1, zirql::Location::Query(range.clone()))?;
97 budget.charge_results(1, zirql::Location::Query(range.clone()))?;
98 Ok(ScalarArgument {
99 source: self.cursor.source[range.clone()].to_owned(),
100 range,
101 names,
102 })
103 }
104}
105impl NativeHost<'_> {
106 pub(super) fn source_copy<'a>(
107 &self,
108 stage: &CopyStage,
109 value: ReportValue<'a>,
110 context: &mut Context<'_, 'a>,
111 bindings: &Bindings<ReportValue<'a>>,
112 budget: &mut Budget,
113 range: &Range<usize>,
114 ) -> Result<ReportValue<'a>, zirql::EvaluationError<QueryError>> {
115 if !self.editing {
116 return Err(zirql::QueryError::query(
117 range.clone(),
118 "source-copy stages require an editing program ending with emit",
119 )
120 .into());
121 }
122 let facts = context
123 .report_facts(budget, range)
124 .map_err(zirql::EvaluationError::Host)?
125 .ok_or_else(|| {
126 zirql::QueryError::query(
127 range.clone(),
128 "source copying requires read-only model context (--model)",
129 )
130 })?;
131 let mut scalar = |arg: &ScalarArgument, budget: &mut Budget| {
132 let expression =
133 comprehension::read_data(&arg.source, arg.range.clone(), budget, self.nesting)
134 .map_err(zirql::EvaluationError::Host)?;
135 self.constructed(
136 &expression,
137 &arg.names,
138 context,
139 bindings,
140 budget,
141 &arg.range,
142 )
143 };
144 let request = match stage {
145 CopyStage::Members(count, prefix) => {
146 let n = scalar(count, budget)?;
147 let p = scalar(prefix, budget)?;
148 let zirql::ConstructedValue::String(prefix) = p.data() else {
149 return Err(zirql::QueryError::query(
150 range.clone(),
151 "clone_members prefix must be a string",
152 )
153 .into());
154 };
155 charge(budget, prefix.len(), range).map_err(zirql::EvaluationError::Host)?;
156 CopyRequest::Members {
157 count: copy_count(&n, range)?,
158 prefix: prefix.clone(),
159 }
160 }
161 CopyStage::Repeat(count) => {
162 CopyRequest::Repeat(copy_count(&scalar(count, budget)?, range)?)
163 }
164 CopyStage::Elements(name) => {
165 let ReportValue::SourceCopies(mapping) =
166 functions::unretained(bindings.get(&name.value, name.range.clone())?)
167 else {
168 return Err(zirql::QueryError::query(
169 name.range.clone(),
170 "clone_elements requires member copies",
171 )
172 .into());
173 };
174 let CopyKind::Members { .. } = &mapping.kind else {
175 return Err(zirql::QueryError::query(
176 name.range.clone(),
177 "clone_elements requires member copies",
178 )
179 .into());
180 };
181 admit_owner(mapping, context.input, &facts, range)
182 .map_err(zirql::EvaluationError::Host)?;
183 CopyRequest::Elements(mapping.as_ref())
184 }
185 };
186 let Some(SemanticStream::Inspection(selection)) = value.stream() else {
187 return Err(zirql::QueryError::query(
188 range.clone(),
189 "source copying requires one literal assignment",
190 )
191 .into());
192 };
193 let QueryResult::Literals(selection) = selection.as_ref() else {
194 return Err(zirql::QueryError::query(
195 range.clone(),
196 "source copying requires one literal assignment",
197 )
198 .into());
199 };
200 if selection.values.len() != 1 || !std::ptr::eq(selection.input, context.input) {
201 return Err(zirql::QueryError::query(
202 range.clone(),
203 "source copying requires one original literal assignment",
204 )
205 .into());
206 }
207 let copies = make_copies(
208 context.input,
209 facts,
210 &selection.values[0],
211 request,
212 budget,
213 self.limits,
214 range,
215 )
216 .map_err(zirql::EvaluationError::Host)?;
217 Ok(ReportValue::SourceCopies(Rc::new(copies)))
218 }
219}
220fn copy_count(
221 value: &zirql::ConstructedValue,
222 range: &Range<usize>,
223) -> Result<usize, zirql::QueryError> {
224 match value.data() {
225 zirql::ConstructedValue::Integer(n) => usize::try_from(*n).map_err(|_| {
226 zirql::QueryError::query(
227 range.clone(),
228 "additional copies must be a nonnegative integer",
229 )
230 }),
231 _ => Err(zirql::QueryError::query(
232 range.clone(),
233 "additional copies must be a nonnegative integer",
234 )),
235 }
236}
237fn admit_owner<'a>(
238 copies: &SourceCopies<'a>,
239 input: &Input,
240 facts: &ModelFacts<'a>,
241 range: &Range<usize>,
242) -> Result<(), QueryError> {
243 if !std::ptr::eq(copies.input, input) || !std::ptr::eq(copies.owner.as_ref(), facts) {
244 return Err(QueryError::query(
245 range.clone(),
246 "source copies belong to a different input or model owner",
247 ));
248 }
249 Ok(())
250}
251fn assignment<'a>(
252 input: &'a Input,
253 name: &str,
254 budget: &mut Budget,
255 range: &Range<usize>,
256) -> Result<&'a SyntaxNode, QueryError> {
257 let mut result = None;
258 for node in input.syntax().tree().child_nodes() {
259 charge(budget, 1, range)?;
260 if node.kind() == N::Assignment
261 && structured::direct_token(input, node).is_some_and(|token| {
262 crate::identifier_identity(std::str::from_utf8(input.token_bytes(token)).unwrap())
263 == name
264 })
265 && result.replace(node).is_some()
266 {
267 return Err(QueryError::input(
268 input.original_range(node.range()),
269 "source edits require exactly one original assignment",
270 ));
271 }
272 }
273 result.ok_or_else(|| {
274 QueryError::query(range.clone(), format!("missing data assignment for {name}"))
275 })
276}
277fn source_assignment<'a>(
278 input: &'a Input,
279 value: &LiteralValue<'_>,
280 budget: &mut Budget,
281 range: &Range<usize>,
282) -> Result<(&'a str, &'a SyntaxNode), QueryError> {
283 for node in input.syntax().tree().child_nodes() {
284 charge(budget, 1, range)?;
285 if let Some(rhs) = crate::assignment_rhs(node)
286 && (input.original_range(rhs.range()) == value.range
287 || input.original_range(structured::unwrapped(rhs).range()) == value.range)
288 {
289 let token = structured::direct_token(input, node).unwrap();
290 return Ok((
291 crate::identifier_identity(std::str::from_utf8(input.token_bytes(token)).unwrap()),
292 structured::unwrapped(rhs),
293 ));
294 }
295 }
296 Err(QueryError::input(
297 value.range.clone(),
298 "source copying requires a complete assignment RHS",
299 ))
300}
301fn uninitialized(
302 facts: &ModelFacts<'_>,
303 name: &str,
304 role: DeclarationRole,
305 budget: &mut Budget,
306 range: &Range<usize>,
307) -> Result<DeclarationId, QueryError> {
308 let declaration = comprehension::unique_declaration(facts, name, role, budget, range)?;
309 let node = comprehension::declaration_node(facts, declaration.id, budget, range)?.unwrap();
310 let children = node
311 .child_nodes()
312 .filter(|n| n.kind() != N::Annotation)
313 .count();
314 if children != usize::from(role == DeclarationRole::Value) {
315 return Err(QueryError::query(
316 range.clone(),
317 "source edits require an uninitialized model declaration",
318 ));
319 }
320 Ok(declaration.id)
321}
322fn interior(input: &Input, node: &SyntaxNode) -> Result<(Range<usize>, bool), QueryError> {
323 let array = node.kind() == N::ArrayLiteral;
324 if !array && node.kind() != N::SetLiteral {
325 return Err(QueryError::input(
326 input.original_range(node.range()),
327 "source copying supports direct sets and rank-one arrays",
328 ));
329 }
330 let r = input.original_range(node.range());
331 Ok((r.start + 1..r.end - 1, array))
332}
333fn trailing_comma(input: &Input, interior: &Range<usize>) -> bool {
334 input
335 .syntax()
336 .tokens()
337 .iter()
338 .enumerate()
339 .rev()
340 .find(|(i, t)| {
341 let r = input.token_range(*i);
342 interior.start <= r.start
343 && r.end <= interior.end
344 && !matches!(t.kind, T::Whitespace | T::LineComment | T::BlockComment)
345 })
346 .is_some_and(|(_, t)| t.kind == T::Comma)
347}
348fn make_copies<'a>(
349 input: &'a Input,
350 owner: Rc<ModelFacts<'a>>,
351 value: &LiteralValue<'a>,
352 request: CopyRequest<'a, '_>,
353 budget: &mut Budget,
354 limits: Limits,
355 range: &Range<usize>,
356) -> Result<SourceCopies<'a>, QueryError> {
357 let (count, prefix, mapping) = match &request {
358 CopyRequest::Members { count, prefix } => (*count, Some(prefix.as_str()), None),
359 CopyRequest::Repeat(count) => (*count, None, None),
360 CopyRequest::Elements(mapping) => {
361 let CopyKind::Members { maps, .. } = &mapping.kind else {
362 unreachable!()
363 };
364 (maps.len(), None, Some(*mapping))
365 }
366 };
367 let (name, node) = source_assignment(input, value, budget, range)?;
368 let (inside, array) = interior(input, node)?;
369 let units = count
370 .checked_mul(inside.len().max(1))
371 .ok_or_else(|| QueryError::query(range.clone(), "source copy size overflow"))?;
372 charge(budget, units, range)?;
373 budget
374 .charge_results(count, zirql::Location::Query(range.clone()))
375 .map_err(pipeline::core_error)?;
376 if count > limits.collection {
377 return Err(QueryError::query(
378 range.clone(),
379 "query collection limit exceeded",
380 ));
381 }
382 for (i, token) in input.syntax().tokens().iter().enumerate() {
383 charge(budget, 1, range)?;
384 let r = input.token_range(i);
385 if inside.start <= r.start
386 && r.end <= inside.end
387 && matches!(token.kind, T::LineComment | T::BlockComment)
388 && crate::directive(input.token_bytes(i)).is_some()
389 {
390 return Err(QueryError::input(r, "cannot copy source directives"));
391 }
392 }
393 let mut checker = Validator::new(input, &owner, budget, limits, range);
394 let mut replacements = Vec::new();
395 let kind = if let Some(prefix) = prefix {
396 if prefix.is_empty()
397 || !prefix.as_bytes()[0].is_ascii_alphabetic()
398 || !prefix
399 .bytes()
400 .all(|b| b.is_ascii_alphanumeric() || b == b'_')
401 {
402 return Err(QueryError::query(
403 range.clone(),
404 "member prefix must be a plain identifier",
405 ));
406 }
407 let id = uninitialized(&owner, name, DeclarationRole::Enum, checker.budget, range)?;
408 let members = checker.members(id)?;
409 if members.is_empty() {
410 return Err(QueryError::input(
411 value.range.clone(),
412 "clone_members requires a nonempty enum",
413 ));
414 }
415 collect_member_tokens(
416 input,
417 value,
418 &members,
419 &mut replacements,
420 checker.budget,
421 range,
422 )?;
423 let mut maps = Vec::with_capacity(count);
424 let mut names = existing_names(input, &owner, checker.budget, range)?;
425 for copy in 1..=count {
426 let mut map = BTreeMap::new();
427 for (position, member) in members.iter().enumerate() {
428 charge(checker.budget, prefix.len() + member.len() + 48, range)?;
429 checker
430 .budget
431 .charge_results(1, zirql::Location::Query(range.clone()))
432 .map_err(pipeline::core_error)?;
433 let generated = format!("{prefix}_{copy}_{}", position + 1);
434 charge(checker.budget, generated.len() + 1, range)?;
435 checker
436 .budget
437 .charge_results(1, zirql::Location::Query(range.clone()))
438 .map_err(pipeline::core_error)?;
439 if !names.insert(generated.clone()) {
440 return Err(QueryError::query(
441 range.clone(),
442 format!("generated member collision: {generated}"),
443 ));
444 }
445 map.insert(member.clone(), generated);
446 }
447 maps.push(map);
448 }
449 CopyKind::Members {
450 declaration: id,
451 maps,
452 }
453 } else {
454 let id = uninitialized(&owner, name, DeclarationRole::Value, checker.budget, range)?;
455 let shape = reduction::declaration_shape(
456 &owner,
457 id,
458 &mut Work {
459 budget: checker.budget,
460 },
461 range,
462 limits,
463 )?;
464 if let Some(mapping) = mapping {
465 let CopyKind::Members { declaration, .. } = &mapping.kind else {
466 unreachable!()
467 };
468 checker.remap = Some(*declaration);
469 }
470 let metadata = comprehension::declared_value(&owner, name, checker.budget, range)?.unwrap();
471 checker.validate(value, &shape, metadata, 0)?;
472 replacements = std::mem::take(&mut checker.replacements);
473 CopyKind::Elements { array }
474 };
475 let maps = match &kind {
476 CopyKind::Members { maps, .. } => Some(maps),
477 _ => mapping.and_then(|m| match &m.kind {
478 CopyKind::Members { maps, .. } => Some(maps),
479 _ => None,
480 }),
481 };
482 let entries = match &value.kind {
483 L::Set(v) => v.len(),
484 L::Array { indexing, entries } if *indexing != structured::ArrayIndexing::StartingKey => {
485 entries.len()
486 }
487 _ => {
488 return Err(QueryError::input(
489 value.range.clone(),
490 "source copying requires a direct rank-one collection",
491 ));
492 }
493 };
494 let literal_count = literal_copy_units(value, checker.budget, range)?;
495 let copied_literals = count
496 .checked_mul(literal_count)
497 .ok_or_else(|| QueryError::query(range.clone(), "source copy size overflow"))?;
498 checker
499 .budget
500 .charge_results(copied_literals, zirql::Location::Query(range.clone()))
501 .map_err(pipeline::core_error)?;
502 let part_count = count
503 .checked_mul(
504 replacements
505 .len()
506 .checked_mul(2)
507 .and_then(|n| n.checked_add(2))
508 .ok_or_else(|| QueryError::query(range.clone(), "source copy size overflow"))?,
509 )
510 .ok_or_else(|| QueryError::query(range.clone(), "source copy size overflow"))?;
511 charge(checker.budget, part_count, range)?;
512 checker
513 .budget
514 .charge_results(part_count, zirql::Location::Query(range.clone()))
515 .map_err(pipeline::core_error)?;
516 let mut parts = Vec::with_capacity(if entries > 0 { part_count } else { 0 });
517 if entries > 0 {
518 for copy in 0..count {
519 if copy > 0 && !trailing_comma(input, &inside) {
520 parts.push(EditPart::Text(",".into()));
521 }
522 let mut start = inside.start;
523 for (token, member) in &replacements {
524 let Some(map) = maps.map(|maps| &maps[copy]) else {
525 break;
526 };
527 parts.push(EditPart::Original(start..token.start));
528 charge(checker.budget, map[member].len(), range)?;
529 parts.push(EditPart::Text(map[member].clone()));
530 start = token.end;
531 }
532 parts.push(EditPart::Original(start..inside.end));
533 }
534 }
535 Ok(SourceCopies {
536 input,
537 owner,
538 parts,
539 kind,
540 })
541}
542fn literal_copy_units(
543 value: &LiteralValue<'_>,
544 budget: &mut Budget,
545 range: &Range<usize>,
546) -> Result<usize, QueryError> {
547 charge(budget, 1, range)?;
548 let mut units = 1;
549 match &value.kind {
550 L::Set(values) | L::Tuple(values) => {
551 for value in values {
552 units += literal_copy_units(value, budget, range)?;
553 }
554 }
555 L::Record(fields) => {
556 for field in fields {
557 units += literal_copy_units(&field.value, budget, range)?;
558 }
559 }
560 L::Array { entries, .. } => {
561 for entry in entries {
562 if let Some(key) = &entry.key {
563 units += literal_copy_units(key, budget, range)?;
564 }
565 units += literal_copy_units(&entry.value, budget, range)?;
566 }
567 }
568 _ => {}
569 }
570 Ok(units)
571}
572
573fn collect_member_tokens(
574 input: &Input,
575 value: &LiteralValue<'_>,
576 members: &[String],
577 output: &mut Vec<(Range<usize>, String)>,
578 budget: &mut Budget,
579 range: &Range<usize>,
580) -> Result<(), QueryError> {
581 charge(budget, 1 + members.len(), range)?;
582 match &value.kind {
583 L::Member(name) if members.iter().any(|m| m == name) => {
584 charge(budget, name.len() + 1, range)?;
585 budget
586 .charge_results(1, zirql::Location::Query(range.clone()))
587 .map_err(pipeline::core_error)?;
588 output.push((
589 member_token_range(input, value, budget, range)?,
590 (*name).to_owned(),
591 ));
592 }
593 L::Set(v) | L::Tuple(v) => {
594 for v in v {
595 collect_member_tokens(input, v, members, output, budget, range)?;
596 }
597 }
598 L::Record(fields) => {
599 for f in fields {
600 collect_member_tokens(input, &f.value, members, output, budget, range)?;
601 }
602 }
603 L::Array { entries, .. } => {
604 for e in entries {
605 if let Some(key) = &e.key {
606 collect_member_tokens(input, key, members, output, budget, range)?;
607 }
608 collect_member_tokens(input, &e.value, members, output, budget, range)?;
609 }
610 }
611 _ => {}
612 }
613 Ok(())
614}
615fn member_token_range(
616 input: &Input,
617 value: &LiteralValue<'_>,
618 budget: &mut Budget,
619 range: &Range<usize>,
620) -> Result<Range<usize>, QueryError> {
621 let tokens = input.syntax().tokens();
622 let start =
623 tokens.partition_point(|t| t.range.end + input.syntax_offset() <= value.range.start);
624 for (i, token) in tokens.iter().enumerate().skip(start) {
625 charge(budget, 1, range)?;
626 if token.range.start + input.syntax_offset() >= value.range.end {
627 break;
628 }
629 if matches!(token.kind, T::Identifier | T::QuotedIdentifier) {
630 return Ok(input.token_range(i));
631 }
632 }
633 Err(QueryError::input(
634 value.range.clone(),
635 "member has no identifier token",
636 ))
637}
638
639fn existing_names(
640 input: &Input,
641 facts: &ModelFacts<'_>,
642 budget: &mut Budget,
643 range: &Range<usize>,
644) -> Result<BTreeSet<String>, QueryError> {
645 let mut names = BTreeSet::new();
646 for (i, token) in input.syntax().tokens().iter().enumerate() {
647 charge(budget, 1 + token.range.len(), range)?;
648 if matches!(token.kind, T::Identifier | T::QuotedIdentifier) {
649 budget
650 .charge_results(1, zirql::Location::Query(range.clone()))
651 .map_err(pipeline::core_error)?;
652 names.insert(
653 crate::identifier_identity(std::str::from_utf8(input.token_bytes(i)).unwrap())
654 .to_owned(),
655 );
656 }
657 }
658 for declaration in &facts.bindings().declarations {
659 charge(budget, declaration.name.len() + 1, range)?;
660 budget
661 .charge_results(1, zirql::Location::Query(range.clone()))
662 .map_err(pipeline::core_error)?;
663 names.insert(declaration.name.clone());
664 }
665 Ok(names)
666}
667
668pub(crate) struct EnumScale {
669 declaration: DeclarationId,
670 pub(crate) edit: Option<TextEdit>,
671}
672
673struct ConstructorDomain<'a> {
675 name: &'a str,
676 name_range: Range<usize>,
677 bound_range: Range<usize>,
678 count: i64,
679}
680
681fn constructor_domain<'a>(
682 input: &'a Input,
683 rhs: &SyntaxNode,
684 budget: &mut Budget,
685 range: &Range<usize>,
686) -> Result<ConstructorDomain<'a>, QueryError> {
687 let rhs = structured::unwrapped(rhs);
688 let invalid = || {
689 QueryError::input(
690 input.original_range(rhs.range()),
691 "scale_enum requires a named constructor C(1..N) with a positive literal integer N",
692 )
693 };
694 let mut significant = Vec::new();
695 let tokens = input.syntax().tokens();
696 let start = tokens.partition_point(|token| token.range.start < rhs.range().start);
697 for (offset, token) in tokens[start..]
698 .iter()
699 .enumerate()
700 .take_while(|(_, t)| t.range.end <= rhs.range().end)
701 {
702 charge(budget, 1, range)?;
703 if !matches!(token.kind, T::Whitespace | T::LineComment | T::BlockComment) {
704 if significant.len() == 6 {
705 return Err(invalid());
706 }
707 significant.push(start + offset);
708 }
709 }
710 let [name, left, low, dots, high, right] = significant.as_slice() else {
711 return Err(invalid());
712 };
713 if !matches!(tokens[*name].kind, T::Identifier | T::QuotedIdentifier)
714 || tokens[*low].kind != T::IntegerLiteral
715 || tokens[*high].kind != T::IntegerLiteral
716 || input.token_bytes(*left) != b"("
717 || input.token_bytes(*dots) != b".."
718 || input.token_bytes(*right) != b")"
719 {
720 return Err(invalid());
721 }
722 let integer = |index, budget: &mut Budget| {
723 let literal = LiteralValue {
724 file: "",
725 range: input.token_range(index),
726 spelling: input.token_bytes(index),
727 kind: L::Integer,
728 };
729 comprehension::written_scalar(&literal, input, budget)
730 };
731 let zirql::ConstructedValue::Integer(count) = integer(*high, budget)? else {
732 return Err(invalid());
733 };
734 if integer(*low, budget)? != zirql::ConstructedValue::Integer(1) || count <= 0 {
735 return Err(invalid());
736 }
737 let name_text =
738 crate::identifier_identity(std::str::from_utf8(input.token_bytes(*name)).unwrap());
739 if name_text == "anon_enum" {
740 return Err(invalid());
741 }
742 Ok(ConstructorDomain {
743 name: name_text,
744 name_range: input.token_range(*name),
745 bound_range: input.token_range(*high),
746 count,
747 })
748}
749
750pub(crate) fn prepare_scale(
751 input: &Input,
752 facts: &ModelFacts<'_>,
753 edit: &data::Edit,
754 factor: &zirql::ConstructedValue,
755 budget: &mut Budget,
756) -> Result<EnumScale, QueryError> {
757 let zirql::ConstructedValue::Integer(factor) = factor.data() else {
758 return Err(QueryError::query(
759 edit.range.clone(),
760 "scale_enum factor must be a positive integer",
761 ));
762 };
763 if *factor <= 0 {
764 return Err(QueryError::query(
765 edit.range.clone(),
766 "scale_enum factor must be a positive integer",
767 ));
768 }
769 let declaration = uninitialized(
770 facts,
771 &edit.name.value,
772 DeclarationRole::Enum,
773 budget,
774 &edit.name.range,
775 )?;
776 let rhs = crate::assignment_rhs(assignment(
777 input,
778 &edit.name.value,
779 budget,
780 &edit.name.range,
781 )?)
782 .unwrap();
783 let domain = constructor_domain(input, rhs, budget, &edit.range)?;
784 let count = domain.count.checked_mul(*factor).ok_or_else(|| {
785 QueryError::query(
786 edit.range.clone(),
787 "scale_enum bound multiplication overflow",
788 )
789 })?;
790 for (i, token) in input.syntax().tokens().iter().enumerate() {
791 charge(budget, 1, &edit.range)?;
792 if matches!(token.kind, T::Identifier | T::QuotedIdentifier)
793 && input.token_range(i) != domain.name_range
794 && crate::identifier_identity(std::str::from_utf8(input.token_bytes(i)).unwrap())
795 == domain.name
796 {
797 return Err(QueryError::input(
798 input.token_range(i),
799 "scale_enum cannot remap constructor references outside its defining assignment",
800 ));
801 }
802 }
803 charge(budget, 22, &edit.range)?;
804 Ok(EnumScale {
805 declaration,
806 edit: (count != domain.count).then(|| TextEdit {
807 range: domain.bound_range,
808 replacement: vec![EditPart::Text(count.to_string())],
809 }),
810 })
811}
812
813#[allow(clippy::too_many_arguments)]
815pub(crate) fn prepare_appends<'a>(
816 input: &Input,
817 facts: &ModelFacts<'a>,
818 copies: &[(&Located<String>, Rc<SourceCopies<'a>>)],
819 edits: &[&data::Edit],
820 scales: &[EnumScale],
821 budget: &mut Budget,
822 limits: Limits,
823 range: &Range<usize>,
824) -> Result<Vec<TextEdit>, QueryError> {
825 if copies.is_empty() && scales.is_empty() {
826 return Ok(Vec::new());
827 }
828 let mut names = existing_names(input, facts, budget, range)?;
829 let mut changed: Vec<_> = scales.iter().map(|scale| scale.declaration).collect();
830 for (target, copy) in copies {
831 admit_owner(copy, input, facts, range)?;
832 if let CopyKind::Members { declaration, maps } = ©.kind {
833 if facts.bindings().declarations[declaration.0].name != target.value {
834 return Err(QueryError::query(
835 target.range.clone(),
836 "member copies must append to their own enum",
837 ));
838 }
839 if !maps.is_empty() {
840 changed.push(*declaration);
841 }
842 for map in maps {
843 for name in map.values() {
844 charge(budget, name.len() + 1, range)?;
845 budget
846 .charge_results(1, zirql::Location::Query(range.clone()))
847 .map_err(pipeline::core_error)?;
848 if !names.insert(name.clone()) {
849 return Err(QueryError::query(
850 target.range.clone(),
851 format!("generated member collision: {name}"),
852 ));
853 }
854 }
855 }
856 }
857 }
858 for edit in edits {
859 let id = comprehension::unique_declaration(
860 facts,
861 &edit.name.value,
862 DeclarationRole::Value,
863 budget,
864 &edit.name.range,
865 )?
866 .id;
867 let shape = reduction::declaration_shape(facts, id, &mut Work { budget }, range, limits)?;
868 if changed.iter().any(|id| shape.contains_enum(Some(*id))) {
869 return Err(QueryError::query(
870 edit.name.range.clone(),
871 "constructed edits depending on a changed enum cannot share a source-copy batch",
872 ));
873 }
874 }
875 let mut result = Vec::new();
876 for (target, copy) in copies {
877 let rhs = structured::unwrapped(
878 crate::assignment_rhs(assignment(input, &target.value, budget, &target.range)?)
879 .unwrap(),
880 );
881 let (inside, array) = interior(input, rhs)?;
882 match ©.kind {
883 CopyKind::Members { .. } if array => {
884 return Err(QueryError::query(
885 target.range.clone(),
886 "enum append requires a member set",
887 ));
888 }
889 CopyKind::Elements { array: expected } => {
890 uninitialized(
891 facts,
892 &target.value,
893 DeclarationRole::Value,
894 budget,
895 &target.range,
896 )?;
897 if array != *expected {
898 return Err(QueryError::query(
899 target.range.clone(),
900 "append collection kinds differ",
901 ));
902 }
903 }
904 _ => {}
905 }
906 if copy.parts.is_empty() {
907 continue;
908 }
909 let mut replacement = Vec::new();
910 charge(budget, inside.len() + copy.parts.len(), range)?;
911 let nonempty = rhs.child_nodes().next().is_some();
912 if nonempty && !trailing_comma(input, &inside) {
913 replacement.push(EditPart::Text(",".into()));
914 }
915 budget
916 .charge_results(copy.parts.len() + 2, zirql::Location::Query(range.clone()))
917 .map_err(pipeline::core_error)?;
918 for part in ©.parts {
919 charge(
920 budget,
921 match part {
922 EditPart::Text(s) => s.len(),
923 EditPart::Original(r) => r.len(),
924 },
925 range,
926 )?;
927 replacement.push(part.clone());
928 }
929 result.push(TextEdit {
930 range: inside.end..inside.end,
931 replacement,
932 });
933 }
934 Ok(result)
935}
936
937pub(crate) fn validate_candidate(
938 candidate: &Input,
939 facts: &ModelFacts<'_>,
940 copies: &[(&Located<String>, Rc<SourceCopies<'_>>)],
941 scales: &[EnumScale],
942 budget: &mut Budget,
943 limits: Limits,
944 range: &Range<usize>,
945) -> Result<(), QueryError> {
946 if copies.is_empty() && scales.is_empty() {
947 return Ok(());
948 }
949 charge(budget, copies.len() + scales.len(), range)?;
950 budget
951 .charge_results(copies.len(), zirql::Location::Query(range.clone()))
952 .map_err(pipeline::core_error)?;
953 let changed: Vec<_> = copies
954 .iter()
955 .filter_map(|(_, c)| match &c.kind {
956 CopyKind::Members { declaration, maps } if !maps.is_empty() => Some(*declaration),
957 _ => None,
958 })
959 .chain(scales.iter().map(|scale| scale.declaration))
960 .collect();
961 let mut checker = Validator::new(candidate, facts, budget, limits, range);
962 for node in candidate.syntax().tree().child_nodes() {
963 charge(checker.budget, 1, range)?;
964 let Some(rhs) = crate::assignment_rhs(node) else {
965 continue;
966 };
967 let token = structured::direct_token(candidate, node).unwrap();
968 let name =
969 crate::identifier_identity(std::str::from_utf8(candidate.token_bytes(token)).unwrap());
970 let targeted = copies.iter().any(|(target, _)| target.value == name);
971 charge(checker.budget, facts.bindings().declarations.len(), range)?;
972 let mut declarations = facts
973 .bindings()
974 .declarations
975 .iter()
976 .filter(|d| d.top_level && d.name == name);
977 let declaration = declarations.next();
978 if declaration.is_none() || declarations.next().is_some() {
979 return Err(QueryError::input(
980 candidate.original_range(rhs.range()),
981 "data declaration identity is unavailable or ambiguous",
982 ));
983 }
984 let declaration = declaration.unwrap();
985 if declaration.role == DeclarationRole::Enum {
986 if targeted {
987 checker.members(declaration.id)?;
988 }
989 continue;
990 }
991 if declaration.role != DeclarationRole::Value {
992 continue;
993 }
994 let shape = reduction::declaration_shape(
995 facts,
996 declaration.id,
997 &mut Work {
998 budget: checker.budget,
999 },
1000 range,
1001 limits,
1002 )?;
1003 if targeted || changed.iter().any(|id| shape.contains_enum(Some(*id))) {
1004 assignment(candidate, name, checker.budget, range)?;
1005 let value = structured::read_literal(
1006 candidate,
1007 rhs,
1008 &mut Work {
1009 budget: checker.budget,
1010 },
1011 range,
1012 limits,
1013 )?;
1014 let metadata =
1015 comprehension::declared_value(facts, name, checker.budget, range)?.unwrap();
1016 checker.validate(&value, &shape, metadata, 0)?;
1017 }
1018 }
1019 Ok(())
1020}
1021enum Axis {
1022 Keys(Vec<String>),
1023 Cardinality(usize),
1024}
1025impl Axis {
1026 fn len(&self) -> usize {
1027 match self {
1028 Self::Keys(keys) => keys.len(),
1029 Self::Cardinality(n) => *n,
1030 }
1031 }
1032}
1033struct Validator<'a, 'm, 'model, 'b> {
1034 input: &'a Input,
1035 facts: &'m ModelFacts<'model>,
1036 budget: &'b mut Budget,
1037 limits: Limits,
1038 range: &'b Range<usize>,
1039 enums: BTreeMap<usize, Vec<String>>,
1040 remap: Option<DeclarationId>,
1041 replacements: Vec<(Range<usize>, String)>,
1042}
1043impl<'a, 'm, 'model, 'b> Validator<'a, 'm, 'model, 'b> {
1044 fn new(
1045 input: &'a Input,
1046 facts: &'m ModelFacts<'model>,
1047 budget: &'b mut Budget,
1048 limits: Limits,
1049 range: &'b Range<usize>,
1050 ) -> Self {
1051 Self {
1052 input,
1053 facts,
1054 budget,
1055 limits,
1056 range,
1057 enums: BTreeMap::new(),
1058 remap: None,
1059 replacements: Vec::new(),
1060 }
1061 }
1062 fn members(&mut self, id: DeclarationId) -> Result<Vec<String>, QueryError> {
1063 if let Some(members) = self.enums.get(&id.0) {
1064 charge(
1065 self.budget,
1066 members.iter().map(|m| m.len() + 1).sum(),
1067 self.range,
1068 )?;
1069 self.budget
1070 .charge_results(members.len(), zirql::Location::Query(self.range.clone()))
1071 .map_err(pipeline::core_error)?;
1072 return Ok(members.clone());
1073 }
1074 let declaration = &self.facts.bindings().declarations[id.0];
1075 comprehension::unique_declaration(
1076 self.facts,
1077 &declaration.name,
1078 DeclarationRole::Enum,
1079 self.budget,
1080 self.range,
1081 )?;
1082 let node =
1083 comprehension::declaration_node(self.facts, id, self.budget, self.range)?.unwrap();
1084 let members = if node.child_nodes().any(|n| n.kind() != N::Annotation) {
1085 comprehension::enum_cases(self.facts, declaration, self.budget, self.range)?
1086 .iter()
1087 .map(|case| case.name.clone())
1088 .collect::<Vec<_>>()
1089 } else {
1090 let rhs = crate::assignment_rhs(assignment(
1091 self.input,
1092 &declaration.name,
1093 self.budget,
1094 self.range,
1095 )?)
1096 .unwrap();
1097 let value = structured::read_literal(
1098 self.input,
1099 rhs,
1100 &mut Work {
1101 budget: self.budget,
1102 },
1103 self.range,
1104 self.limits,
1105 )?;
1106 let L::Set(values) = value.kind else {
1107 return Err(QueryError::input(
1108 value.range,
1109 "enum membership requires a literal member set",
1110 ));
1111 };
1112 let mut members = Vec::new();
1113 let mut seen = HashSet::new();
1114 for value in values {
1115 charge(self.budget, value.spelling.len() + 1, self.range)?;
1116 let L::Member(name) = value.kind else {
1117 return Err(QueryError::input(
1118 value.range,
1119 "enum set requires member identifiers",
1120 ));
1121 };
1122 if !seen.insert(name) {
1123 return Err(QueryError::input(value.range, "duplicate enum member"));
1124 }
1125 members.push(name.to_owned());
1126 }
1127 members
1128 };
1129 charge(
1130 self.budget,
1131 members.iter().map(|m| m.len() + 1).sum(),
1132 self.range,
1133 )?;
1134 self.budget
1135 .charge_results(members.len(), zirql::Location::Query(self.range.clone()))
1136 .map_err(pipeline::core_error)?;
1137 self.enums.insert(id.0, members.clone());
1138 Ok(members)
1139 }
1140 fn member_token(
1141 &mut self,
1142 value: &LiteralValue<'_>,
1143 id: DeclarationId,
1144 name: &str,
1145 ) -> Result<(), QueryError> {
1146 if self.remap == Some(id) {
1147 charge(self.budget, name.len() + 1, self.range)?;
1148 self.budget
1149 .charge_results(1, zirql::Location::Query(self.range.clone()))
1150 .map_err(pipeline::core_error)?;
1151 self.replacements.push((
1152 member_token_range(self.input, value, self.budget, self.range)?,
1153 name.to_owned(),
1154 ));
1155 }
1156 Ok(())
1157 }
1158 fn enum_axis(&mut self, id: DeclarationId) -> Result<Axis, QueryError> {
1159 let declaration = &self.facts.bindings().declarations[id.0];
1160 let node =
1161 comprehension::declaration_node(self.facts, id, self.budget, self.range)?.unwrap();
1162 if !node.child_nodes().any(|n| n.kind() != N::Annotation) {
1163 uninitialized(
1164 self.facts,
1165 &declaration.name,
1166 DeclarationRole::Enum,
1167 self.budget,
1168 self.range,
1169 )?;
1170 let rhs = crate::assignment_rhs(assignment(
1171 self.input,
1172 &declaration.name,
1173 self.budget,
1174 self.range,
1175 )?)
1176 .unwrap();
1177 if structured::unwrapped(rhs).kind() != N::SetLiteral {
1178 let domain = constructor_domain(self.input, rhs, self.budget, self.range)?;
1179 let count = usize::try_from(domain.count).map_err(|_| {
1180 QueryError::input(domain.bound_range, "constructor cardinality overflow")
1181 })?;
1182 return Ok(Axis::Cardinality(count));
1183 }
1184 }
1185 self.members(id).map(Axis::Keys)
1186 }
1187 fn axis(&mut self, domain: &Domain) -> Result<Option<Axis>, QueryError> {
1188 charge(self.budget, 1, self.range)?;
1189 match domain {
1190 Domain::Enum(id) => self.enum_axis(*id).map(Some),
1191 Domain::Named {
1192 declaration,
1193 domain,
1194 } => {
1195 if self.facts.bindings().declarations[declaration.0].role == DeclarationRole::Enum {
1196 self.enum_axis(*declaration).map(Some)
1197 } else {
1198 self.axis(domain)
1199 }
1200 }
1201 Domain::UnconstrainedInt => Ok(None),
1202 Domain::Range { lower, upper } => {
1203 let low = comprehension::integer_bound(
1204 lower,
1205 self.input,
1206 &BTreeMap::new(),
1207 self.facts,
1208 self.budget,
1209 self.range,
1210 )?;
1211 let high = comprehension::integer_bound(
1212 upper,
1213 self.input,
1214 &BTreeMap::new(),
1215 self.facts,
1216 self.budget,
1217 self.range,
1218 )?;
1219 let n = high
1220 .checked_sub(low)
1221 .and_then(|n| n.checked_add(1))
1222 .unwrap_or(i64::MAX)
1223 .max(0);
1224 let n = usize::try_from(n)
1225 .map_err(|_| QueryError::query(self.range.clone(), "axis size overflow"))?;
1226 charge(self.budget, n.saturating_mul(21), self.range)?;
1227 self.budget
1228 .charge_results(n, zirql::Location::Query(self.range.clone()))
1229 .map_err(pipeline::core_error)?;
1230 if n > self.limits.collection {
1231 return Err(QueryError::query(
1232 self.range.clone(),
1233 "query collection limit exceeded",
1234 ));
1235 }
1236 Ok(Some(Axis::Keys(
1237 (low..=high).map(|n| n.to_string()).collect(),
1238 )))
1239 }
1240 _ => Err(QueryError::query(
1241 self.range.clone(),
1242 "source-copy validation requires established literal array domains",
1243 )),
1244 }
1245 }
1246 fn validate(
1247 &mut self,
1248 value: &LiteralValue<'_>,
1249 shape: &reduction::Shape,
1250 metadata: comprehension::LiteralMetadata<'_>,
1251 depth: usize,
1252 ) -> Result<(), QueryError> {
1253 charge(self.budget, 1, self.range)?;
1254 let metadata = comprehension::resolve_metadata(
1255 metadata,
1256 self.facts,
1257 self.budget,
1258 self.range,
1259 self.limits.nesting,
1260 )?;
1261 let invalid = || {
1262 QueryError::input(
1263 value.range.clone(),
1264 "literal does not satisfy its retained model type or final domain",
1265 )
1266 };
1267 if depth >= self.limits.nesting.min(64)
1268 || shape.ty.optional
1269 || shape.ty.instantiation != Instantiation::Parameter
1270 {
1271 return Err(invalid());
1272 }
1273 match (&value.kind, &shape.ty.kind) {
1274 (L::Integer, TypeKind::Int | TypeKind::Float) | (L::Float, TypeKind::Float) => {
1275 let number = comprehension::written_scalar(value, self.input, self.budget)?;
1276 data::validate_numeric_domain(
1277 &number,
1278 metadata,
1279 self.input,
1280 &BTreeMap::new(),
1281 self.facts,
1282 self.budget,
1283 self.range,
1284 )
1285 .map_err(|mut error| {
1286 if matches!(error.location, crate::ErrorLocation::Query) {
1287 error.location = crate::ErrorLocation::Input;
1288 error.range = value.range.clone();
1289 }
1290 error
1291 })
1292 }
1293 (L::Boolean(_), TypeKind::Bool) | (L::String, TypeKind::String) => Ok(()),
1294 (L::Member(name), TypeKind::Enum(id)) => {
1295 let mut domain = metadata
1298 .node
1299 .filter(|node| node.kind() == N::DomainType)
1300 .and_then(|node| node.child_nodes().next());
1301 while let Some(node) = domain
1302 && node.kind() == N::ParenthesizedExpression
1303 {
1304 charge(self.budget, 1 + node.children().len(), self.range)?;
1305 domain = node.child_nodes().next();
1306 }
1307 let location = domain
1308 .filter(|node| node.kind() == N::Expression)
1309 .map(|node| self.facts.model().files[metadata.file].location(node.range()));
1310 let mut full_enum = false;
1311 for reference in &self.facts.bindings().references {
1312 charge(self.budget, 1, self.range)?;
1313 if reference.file == metadata.file
1314 && location.as_ref().is_some_and(|location| {
1315 location.range.start <= reference.location.range.start
1316 && reference.location.range.end <= location.range.end
1317 })
1318 && reference.resolution == zincite_lint::BindingResolution::Resolved(*id)
1319 {
1320 full_enum = true;
1321 break;
1322 }
1323 }
1324 if !full_enum {
1325 return Err(QueryError::input(
1326 value.range.clone(),
1327 "source-copy validation requires a full enum domain; enum subsets are unsupported",
1328 ));
1329 }
1330 if !self.members(*id)?.iter().any(|m| m == name) {
1331 return Err(invalid());
1332 }
1333 self.member_token(value, *id, name)?;
1334 Ok(())
1335 }
1336 (L::Set(values), TypeKind::Set(_)) => {
1337 for v in values {
1338 self.validate(
1339 v,
1340 &shape.components[0].1,
1341 comprehension::child_metadata(
1342 Some(metadata),
1343 &shape.components[0].1.ty,
1344 None,
1345 ),
1346 depth + 1,
1347 )?;
1348 }
1349 Ok(())
1350 }
1351 (L::Tuple(values), TypeKind::Tuple(_)) if values.len() == shape.components.len() => {
1352 for (i, (v, (_, s))) in values.iter().zip(&shape.components).enumerate() {
1353 self.validate(
1354 v,
1355 s,
1356 comprehension::child_metadata(Some(metadata), &s.ty, Some(i)),
1357 depth + 1,
1358 )?;
1359 }
1360 Ok(())
1361 }
1362 (L::Record(fields), TypeKind::Record(_)) if fields.len() == shape.components.len() => {
1363 let mut seen = HashSet::new();
1364 for f in fields {
1365 charge(
1366 self.budget,
1367 shape.components.len() + f.label.name.len(),
1368 self.range,
1369 )?;
1370 self.budget
1371 .charge_results(1, zirql::Location::Query(self.range.clone()))
1372 .map_err(pipeline::core_error)?;
1373 let Some((_, s)) = shape
1374 .components
1375 .iter()
1376 .find(|(name, _)| name.as_deref() == Some(f.label.name))
1377 else {
1378 return Err(invalid());
1379 };
1380 if !seen.insert(f.label.name) {
1381 return Err(invalid());
1382 }
1383 let child = data::record_metadata(
1384 metadata,
1385 f.label.name,
1386 &s.ty,
1387 self.facts,
1388 self.budget,
1389 self.range,
1390 )?;
1391 self.validate(&f.value, s, child, depth + 1)?;
1392 }
1393 Ok(())
1394 }
1395 (L::Array { indexing, entries }, TypeKind::Array { indices, .. })
1396 if indices.len() == 1 && *indexing != structured::ArrayIndexing::StartingKey =>
1397 {
1398 let axis = self.axis(&shape.indices[0])?;
1399 if let Some(axis) = &axis
1400 && axis.len() != entries.len()
1401 {
1402 return Err(QueryError::input(
1403 value.range.clone(),
1404 "array coverage differs from the final domain; append every affected assignment",
1405 ));
1406 }
1407 let mut keys = BTreeSet::new();
1408 for entry in entries {
1409 if let Some(key) = &entry.key {
1410 if matches!(axis, Some(Axis::Cardinality(_))) {
1411 return Err(QueryError::input(
1412 key.range.clone(),
1413 "constructor-indexed arrays require positional entries",
1414 ));
1415 }
1416 charge(
1417 self.budget,
1418 key.spelling.len().saturating_mul(2)
1419 + axis.as_ref().map_or(0, Axis::len)
1420 + 1,
1421 self.range,
1422 )?;
1423 self.budget
1424 .charge_results(2, zirql::Location::Query(self.range.clone()))
1425 .map_err(pipeline::core_error)?;
1426 if let (L::Member(name), TypeKind::Enum(id)) = (&key.kind, &indices[0].kind)
1427 {
1428 self.member_token(key, *id, name)?;
1429 }
1430 let key = match &key.kind {
1431 L::Member(name) if matches!(indices[0].kind, TypeKind::Enum(_)) => {
1432 (*name).to_owned()
1433 }
1434 L::Integer if indices[0].kind == TypeKind::Int => {
1435 match comprehension::written_scalar(key, self.input, self.budget)? {
1436 zirql::ConstructedValue::Integer(n) => n.to_string(),
1437 _ => return Err(invalid()),
1438 }
1439 }
1440 _ => return Err(invalid()),
1441 };
1442 if !keys.insert(key.clone())
1443 || axis.as_ref().is_some_and(
1444 |axis| matches!(axis, Axis::Keys(keys) if !keys.contains(&key)),
1445 )
1446 {
1447 return Err(invalid());
1448 }
1449 }
1450 self.validate(
1451 &entry.value,
1452 &shape.components[0].1,
1453 comprehension::child_metadata(
1454 Some(metadata),
1455 &shape.components[0].1.ty,
1456 None,
1457 ),
1458 depth + 1,
1459 )?;
1460 }
1461 if *indexing == structured::ArrayIndexing::Explicit && axis.is_none() {
1462 charge(
1463 self.budget,
1464 keys.len()
1465 .saturating_mul(keys.len().max(1).ilog2() as usize + 1),
1466 self.range,
1467 )?;
1468 self.budget
1469 .charge_results(keys.len(), zirql::Location::Query(self.range.clone()))
1470 .map_err(pipeline::core_error)?;
1471 let mut integers = keys
1472 .iter()
1473 .map(|key| key.parse::<i64>().map_err(|_| invalid()))
1474 .collect::<Result<Vec<_>, _>>()?;
1475 integers.sort_unstable();
1476 if integers
1477 .windows(2)
1478 .any(|pair| pair[0].checked_add(1) != Some(pair[1]))
1479 {
1480 return Err(invalid());
1481 }
1482 }
1483 Ok(())
1484 }
1485 _ => Err(invalid()),
1486 }
1487 }
1488}
1489
1490impl SourceCopies<'_> {
1491 pub(super) fn kind(&self) -> Kind {
1492 match self.kind {
1493 CopyKind::Members { .. } => Kind::MemberCopies,
1494 CopyKind::Elements { .. } => Kind::SourceCopies,
1495 }
1496 }
1497}