1
pub mod serde;
2

            
3
use std::any::TypeId;
4
// allow use of Declaration in this file, and nowhere else
5
use std::cell::{Cell, Ref, RefCell, RefMut};
6
use std::fmt::{Debug, Display};
7
use std::rc::Rc;
8

            
9
use ::serde::{Deserialize, Serialize};
10

            
11
use super::categories::{Category, CategoryOf};
12
use super::name::Name;
13
use super::serde::{DefaultWithId, HasId, ObjId};
14
use super::{
15
    DecisionVariable, DomainPtr, Expression, GroundDomain, Moo, RecordEntry, ReturnType, Typeable,
16
};
17
use crate::ast::domains::HasDomain;
18
use uniplate::{Biplate, Tree, Uniplate};
19

            
20
thread_local! {
21
    // make each thread have its own id counter.
22
    static DECLARATION_PTR_ID_COUNTER: Cell<u32> = const { Cell::new(0) };
23

            
24
    // We run integration tests in parallel threads - making this thread local ensures that
25
    // declarations in a test always have the same id, instead of the ids depending on how many
26
    // threads are running, how they are scheduled, etc.
27
}
28

            
29
#[doc(hidden)]
30
/// Resets the id counter of `DeclarationPtr` to 0.
31
///
32
/// This is probably always a bad idea.
33
pub fn reset_declaration_id_unchecked() {
34
    DECLARATION_PTR_ID_COUNTER.set(0);
35
}
36

            
37
/// A shared pointer to a [`Declaration`].
38
///
39
/// Two declaration pointers are equal if they point to the same underlying declaration.
40
///
41
/// # Id
42
///
43
///  The id of `DeclarationPtr` obeys the following invariants:
44
///
45
/// 1. Declaration pointers have the same id if they point to the same
46
///    underlying declaration.
47
///
48
/// 2. The id is immutable.
49
///
50
/// 3. Changing the declaration pointed to by the declaration pointer does not change the id. This
51
///    allows declarations to be updated by replacing them with a newer version of themselves.
52
///
53
/// `Ord`, `Hash`, and `Eq` use id for comparisons.
54
/// # Serde
55
///
56
/// Declaration pointers can be serialised using the following serializers:
57
///
58
/// + [`DeclarationPtrFull`](serde::DeclarationPtrFull)
59
/// + [`DeclarationPtrAsId`](serde::DeclarationPtrAsId)
60
///
61
/// See their documentation for more information.
62
#[derive(Clone, Debug)]
63
pub struct DeclarationPtr {
64
    // the shared bits of the pointer
65
    inner: Rc<DeclarationPtrInner>,
66
}
67

            
68
// The bits of a declaration that are shared between all pointers.
69
#[derive(Clone, Debug)]
70
struct DeclarationPtrInner {
71
    // We don't want this to be mutable, as `HashMap` and `BTreeMap` rely on the hash or order of
72
    // keys to be unchanging.
73
    //
74
    // See:  https://rust-lang.github.io/rust-clippy/master/index.html#mutable_key_type
75
    id: ObjId,
76

            
77
    // The contents of the declaration itself should be mutable.
78
    value: RefCell<Declaration>,
79
}
80

            
81
impl DeclarationPtrInner {
82
    fn new(value: RefCell<Declaration>) -> Rc<DeclarationPtrInner> {
83
        Rc::new(DeclarationPtrInner {
84
            id: DECLARATION_PTR_ID_COUNTER.replace(DECLARATION_PTR_ID_COUNTER.get() + 1),
85
            value,
86
        })
87
    }
88

            
89
    // SAFETY: only use if you are really really sure you arn't going to break the id invariants of
90
    // DeclarationPtr and HasId!
91
    fn new_with_id_unchecked(value: RefCell<Declaration>, id: ObjId) -> Rc<DeclarationPtrInner> {
92
        Rc::new(DeclarationPtrInner { id, value })
93
    }
94
}
95

            
96
#[allow(dead_code)]
97
impl DeclarationPtr {
98
    /******************************/
99
    /*        Constructors        */
100
    /******************************/
101

            
102
    /// Creates a `DeclarationPtr` for the given `Declaration`.
103
    fn from_declaration(declaration: Declaration) -> DeclarationPtr {
104
        DeclarationPtr {
105
            inner: DeclarationPtrInner::new(RefCell::new(declaration)),
106
        }
107
    }
108

            
109
    /// Creates a new declaration.
110
    ///
111
    /// # Examples
112
    ///
113
    /// ```
114
    /// use conjure_cp_core::ast::{DeclarationPtr,Name,DeclarationKind,Domain,Range};
115
    ///
116
    /// // letting MyDomain be int(1..5)
117
    /// let declaration = DeclarationPtr::new(
118
    ///     Name::User("MyDomain".into()),
119
    ///     DeclarationKind::DomainLetting(Domain::int(vec![
120
    ///         Range::Bounded(1,5)])));
121
    /// ```
122
    pub fn new(name: Name, kind: DeclarationKind) -> DeclarationPtr {
123
        DeclarationPtr::from_declaration(Declaration::new(name, kind))
124
    }
125

            
126
    /// Creates a new decision variable declaration with the decision category.
127
    ///
128
    /// # Examples
129
    ///
130
    /// ```
131
    /// use conjure_cp_core::ast::{DeclarationPtr,Name,DeclarationKind,Domain,Range};
132
    ///
133
    /// // find x: int(1..5)
134
    /// let declaration = DeclarationPtr::new_var(
135
    ///     Name::User("x".into()),
136
    ///     Domain::int(vec![Range::Bounded(1,5)]));
137
    ///
138
    /// ```
139
    pub fn new_var(name: Name, domain: DomainPtr) -> DeclarationPtr {
140
        let kind =
141
            DeclarationKind::DecisionVariable(DecisionVariable::new(domain, Category::Decision));
142
        DeclarationPtr::new(name, kind)
143
    }
144

            
145
    /// Creates a new decision variable with the quantified category.
146
    ///
147
    /// This is useful to represent a quantified / induction variable in a comprehension.
148
    pub fn new_var_quantified(name: Name, domain: DomainPtr) -> DeclarationPtr {
149
        let kind =
150
            DeclarationKind::DecisionVariable(DecisionVariable::new(domain, Category::Quantified));
151

            
152
        DeclarationPtr::new(name, kind)
153
    }
154

            
155
    /// Creates a new domain letting declaration.
156
    ///
157
    /// # Examples
158
    ///
159
    /// ```
160
    /// use conjure_cp_core::ast::{DeclarationPtr,Name,DeclarationKind,Domain,Range};
161
    ///
162
    /// // letting MyDomain be int(1..5)
163
    /// let declaration = DeclarationPtr::new_domain_letting(
164
    ///     Name::User("MyDomain".into()),
165
    ///     Domain::int(vec![Range::Bounded(1,5)]));
166
    ///
167
    /// ```
168
    pub fn new_domain_letting(name: Name, domain: DomainPtr) -> DeclarationPtr {
169
        let kind = DeclarationKind::DomainLetting(domain);
170
        DeclarationPtr::new(name, kind)
171
    }
172

            
173
    /// Creates a new value letting declaration.
174
    ///
175
    /// # Examples
176
    ///
177
    /// ```
178
    /// use conjure_cp_core::ast::{DeclarationPtr,Name,DeclarationKind,Domain,Range, Expression,
179
    /// Literal,Atom,Moo};
180
    /// use conjure_cp_core::{matrix_expr,ast::Metadata};
181
    ///
182
    /// // letting n be 10 + 10
183
    /// let ten = Expression::Atomic(Metadata::new(),Atom::Literal(Literal::Int(10)));
184
    /// let expression = Expression::Sum(Metadata::new(),Moo::new(matrix_expr![ten.clone(),ten]));
185
    /// let declaration = DeclarationPtr::new_value_letting(
186
    ///     Name::User("n".into()),
187
    ///     expression);
188
    ///
189
    /// ```
190
    pub fn new_value_letting(name: Name, expression: Expression) -> DeclarationPtr {
191
        let kind = DeclarationKind::ValueLetting(expression);
192
        DeclarationPtr::new(name, kind)
193
    }
194

            
195
    /// Creates a new given declaration.
196
    ///
197
    /// # Examples
198
    ///
199
    /// ```
200
    /// use conjure_cp_core::ast::{DeclarationPtr,Name,DeclarationKind,Domain,Range};
201
    ///
202
    /// // given n: int(1..5)
203
    /// let declaration = DeclarationPtr::new_given(
204
    ///     Name::User("n".into()),
205
    ///     Domain::int(vec![Range::Bounded(1,5)]));
206
    ///
207
    /// ```
208
    pub fn new_given(name: Name, domain: DomainPtr) -> DeclarationPtr {
209
        let kind = DeclarationKind::Given(domain);
210
        DeclarationPtr::new(name, kind)
211
    }
212

            
213
    /// Creates a new record field declaration.
214
    ///
215
    /// # Examples
216
    ///
217
    /// ```
218
    /// use conjure_cp_core::ast::{DeclarationPtr,Name,RecordEntry,Domain,Range};
219
    ///
220
    /// // create declaration for field A in `find rec: record {A: int(0..1), B: int(0..2)}`
221
    ///
222
    /// let field = RecordEntry {
223
    ///     name: Name::User("n".into()),
224
    ///     domain: Domain::int(vec![Range::Bounded(1,5)])
225
    /// };
226
    ///
227
    /// let declaration = DeclarationPtr::new_record_field(field);
228
    /// ```
229
    pub fn new_record_field(entry: RecordEntry) -> DeclarationPtr {
230
        let kind = DeclarationKind::RecordField(entry.domain);
231
        DeclarationPtr::new(entry.name, kind)
232
    }
233

            
234
    /**********************************************/
235
    /*        Declaration accessor methods        */
236
    /**********************************************/
237

            
238
    /// Gets the domain of the declaration, if it has one.
239
    ///
240
    /// # Examples
241
    ///
242
    /// ```
243
    /// use conjure_cp_core::ast::{DeclarationPtr, Name, Domain, Range, GroundDomain};
244
    ///
245
    /// // find a: int(1..5)
246
    /// let declaration = DeclarationPtr::new_var(Name::User("a".into()),Domain::int(vec![Range::Bounded(1,5)]));
247
    ///
248
    /// assert!(declaration.domain().is_some_and(|x| x.as_ground().unwrap() == &GroundDomain::Int(vec![Range::Bounded(1,5)])))
249
    ///
250
    /// ```
251
    pub fn domain(&self) -> Option<DomainPtr> {
252
        match &self.kind() as &DeclarationKind {
253
            DeclarationKind::DecisionVariable(var) => Some(var.domain_of()),
254
            DeclarationKind::ValueLetting(e) => e.domain_of(),
255
            DeclarationKind::DomainLetting(domain) => Some(domain.clone()),
256
            DeclarationKind::Given(domain) => Some(domain.clone()),
257
            DeclarationKind::RecordField(domain) => Some(domain.clone()),
258
        }
259
    }
260

            
261
    /// Gets the domain of the declaration and fully resolve it
262
    pub fn resolved_domain(&self) -> Option<Moo<GroundDomain>> {
263
        self.domain()?.resolve()
264
    }
265

            
266
    /// Gets the kind of the declaration.
267
    ///
268
    /// # Examples
269
    ///
270
    /// ```
271
    /// use conjure_cp_core::ast::{DeclarationPtr,DeclarationKind,Name,Domain,Range};
272
    ///
273
    /// // find a: int(1..5)
274
    /// let declaration = DeclarationPtr::new_var(Name::User("a".into()),Domain::int(vec![Range::Bounded(1,5)]));
275
    /// assert!(matches!(&declaration.kind() as &DeclarationKind, DeclarationKind::DecisionVariable(_)))
276
    /// ```
277
    pub fn kind(&self) -> Ref<'_, DeclarationKind> {
278
        self.map(|x| &x.kind)
279
    }
