plotnik_core/
lib.rs

1#![cfg_attr(coverage_nightly, feature(coverage_attribute))]
2
3//! Core data structures for Plotnik node type information.
4//!
5//! Two layers:
6//! - **Deserialization layer**: 1:1 mapping to `node-types.json`
7//! - **Analysis layer**: ID-indexed structures for efficient lookups
8//!
9//! Two implementations:
10//! - **Dynamic** (`DynamicNodeTypes`): HashMap-based, for runtime construction
11//! - **Static** (`StaticNodeTypes`): Array-based, zero runtime init
12
13use std::collections::HashMap;
14use std::num::NonZeroU16;
15
16mod invariants;
17
18/// Raw node definition from `node-types.json`.
19#[derive(Debug, Clone, serde::Deserialize)]
20pub struct RawNode {
21    #[serde(rename = "type")]
22    pub type_name: String,
23    pub named: bool,
24    #[serde(default)]
25    pub root: bool,
26    #[serde(default)]
27    pub extra: bool,
28    #[serde(default)]
29    pub fields: HashMap<String, RawCardinality>,
30    pub children: Option<RawCardinality>,
31    pub subtypes: Option<Vec<RawTypeRef>>,
32}
33
34/// Cardinality constraints for a field or children slot.
35#[derive(Debug, Clone, serde::Deserialize)]
36pub struct RawCardinality {
37    pub multiple: bool,
38    pub required: bool,
39    pub types: Vec<RawTypeRef>,
40}
41
42/// Reference to a node type.
43#[derive(Debug, Clone, serde::Deserialize)]
44pub struct RawTypeRef {
45    #[serde(rename = "type")]
46    pub type_name: String,
47    pub named: bool,
48}
49
50/// Parse `node-types.json` content into raw nodes.
51pub fn parse_node_types(json: &str) -> Result<Vec<RawNode>, serde_json::Error> {
52    serde_json::from_str(json)
53}
54
55/// Node type ID (tree-sitter uses u16).
56pub type NodeTypeId = u16;
57
58/// Field ID (tree-sitter uses NonZeroU16).
59pub type NodeFieldId = NonZeroU16;
60
61/// Cardinality info for a field or children slot.
62#[derive(Debug, Clone, Copy, PartialEq, Eq)]
63pub struct Cardinality {
64    pub multiple: bool,
65    pub required: bool,
66}
67
68/// Trait for node type constraint lookups.
69///
70/// Provides only what tree-sitter's `Language` API doesn't:
71/// - Root node identification
72/// - Extra nodes (comments, whitespace)
73/// - Field constraints per node type
74/// - Children constraints per node type
75///
76/// For name↔ID resolution and supertype info, use `Language` directly.
77pub trait NodeTypes {
78    fn root(&self) -> Option<NodeTypeId>;
79    fn is_extra(&self, node_type_id: NodeTypeId) -> bool;
80
81    fn has_field(&self, node_type_id: NodeTypeId, node_field_id: NodeFieldId) -> bool;
82    fn field_cardinality(
83        &self,
84        node_type_id: NodeTypeId,
85        node_field_id: NodeFieldId,
86    ) -> Option<Cardinality>;
87    fn valid_field_types(
88        &self,
89        node_type_id: NodeTypeId,
90        node_field_id: NodeFieldId,
91    ) -> &[NodeTypeId];
92    fn is_valid_field_type(
93        &self,
94        node_type_id: NodeTypeId,
95        node_field_id: NodeFieldId,
96        child: NodeTypeId,
97    ) -> bool;
98
99    fn children_cardinality(&self, node_type_id: NodeTypeId) -> Option<Cardinality>;
100    fn valid_child_types(&self, node_type_id: NodeTypeId) -> &[NodeTypeId];
101    fn is_valid_child_type(&self, node_type_id: NodeTypeId, child: NodeTypeId) -> bool;
102}
103
104impl<T: NodeTypes + ?Sized> NodeTypes for &T {
105    fn root(&self) -> Option<NodeTypeId> {
106        (*self).root()
107    }
108    fn is_extra(&self, node_type_id: NodeTypeId) -> bool {
109        (*self).is_extra(node_type_id)
110    }
111    fn has_field(&self, node_type_id: NodeTypeId, node_field_id: NodeFieldId) -> bool {
112        (*self).has_field(node_type_id, node_field_id)
113    }
114    fn field_cardinality(
115        &self,
116        node_type_id: NodeTypeId,
117        node_field_id: NodeFieldId,
118    ) -> Option<Cardinality> {
119        (*self).field_cardinality(node_type_id, node_field_id)
120    }
121    fn valid_field_types(
122        &self,
123        node_type_id: NodeTypeId,
124        node_field_id: NodeFieldId,
125    ) -> &[NodeTypeId] {
126        (*self).valid_field_types(node_type_id, node_field_id)
127    }
128    fn is_valid_field_type(
129        &self,
130        node_type_id: NodeTypeId,
131        node_field_id: NodeFieldId,
132        child: NodeTypeId,
133    ) -> bool {
134        (*self).is_valid_field_type(node_type_id, node_field_id, child)
135    }
136    fn children_cardinality(&self, node_type_id: NodeTypeId) -> Option<Cardinality> {
137        (*self).children_cardinality(node_type_id)
138    }
139    fn valid_child_types(&self, node_type_id: NodeTypeId) -> &[NodeTypeId] {
140        (*self).valid_child_types(node_type_id)
141    }
142    fn is_valid_child_type(&self, node_type_id: NodeTypeId, child: NodeTypeId) -> bool {
143        (*self).is_valid_child_type(node_type_id, child)
144    }
145}
146
147/// Field info for static storage.
148#[derive(Debug, Clone, Copy)]
149pub struct StaticFieldInfo {
150    pub cardinality: Cardinality,
151    pub valid_types: &'static [NodeTypeId],
152}
153
154/// Children info for static storage.
155#[derive(Debug, Clone, Copy)]
156pub struct StaticChildrenInfo {
157    pub cardinality: Cardinality,
158    pub valid_types: &'static [NodeTypeId],
159}
160
161/// Complete node type information for static storage.
162///
163/// Note: supertype/subtype info is NOT stored here - use `Language::node_kind_is_supertype()`
164/// and `Language::subtypes_for_supertype()` from tree-sitter instead.
165#[derive(Debug, Clone, Copy)]
166pub struct StaticNodeTypeInfo {
167    pub name: &'static str,
168    pub named: bool,
169    /// Sorted slice of (field_id, field_info) pairs for binary search.
170    pub fields: &'static [(NodeFieldId, StaticFieldInfo)],
171    pub children: Option<StaticChildrenInfo>,
172}
173
174/// Compiled node type database with static storage.
175///
176/// All data is statically allocated - no runtime initialization needed.
177/// Node lookups use binary search on sorted arrays.
178#[derive(Debug, Clone, Copy)]
179pub struct StaticNodeTypes {
180    /// Sorted slice of (node_id, node_info) pairs.
181    nodes: &'static [(NodeTypeId, StaticNodeTypeInfo)],
182    /// Slice of extra node type IDs.
183    extras: &'static [NodeTypeId],
184    root: Option<NodeTypeId>,
185}
186
187impl StaticNodeTypes {
188    pub const fn new(
189        nodes: &'static [(NodeTypeId, StaticNodeTypeInfo)],
190        extras: &'static [NodeTypeId],
191        root: Option<NodeTypeId>,
192    ) -> Self {
193        Self {
194            nodes,
195            extras,
196            root,
197        }
198    }
199
200    /// Get info for a node type by ID (binary search).
201    pub fn get(&self, node_type_id: NodeTypeId) -> Option<&'static StaticNodeTypeInfo> {
202        self.nodes
203            .binary_search_by_key(&node_type_id, |(node_id, _)| *node_id)
204            .ok()
205            .map(|idx| &self.nodes[idx].1)
206    }
207
208    /// Check if node type exists.
209    pub fn contains(&self, node_type_id: NodeTypeId) -> bool {
210        self.nodes
211            .binary_search_by_key(&node_type_id, |(node_id, _)| *node_id)
212            .is_ok()
213    }
214
215    /// Get field info for a node type (binary search for node, then field).
216    pub fn field(
217        &self,
218        node_type_id: NodeTypeId,
219        field_id: NodeFieldId,
220    ) -> Option<&'static StaticFieldInfo> {
221        let info = self.ensure_node(node_type_id);
222        info.fields
223            .binary_search_by_key(&field_id, |(fid, _)| *fid)
224            .ok()
225            .map(|idx| &info.fields[idx].1)
226    }
227
228    /// Get children info for a node type.
229    pub fn children(&self, node_type_id: NodeTypeId) -> Option<StaticChildrenInfo> {
230        self.ensure_node(node_type_id).children
231    }
232
233    /// Get all extra node type IDs.
234    pub fn extras(&self) -> &'static [NodeTypeId] {
235        self.extras
236    }
237
238    pub fn len(&self) -> usize {
239        self.nodes.len()
240    }
241
242    pub fn is_empty(&self) -> bool {
243        self.nodes.is_empty()
244    }
245
246    pub fn iter(&self) -> impl Iterator<Item = (NodeTypeId, &'static StaticNodeTypeInfo)> {
247        self.nodes.iter().map(|(id, info)| (*id, info))
248    }
249}
250
251impl NodeTypes for StaticNodeTypes {
252    fn root(&self) -> Option<NodeTypeId> {
253        self.root
254    }
255
256    fn is_extra(&self, node_type_id: NodeTypeId) -> bool {
257        self.extras.contains(&node_type_id)
258    }
259
260    fn has_field(&self, node_type_id: NodeTypeId, node_field_id: NodeFieldId) -> bool {
261        self.get(node_type_id).is_some_and(|info| {
262            info.fields
263                .binary_search_by_key(&node_field_id, |(fid, _)| *fid)
264                .is_ok()
265        })
266    }
267
268    fn field_cardinality(
269        &self,
270        node_type_id: NodeTypeId,
271        node_field_id: NodeFieldId,
272    ) -> Option<Cardinality> {
273        self.field(node_type_id, node_field_id)
274            .map(|f| f.cardinality)
275    }
276
277    fn valid_field_types(
278        &self,
279        node_type_id: NodeTypeId,
280        node_field_id: NodeFieldId,
281    ) -> &[NodeTypeId] {
282        self.field(node_type_id, node_field_id)
283            .map(|f| f.valid_types)
284            .unwrap_or(&[])
285    }
286
287    fn is_valid_field_type(
288        &self,
289        node_type_id: NodeTypeId,
290        node_field_id: NodeFieldId,
291        child: NodeTypeId,
292    ) -> bool {
293        self.valid_field_types(node_type_id, node_field_id)
294            .contains(&child)
295    }
296
297    fn children_cardinality(&self, node_type_id: NodeTypeId) -> Option<Cardinality> {
298        self.children(node_type_id).map(|c| c.cardinality)
299    }
300
301    fn valid_child_types(&self, node_type_id: NodeTypeId) -> &[NodeTypeId] {
302        self.children(node_type_id)
303            .map(|c| c.valid_types)
304            .unwrap_or(&[])
305    }
306
307    fn is_valid_child_type(&self, node_type_id: NodeTypeId, child: NodeTypeId) -> bool {
308        self.valid_child_types(node_type_id).contains(&child)
309    }
310}
311
312/// Information about a single field on a node type.
313#[derive(Debug, Clone)]
314pub struct FieldInfo {
315    pub cardinality: Cardinality,
316    pub valid_types: Vec<NodeTypeId>,
317}
318
319/// Information about a node type's children (non-field children).
320#[derive(Debug, Clone)]
321pub struct ChildrenInfo {
322    pub cardinality: Cardinality,
323    pub valid_types: Vec<NodeTypeId>,
324}
325
326/// Complete node type information.
327///
328/// Note: supertype/subtype info is NOT stored here - use tree-sitter's Language API.
329#[derive(Debug, Clone)]
330pub struct NodeTypeInfo {
331    pub name: String,
332    pub named: bool,
333    pub fields: HashMap<NodeFieldId, FieldInfo>,
334    pub children: Option<ChildrenInfo>,
335}
336
337/// Compiled node type database for a language (dynamic/heap-allocated).
338///
339/// Use this for runtime construction or as reference implementation.
340/// For zero-init static data, use `StaticNodeTypes`.
341#[derive(Debug, Clone)]
342pub struct DynamicNodeTypes {
343    nodes: HashMap<NodeTypeId, NodeTypeInfo>,
344    extras: Vec<NodeTypeId>,
345    root: Option<NodeTypeId>,
346}
347
348impl DynamicNodeTypes {
349    pub fn from_raw(
350        nodes: HashMap<NodeTypeId, NodeTypeInfo>,
351        extras: Vec<NodeTypeId>,
352        root: Option<NodeTypeId>,
353    ) -> Self {
354        Self {
355            nodes,
356            extras,
357            root,
358        }
359    }
360
361    /// Build from raw nodes and ID resolution functions.
362    pub fn build<F, G>(raw_nodes: &[RawNode], node_id_for_name: F, field_id_for_name: G) -> Self
363    where
364        F: Fn(&str, bool) -> Option<NodeTypeId>,
365        G: Fn(&str) -> Option<NodeFieldId>,
366    {
367        let mut nodes = HashMap::new();
368        let mut extras = Vec::new();
369        let mut root = None;
370
371        for raw in raw_nodes {
372            let Some(node_id) = node_id_for_name(&raw.type_name, raw.named) else {
373                continue;
374            };
375
376            if raw.root {
377                root = Some(node_id);
378            }
379
380            if raw.extra {
381                extras.push(node_id);
382            }
383
384            let mut fields = HashMap::new();
385            for (field_name, raw_card) in &raw.fields {
386                let Some(field_id) = field_id_for_name(field_name) else {
387                    continue;
388                };
389
390                let valid_types = raw_card
391                    .types
392                    .iter()
393                    .filter_map(|t| node_id_for_name(&t.type_name, t.named))
394                    .collect();
395
396                fields.insert(
397                    field_id,
398                    FieldInfo {
399                        cardinality: Cardinality {
400                            multiple: raw_card.multiple,
401                            required: raw_card.required,
402                        },
403                        valid_types,
404                    },
405                );
406            }
407
408            let children = raw.children.as_ref().map(|raw_card| {
409                let valid_types = raw_card
410                    .types
411                    .iter()
412                    .filter_map(|t| node_id_for_name(&t.type_name, t.named))
413                    .collect();
414
415                ChildrenInfo {
416                    cardinality: Cardinality {
417                        multiple: raw_card.multiple,
418                        required: raw_card.required,
419                    },
420                    valid_types,
421                }
422            });
423
424            nodes.insert(
425                node_id,
426                NodeTypeInfo {
427                    name: raw.type_name.clone(),
428                    named: raw.named,
429                    fields,
430                    children,
431                },
432            );
433        }
434
435        Self {
436            nodes,
437            extras,
438            root,
439        }
440    }
441
442    pub fn get(&self, node_type_id: NodeTypeId) -> Option<&NodeTypeInfo> {
443        self.nodes.get(&node_type_id)
444    }
445
446    pub fn contains(&self, node_type_id: NodeTypeId) -> bool {
447        self.nodes.contains_key(&node_type_id)
448    }
449
450    pub fn field(&self, node_type_id: NodeTypeId, field_id: NodeFieldId) -> Option<&FieldInfo> {
451        self.ensure_node(node_type_id).fields.get(&field_id)
452    }
453
454    pub fn children(&self, node_type_id: NodeTypeId) -> Option<&ChildrenInfo> {
455        self.ensure_node(node_type_id).children.as_ref()
456    }
457
458    pub fn extras(&self) -> &[NodeTypeId] {
459        &self.extras
460    }
461
462    pub fn len(&self) -> usize {
463        self.nodes.len()
464    }
465
466    pub fn is_empty(&self) -> bool {
467        self.nodes.is_empty()
468    }
469
470    pub fn iter(&self) -> impl Iterator<Item = (NodeTypeId, &NodeTypeInfo)> {
471        self.nodes.iter().map(|(&id, info)| (id, info))
472    }
473
474    /// Get sorted vec of all node IDs (for conversion to static).
475    pub fn sorted_node_ids(&self) -> Vec<NodeTypeId> {
476        let mut ids: Vec<_> = self.nodes.keys().copied().collect();
477        ids.sort_unstable();
478        ids
479    }
480
481    /// Get sorted vec of extra IDs (for conversion to static).
482    pub fn sorted_extras(&self) -> Vec<NodeTypeId> {
483        let mut ids = self.extras.clone();
484        ids.sort_unstable();
485        ids
486    }
487}
488
489impl NodeTypes for DynamicNodeTypes {
490    fn root(&self) -> Option<NodeTypeId> {
491        self.root
492    }
493
494    fn is_extra(&self, node_type_id: NodeTypeId) -> bool {
495        self.extras.contains(&node_type_id)
496    }
497
498    fn has_field(&self, node_type_id: NodeTypeId, node_field_id: NodeFieldId) -> bool {
499        self.nodes
500            .get(&node_type_id)
501            .is_some_and(|n| n.fields.contains_key(&node_field_id))
502    }
503
504    fn field_cardinality(
505        &self,
506        node_type_id: NodeTypeId,
507        node_field_id: NodeFieldId,
508    ) -> Option<Cardinality> {
509        self.field(node_type_id, node_field_id)
510            .map(|f| f.cardinality)
511    }
512
513    fn valid_field_types(
514        &self,
515        node_type_id: NodeTypeId,
516        node_field_id: NodeFieldId,
517    ) -> &[NodeTypeId] {
518        self.field(node_type_id, node_field_id)
519            .map(|f| f.valid_types.as_slice())
520            .unwrap_or(&[])
521    }
522
523    fn is_valid_field_type(
524        &self,
525        node_type_id: NodeTypeId,
526        node_field_id: NodeFieldId,
527        child: NodeTypeId,
528    ) -> bool {
529        self.valid_field_types(node_type_id, node_field_id)
530            .contains(&child)
531    }
532
533    fn children_cardinality(&self, node_type_id: NodeTypeId) -> Option<Cardinality> {
534        self.children(node_type_id).map(|c| c.cardinality)
535    }
536
537    fn valid_child_types(&self, node_type_id: NodeTypeId) -> &[NodeTypeId] {
538        self.children(node_type_id)
539            .map(|c| c.valid_types.as_slice())
540            .unwrap_or(&[])
541    }
542
543    fn is_valid_child_type(&self, node_type_id: NodeTypeId, child: NodeTypeId) -> bool {
544        self.valid_child_types(node_type_id).contains(&child)
545    }
546}
547
548#[cfg(test)]
549mod tests {
550    use super::*;
551
552    const SAMPLE_JSON: &str = r#"[
553        {
554            "type": "expression",
555            "named": true,
556            "subtypes": [
557                {"type": "identifier", "named": true},
558                {"type": "number", "named": true}
559            ]
560        },
561        {
562            "type": "function_declaration",
563            "named": true,
564            "fields": {
565                "name": {
566                    "multiple": false,
567                    "required": true,
568                    "types": [{"type": "identifier", "named": true}]
569                },
570                "body": {
571                    "multiple": false,
572                    "required": true,
573                    "types": [{"type": "block", "named": true}]
574                }
575            }
576        },
577        {
578            "type": "program",
579            "named": true,
580            "root": true,
581            "fields": {},
582            "children": {
583                "multiple": true,
584                "required": false,
585                "types": [{"type": "statement", "named": true}]
586            }
587        },
588        {
589            "type": "comment",
590            "named": true,
591            "extra": true
592        },
593        {
594            "type": "identifier",
595            "named": true
596        },
597        {
598            "type": "+",
599            "named": false
600        }
601    ]"#;
602
603    #[test]
604    fn parse_raw_nodes() {
605        let nodes = parse_node_types(SAMPLE_JSON).unwrap();
606        assert_eq!(nodes.len(), 6);
607
608        let expr = nodes.iter().find(|n| n.type_name == "expression").unwrap();
609        assert!(expr.named);
610        assert!(expr.subtypes.is_some());
611        assert_eq!(expr.subtypes.as_ref().unwrap().len(), 2);
612
613        let func = nodes
614            .iter()
615            .find(|n| n.type_name == "function_declaration")
616            .unwrap();
617        assert!(func.fields.contains_key("name"));
618        assert!(func.fields.contains_key("body"));
619
620        let plus = nodes.iter().find(|n| n.type_name == "+").unwrap();
621        assert!(!plus.named);
622    }
623
624    #[test]
625    fn build_dynamic_node_types() {
626        let raw = parse_node_types(SAMPLE_JSON).unwrap();
627
628        let node_ids: HashMap<(&str, bool), NodeTypeId> = [
629            (("expression", true), 1),
630            (("function_declaration", true), 2),
631            (("program", true), 3),
632            (("comment", true), 4),
633            (("identifier", true), 5),
634            (("+", false), 6),
635            (("block", true), 7),
636            (("statement", true), 8),
637            (("number", true), 9),
638        ]
639        .into_iter()
640        .collect();
641
642        let field_ids: HashMap<&str, NodeFieldId> = [
643            ("name", NonZeroU16::new(1).unwrap()),
644            ("body", NonZeroU16::new(2).unwrap()),
645        ]
646        .into_iter()
647        .collect();
648
649        let node_types = DynamicNodeTypes::build(
650            &raw,
651            |name, named| node_ids.get(&(name, named)).copied(),
652            |name| field_ids.get(name).copied(),
653        );
654
655        assert_eq!(node_types.len(), 6);
656
657        // Test via trait
658        assert_eq!(node_types.root(), Some(3));
659        assert!(node_types.is_extra(4));
660        assert!(!node_types.is_extra(5));
661        assert!(node_types.has_field(2, NonZeroU16::new(1).unwrap()));
662        assert!(node_types.has_field(2, NonZeroU16::new(2).unwrap()));
663        assert!(!node_types.has_field(2, NonZeroU16::new(99).unwrap()));
664        assert!(node_types.is_valid_field_type(2, NonZeroU16::new(1).unwrap(), 5));
665        assert!(!node_types.is_valid_field_type(2, NonZeroU16::new(1).unwrap(), 7));
666    }
667
668    // Static tests using manually constructed data
669    static TEST_VALID_TYPES_ID: [NodeTypeId; 1] = [5]; // identifier
670    static TEST_VALID_TYPES_BLOCK: [NodeTypeId; 1] = [7]; // block
671    static TEST_CHILDREN_TYPES: [NodeTypeId; 1] = [8]; // statement
672
673    static TEST_FIELDS: [(NodeFieldId, StaticFieldInfo); 2] = [
674        (
675            NonZeroU16::new(1).unwrap(),
676            StaticFieldInfo {
677                cardinality: Cardinality {
678                    multiple: false,
679                    required: true,
680                },
681                valid_types: &TEST_VALID_TYPES_ID,
682            },
683        ),
684        (
685            NonZeroU16::new(2).unwrap(),
686            StaticFieldInfo {
687                cardinality: Cardinality {
688                    multiple: false,
689                    required: true,
690                },
691                valid_types: &TEST_VALID_TYPES_BLOCK,
692            },
693        ),
694    ];
695
696    static TEST_NODES: [(NodeTypeId, StaticNodeTypeInfo); 4] = [
697        (
698            1,
699            StaticNodeTypeInfo {
700                name: "expression",
701                named: true,
702                fields: &[],
703                children: None,
704            },
705        ),
706        (
707            2,
708            StaticNodeTypeInfo {
709                name: "function_declaration",
710                named: true,
711                fields: &TEST_FIELDS,
712                children: None,
713            },
714        ),
715        (
716            3,
717            StaticNodeTypeInfo {
718                name: "program",
719                named: true,
720                fields: &[],
721                children: Some(StaticChildrenInfo {
722                    cardinality: Cardinality {
723                        multiple: true,
724                        required: false,
725                    },
726                    valid_types: &TEST_CHILDREN_TYPES,
727                }),
728            },
729        ),
730        (
731            4,
732            StaticNodeTypeInfo {
733                name: "comment",
734                named: true,
735                fields: &[],
736                children: None,
737            },
738        ),
739    ];
740
741    static TEST_EXTRAS: [NodeTypeId; 1] = [4];
742
743    static TEST_STATIC_NODE_TYPES: StaticNodeTypes =
744        StaticNodeTypes::new(&TEST_NODES, &TEST_EXTRAS, Some(3));
745
746    #[test]
747    fn static_node_types_get() {
748        let info = TEST_STATIC_NODE_TYPES.get(2).unwrap();
749        assert_eq!(info.name, "function_declaration");
750        assert!(info.named);
751
752        assert!(TEST_STATIC_NODE_TYPES.get(99).is_none());
753    }
754
755    #[test]
756    fn static_node_types_contains() {
757        assert!(TEST_STATIC_NODE_TYPES.contains(1));
758        assert!(TEST_STATIC_NODE_TYPES.contains(2));
759        assert!(!TEST_STATIC_NODE_TYPES.contains(99));
760    }
761
762    #[test]
763    fn static_node_types_trait() {
764        // Test via trait methods
765        assert_eq!(TEST_STATIC_NODE_TYPES.root(), Some(3));
766        assert!(TEST_STATIC_NODE_TYPES.is_extra(4));
767        assert!(!TEST_STATIC_NODE_TYPES.is_extra(1));
768
769        assert!(TEST_STATIC_NODE_TYPES.has_field(2, NonZeroU16::new(1).unwrap()));
770        assert!(TEST_STATIC_NODE_TYPES.has_field(2, NonZeroU16::new(2).unwrap()));
771        assert!(!TEST_STATIC_NODE_TYPES.has_field(2, NonZeroU16::new(99).unwrap()));
772        assert!(!TEST_STATIC_NODE_TYPES.has_field(1, NonZeroU16::new(1).unwrap()));
773
774        assert!(TEST_STATIC_NODE_TYPES.is_valid_field_type(2, NonZeroU16::new(1).unwrap(), 5));
775        assert!(!TEST_STATIC_NODE_TYPES.is_valid_field_type(2, NonZeroU16::new(1).unwrap(), 7));
776        assert!(TEST_STATIC_NODE_TYPES.is_valid_field_type(2, NonZeroU16::new(2).unwrap(), 7));
777
778        let field_types = TEST_STATIC_NODE_TYPES.valid_field_types(2, NonZeroU16::new(1).unwrap());
779        assert_eq!(field_types, &[5]);
780
781        let card = TEST_STATIC_NODE_TYPES
782            .field_cardinality(2, NonZeroU16::new(1).unwrap())
783            .unwrap();
784        assert!(!card.multiple);
785        assert!(card.required);
786    }
787
788    #[test]
789    fn static_node_types_children() {
790        let card = TEST_STATIC_NODE_TYPES.children_cardinality(3).unwrap();
791        assert!(card.multiple);
792        assert!(!card.required);
793
794        let child_types = TEST_STATIC_NODE_TYPES.valid_child_types(3);
795        assert_eq!(child_types, &[8]);
796
797        assert!(TEST_STATIC_NODE_TYPES.is_valid_child_type(3, 8));
798        assert!(!TEST_STATIC_NODE_TYPES.is_valid_child_type(3, 5));
799
800        assert!(TEST_STATIC_NODE_TYPES.children_cardinality(1).is_none());
801        assert!(TEST_STATIC_NODE_TYPES.valid_child_types(1).is_empty());
802    }
803
804    #[test]
805    fn static_node_types_len() {
806        assert_eq!(TEST_STATIC_NODE_TYPES.len(), 4);
807        assert!(!TEST_STATIC_NODE_TYPES.is_empty());
808    }
809
810    #[test]
811    fn static_node_types_iter() {
812        let ids: Vec<_> = TEST_STATIC_NODE_TYPES.iter().map(|(id, _)| id).collect();
813        assert_eq!(ids, vec![1, 2, 3, 4]);
814    }
815}