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