280

            
281
    /// Gets the name of the declaration.
282
    ///
283
    /// # Examples
284
    ///
285
    /// ```
286
    /// use conjure_cp_core::ast::{DeclarationPtr,Name,Domain,Range};
287
    ///
288
    /// // find a: int(1..5)
289
    /// let declaration = DeclarationPtr::new_var(Name::User("a".into()),Domain::int(vec![Range::Bounded(1,5)]));
290
    ///
291
    /// assert_eq!(&declaration.name() as &Name, &Name::User("a".into()))
292
    /// ```
293
    pub fn name(&self) -> Ref<'_, Name> {
294
        self.map(|x| &x.name)
295
    }
296

            
297
    /// This declaration as a decision variable, if it is one.
298
    pub fn as_var(&self) -> Option<Ref<'_, DecisionVariable>> {
299
        Ref::filter_map(self.borrow(), |x| {
300
            if let DeclarationKind::DecisionVariable(var) = &x.kind {
301
                Some(var)
302
            } else {
303
                None
304
            }
305
        })
306
        .ok()
307
    }
308

            
309
    /// This declaration as a mutable decision variable, if it is one.
310
    pub fn as_var_mut(&mut self) -> Option<RefMut<'_, DecisionVariable>> {
311
        RefMut::filter_map(self.borrow_mut(), |x| {
312
            if let DeclarationKind::DecisionVariable(var) = &mut x.kind {
313
                Some(var)
314
            } else {
315
                None
316
            }
317
        })
318
        .ok()
319
    }
320

            
321
    /// This declaration as a domain letting, if it is one.
322
    pub fn as_domain_letting(&self) -> Option<Ref<'_, DomainPtr>> {
323
        Ref::filter_map(self.borrow(), |x| {
324
            if let DeclarationKind::DomainLetting(domain) = &x.kind {
325
                Some(domain)
326
            } else {
327
                None
328
            }
329
        })
330
        .ok()
331
    }
332

            
333
    /// This declaration as a mutable domain letting, if it is one.
334
    pub fn as_domain_letting_mut(&mut self) -> Option<RefMut<'_, DomainPtr>> {
335
        RefMut::filter_map(self.borrow_mut(), |x| {
336
            if let DeclarationKind::DomainLetting(domain) = &mut x.kind {
337
                Some(domain)
338
            } else {
339
                None
340
            }
341
        })
342
        .ok()
343
    }
344

            
345
    /// This declaration as a value letting, if it is one.
346
    pub fn as_value_letting(&self) -> Option<Ref<'_, Expression>> {
347
        Ref::filter_map(self.borrow(), |x| {
348
            if let DeclarationKind::ValueLetting(e) = &x.kind {
349
                Some(e)
350
            } else {
351
                None
352
            }
353
        })
354
        .ok()
355
    }
356

            
357
    /// This declaration as a mutable value letting, if it is one.
358
    pub fn as_value_letting_mut(&mut self) -> Option<RefMut<'_, Expression>> {
359
        RefMut::filter_map(self.borrow_mut(), |x| {
360
            if let DeclarationKind::ValueLetting(e) = &mut x.kind {
361
                Some(e)
362
            } else {
363
                None
364
            }
365
        })
366
        .ok()
367
    }
368

            
369
    /// Changes the name in this declaration, returning the old one.
370
    ///
371
    /// # Examples
372
    ///
373
    /// ```
374
    /// use conjure_cp_core::ast::{DeclarationPtr, Domain, Range, Name};
375
    ///
376
    /// // find a: int(1..5)
377
    /// let mut declaration = DeclarationPtr::new_var(Name::User("a".into()),Domain::int(vec![Range::Bounded(1,5)]));
378
    ///
379
    /// let old_name = declaration.replace_name(Name::User("b".into()));
380
    /// assert_eq!(old_name,Name::User("a".into()));
381
    /// assert_eq!(&declaration.name() as &Name,&Name::User("b".into()));
382
    /// ```
383
    pub fn replace_name(&mut self, name: Name) -> Name {
384
        let mut decl = self.borrow_mut();
385
        std::mem::replace(&mut decl.name, name)
386
    }
387

            
388
    /*****************************************/
389
    /*        Pointer utility methods        */
390
    /*****************************************/
391

            
392
    // These are mostly wrappers over RefCell, Ref, and RefMut methods, re-exported here for
393
    // convenience.
394

            
395
    /// Immutably borrows the declaration.
396
    fn borrow(&self) -> Ref<'_, Declaration> {
397
        // unlike refcell.borrow(), this never panics
398
        self.inner.value.borrow()
399
    }
400

            
401
    /// Mutably borrows the declaration.
402
    fn borrow_mut(&mut self) -> RefMut<'_, Declaration> {
403
        // unlike refcell.borrow_mut(), this never panics
404
        self.inner.value.borrow_mut()
405
    }
406

            
407
    /// Creates a new declaration pointer with the same contents as `self` that is not shared with
408
    /// anyone else.
409
    ///
410
    /// As the resulting pointer is unshared, it will have a new id.
411
    ///
412
    /// # Examples
413
    ///
414
    /// ```
415
    /// use conjure_cp_core::ast::{DeclarationPtr,Name,Domain,Range};
416
    ///
417
    /// // find a: int(1..5)
418
    /// let declaration = DeclarationPtr::new_var(Name::User("a".into()),Domain::int(vec![Range::Bounded(1,5)]));
419
    ///
420
    /// let mut declaration2 = declaration.clone();
421
    ///
422
    /// declaration2.replace_name(Name::User("b".into()));
423
    ///
424
    /// assert_eq!(&declaration.name() as &Name, &Name::User("b".into()));
425
    /// assert_eq!(&declaration2.name() as &Name, &Name::User("b".into()));
426
    ///
427
    /// declaration2 = declaration2.detach();
428
    ///
429
    /// assert_eq!(&declaration2.name() as &Name, &Name::User("b".into()));
430
    ///
431
    /// declaration2.replace_name(Name::User("c".into()));
432
    ///
433
    /// assert_eq!(&declaration.name() as &Name, &Name::User("b".into()));
434
    /// assert_eq!(&declaration2.name() as &Name, &Name::User("c".into()));
435
    /// ```
436
    pub fn detach(self) -> DeclarationPtr {
437
        // despite having the same contents, the new declaration pointer is unshared, so it should
438
        // get a new id.
439
        DeclarationPtr {
440
            inner: DeclarationPtrInner::new(self.inner.value.clone()),
441
        }
442
    }
443

            
444
    /// Applies `f` to the declaration, returning the result as a reference.
445
    fn map<U>(&self, f: impl FnOnce(&Declaration) -> &U) -> Ref<'_, U> {
446
        Ref::map(self.borrow(), f)
447
    }
448

            
449
    /// Applies mutable function `f` to the declaration, returning the result as a mutable reference.
450
    fn map_mut<U>(&mut self, f: impl FnOnce(&mut Declaration) -> &mut U) -> RefMut<'_, U> {
451
        RefMut::map(self.borrow_mut(), f)
452
    }
453

            
454
    /// Replaces the declaration with a new one, returning the old value, without deinitialising
455
    /// either one.
456
    fn replace(&mut self, declaration: Declaration) -> Declaration {
457
        self.inner.value.replace(declaration)
458
    }
459
}
460

            
461
impl CategoryOf for DeclarationPtr {
462
    fn category_of(&self) -> Category {
463
        match &self.kind() as &DeclarationKind {
464
            DeclarationKind::DecisionVariable(decision_variable) => decision_variable.category_of(),
465
            DeclarationKind::ValueLetting(expression) => expression.category_of(),
466
            DeclarationKind::DomainLetting(_) => Category::Constant,
467
            DeclarationKind::Given(_) => Category::Parameter,
468
            DeclarationKind::RecordField(_) => Category::Bottom,
469
        }
470
    }
471
}
472
impl HasId for DeclarationPtr {
473
    fn id(&self) -> ObjId {
474
        self.inner.id
475
    }
476
}
477

            
478
impl DefaultWithId for DeclarationPtr {
479
    fn default_with_id(id: ObjId) -> Self {
480
        DeclarationPtr {
481
            inner: DeclarationPtrInner::new_with_id_unchecked(
482
                RefCell::new(Declaration {
483
                    name: Name::User("_UNKNOWN".into()),
484
                    kind: DeclarationKind::ValueLetting(false.into()),
485
                }),
486
                id,
487
            ),
488
        }
489
    }
490
}
491

            
492
impl Typeable for DeclarationPtr {
493
    fn return_type(&self) -> ReturnType {
494
        match &self.kind() as &DeclarationKind {
495
            DeclarationKind::DecisionVariable(var) => var.return_type(),
496
            DeclarationKind::ValueLetting(expression) => expression.return_type(),
497
            DeclarationKind::DomainLetting(domain) => domain.return_type(),
498
            DeclarationKind::Given(domain) => domain.return_type(),
499
            DeclarationKind::RecordField(domain) => domain.return_type(),
500
        }
501
    }
502
}
503

            
504
impl Uniplate for DeclarationPtr {
505
    fn uniplate(&self) -> (Tree<Self>, Box<dyn Fn(Tree<Self>) -> Self>) {
506
        let decl = self.borrow();
507
        let (tree, recons) = Biplate::<DeclarationPtr>::biplate(&decl as &Declaration);
508

            
509
        let self2 = self.clone();
510
        (
511
            tree,
512
            Box::new(move |x| {
513
                let mut self3 = self2.clone();
514
                let inner = recons(x);
515
                *(&mut self3.borrow_mut() as &mut Declaration) = inner;
516
                self3
517
            }),
518
        )
519
    }
520
}
521

            
522
impl<To> Biplate<To> for DeclarationPtr
523
where
524
    Declaration: Biplate<To>,
525
    To: Uniplate,
526
{
527
    fn biplate(&self) -> (Tree<To>, Box<dyn Fn(Tree<To>) -> Self>) {
528
        if TypeId::of::<To>() == TypeId::of::<Self>() {
529
            unsafe {
530
                let self_as_to = std::mem::transmute::<&Self, &To>(self).clone();
531
                (
532
                    Tree::One(self_as_to),
533
                    Box::new(move |x| {
534
                        let Tree::One(x) = x else { panic!() };
535

            
536
                        let x_as_self = std::mem::transmute::<&To, &Self>(&x);
537
                        x_as_self.clone()
538
                    }),
539
                )
540
            }
541
        } else {
542
            // call biplate on the enclosed declaration
543
            let decl = self.borrow();
544
            let (tree, recons) = Biplate::<To>::biplate(&decl as &Declaration);
545

            
546
            let self2 = self.clone();
547
            (
548
                tree,
549
                Box::new(move |x| {
550
                    let mut self3 = self2.clone();
551
                    let inner = recons(x);
552
                    *(&mut self3.borrow_mut() as &mut Declaration) = inner;
553
                    self3
554
                }),
555
            )
556
        }
557
    }
558
}
559

            
560
impl Ord for DeclarationPtr {
561
    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
562
        self.inner.id.cmp(&other.inner.id)
563
    }
564
}
565

            
566
impl PartialOrd for DeclarationPtr {
567
    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
568
        Some(self.cmp(other))
569
    }
570
}
571

            
572
impl PartialEq for DeclarationPtr {
573
    fn eq(&self, other: &Self) -> bool {
574
        self.inner.id == other.inner.id
575
    }
576
}
577

            
578
impl Eq for DeclarationPtr {}
579

            
580
impl std::hash::Hash for DeclarationPtr {
581
    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
582
        // invariant: x == y -> hash(x) == hash(y)
583
        self.inner.id.hash(state);
584
    }
585
}
586

            
587
impl Display for DeclarationPtr {
588
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
589
        let value: &Declaration = &self.borrow();
590
        value.fmt(f)
591
    }
592
}
593

            
594
#[derive(Clone, PartialEq, Debug, Serialize, Deserialize, Eq, Uniplate)]
595
#[biplate(to=Expression)]
596
#[biplate(to=DeclarationPtr)]
597
#[biplate(to=Name)]
598
/// The contents of a declaration
599
struct Declaration {
600
    /// The name of the declared symbol.
601
    name: Name,
602

            
603
    /// The kind of the declaration.
604
    kind: DeclarationKind,
605
}
606

            
607
impl Declaration {
608
    /// Creates a new declaration.
609
    fn new(name: Name, kind: DeclarationKind) -> Declaration {
610
        Declaration { name, kind }
611
    }
612
}
613

            
614
/// A specific kind of declaration.
615
#[non_exhaustive]
616
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Uniplate)]
617
#[biplate(to=Expression)]
618
#[biplate(to=DeclarationPtr)]
619
#[biplate(to=Declaration)]
620
pub enum DeclarationKind {
621
    DecisionVariable(DecisionVariable),
622
    ValueLetting(Expression),
623
    DomainLetting(DomainPtr),
624
    Given(DomainPtr),
625

            
626
    /// A named field inside a record type.
627
    /// e.g. A, B in record{A: int(0..1), B: int(0..2)}
628
    RecordField(DomainPtr),
629
}