Skip to main content

dry4rust/
node_encoder.rs

1// Copyright (c) 2026 Matjaz Domen Pecan
2// Copyright 2026 Umberto Gotti <umberto.gotti@umbertogotti.dev>
3// Licensed under the MIT License
4// SPDX-License-Identifier: MIT
5
6use crate::node::BinOpKind;
7use crate::node::LiteralKind;
8use crate::node::NodeKind;
9use crate::node::NormalizedNode;
10use crate::node::PlaceholderKind;
11use crate::node::UnOpKind;
12use crate::stable_hasher::StableHasher;
13
14/// Walks a normalised tree and feeds it to a [`StableHasher`] in a form that
15/// does not change when the code around it does.
16///
17/// Every variant is written as its own name rather than its position in the
18/// enum, so reordering `NodeKind` -- or inserting a variant in the middle --
19/// leaves existing fingerprints alone. Every match here is exhaustive, so a
20/// new variant is a compile error rather than a silently unhashed field.
21///
22/// Children are written with their count in front, which is what stops
23/// `[a, [b]]` and `[[a], b]` from hashing alike.
24pub struct NodeEncoder {
25    hasher: StableHasher,
26}
27
28impl NodeEncoder {
29    #[must_use]
30    pub const fn new() -> Self {
31        Self {
32            hasher: StableHasher::new(),
33        }
34    }
35
36    /// Absorb a whole tree.
37    pub fn encode(&mut self, node: &NormalizedNode) {
38        self.encode_kind(&node.kind);
39        self.hasher.write_u64(node.children.len() as u64);
40        for child in &node.children {
41            self.encode(child);
42        }
43    }
44
45    /// The fingerprint of everything absorbed so far.
46    #[must_use]
47    pub const fn finish(&self) -> u64 {
48        self.hasher.finish()
49    }
50
51    fn encode_kind(&mut self, kind: &NodeKind) {
52        self.hasher.write_str(Self::kind_name(kind));
53        self.encode_payload(kind);
54    }
55
56    /// The data a variant carries beyond its name.
57    fn encode_payload(&mut self, kind: &NodeKind) {
58        match kind {
59            NodeKind::Literal(literal) => self.hasher.write_str(Self::literal_name(literal)),
60            NodeKind::Placeholder(placeholder, index)
61            | NodeKind::PatPlaceholder(placeholder, index)
62            | NodeKind::TypePlaceholder(placeholder, index) => {
63                self.hasher.write_str(Self::placeholder_name(*placeholder));
64                self.hasher.write_u64(*index as u64);
65            }
66            NodeKind::BinaryOp(op) => self.hasher.write_str(Self::bin_op_name(op)),
67            NodeKind::UnaryOp(op) => self.hasher.write_str(Self::un_op_name(op)),
68            NodeKind::Reference { mutable }
69            | NodeKind::PatReference { mutable }
70            | NodeKind::TypeReference { mutable } => self.hasher.write_u8(u8::from(*mutable)),
71            NodeKind::MacroCall { name } => self.hasher.write_str(name),
72            _ => {}
73        }
74    }
75
76    const fn kind_name(kind: &NodeKind) -> &'static str {
77        match kind {
78            NodeKind::Block => "Block",
79            NodeKind::LetBinding => "LetBinding",
80            NodeKind::Semi => "Semi",
81            NodeKind::Paren => "Paren",
82            NodeKind::Literal(..) => "Literal",
83            NodeKind::Placeholder(..) => "Placeholder",
84            NodeKind::BinaryOp(..) => "BinaryOp",
85            NodeKind::UnaryOp(..) => "UnaryOp",
86            NodeKind::Range => "Range",
87            NodeKind::Call => "Call",
88            NodeKind::MethodCall => "MethodCall",
89            NodeKind::FieldAccess => "FieldAccess",
90            NodeKind::Index => "Index",
91            NodeKind::Path => "Path",
92            NodeKind::Closure => "Closure",
93            NodeKind::FnSignature => "FnSignature",
94            NodeKind::Return => "Return",
95            NodeKind::Break => "Break",
96            NodeKind::Continue => "Continue",
97            NodeKind::Assign => "Assign",
98            NodeKind::Reference { .. } => "Reference",
99            NodeKind::Tuple => "Tuple",
100            NodeKind::Array => "Array",
101            NodeKind::Set => "Set",
102            NodeKind::Repeat => "Repeat",
103            NodeKind::Cast => "Cast",
104            NodeKind::StructInit => "StructInit",
105            NodeKind::Await => "Await",
106            NodeKind::Yield => "Yield",
107            NodeKind::Try => "Try",
108            NodeKind::If => "If",
109            NodeKind::Match => "Match",
110            NodeKind::MatchArm => "MatchArm",
111            NodeKind::Loop => "Loop",
112            NodeKind::While => "While",
113            NodeKind::ForLoop => "ForLoop",
114            NodeKind::LetExpr => "LetExpr",
115            NodeKind::PatWild => "PatWild",
116            NodeKind::PatPlaceholder(..) => "PatPlaceholder",
117            NodeKind::PatTuple => "PatTuple",
118            NodeKind::PatStruct => "PatStruct",
119            NodeKind::PatOr => "PatOr",
120            NodeKind::PatLiteral => "PatLiteral",
121            NodeKind::PatReference { .. } => "PatReference",
122            NodeKind::PatSlice => "PatSlice",
123            NodeKind::PatRest => "PatRest",
124            NodeKind::PatRange => "PatRange",
125            NodeKind::TypePlaceholder(..) => "TypePlaceholder",
126            NodeKind::TypeReference { .. } => "TypeReference",
127            NodeKind::TypeTuple => "TypeTuple",
128            NodeKind::TypeSlice => "TypeSlice",
129            NodeKind::TypeArray => "TypeArray",
130            NodeKind::TypePath => "TypePath",
131            NodeKind::TypeImplTrait => "TypeImplTrait",
132            NodeKind::TypeInfer => "TypeInfer",
133            NodeKind::TypeUnit => "TypeUnit",
134            NodeKind::TypeNever => "TypeNever",
135            NodeKind::FieldValue => "FieldValue",
136            NodeKind::MacroCall { .. } => "MacroCall",
137            NodeKind::Opaque => "Opaque",
138            NodeKind::None => "None",
139        }
140    }
141
142    const fn literal_name(literal: &LiteralKind) -> &'static str {
143        match literal {
144            LiteralKind::Int => "Int",
145            LiteralKind::Float => "Float",
146            LiteralKind::Str => "Str",
147            LiteralKind::ByteStr => "ByteStr",
148            LiteralKind::CStr => "CStr",
149            LiteralKind::Byte => "Byte",
150            LiteralKind::Char => "Char",
151            LiteralKind::Bool => "Bool",
152            LiteralKind::Null => "Null",
153        }
154    }
155
156    const fn placeholder_name(placeholder: PlaceholderKind) -> &'static str {
157        match placeholder {
158            PlaceholderKind::Variable => "Variable",
159            PlaceholderKind::Function => "Function",
160            PlaceholderKind::Type => "Type",
161            PlaceholderKind::Lifetime => "Lifetime",
162            PlaceholderKind::Label => "Label",
163        }
164    }
165
166    const fn bin_op_name(op: &BinOpKind) -> &'static str {
167        match op {
168            BinOpKind::Add => "Add",
169            BinOpKind::Sub => "Sub",
170            BinOpKind::Mul => "Mul",
171            BinOpKind::Div => "Div",
172            BinOpKind::Rem => "Rem",
173            BinOpKind::And => "And",
174            BinOpKind::Or => "Or",
175            BinOpKind::BitXor => "BitXor",
176            BinOpKind::BitAnd => "BitAnd",
177            BinOpKind::BitOr => "BitOr",
178            BinOpKind::Shl => "Shl",
179            BinOpKind::Shr => "Shr",
180            BinOpKind::Eq => "Eq",
181            BinOpKind::Lt => "Lt",
182            BinOpKind::Le => "Le",
183            BinOpKind::Ne => "Ne",
184            BinOpKind::Ge => "Ge",
185            BinOpKind::Gt => "Gt",
186            BinOpKind::AddAssign => "AddAssign",
187            BinOpKind::SubAssign => "SubAssign",
188            BinOpKind::MulAssign => "MulAssign",
189            BinOpKind::DivAssign => "DivAssign",
190            BinOpKind::RemAssign => "RemAssign",
191            BinOpKind::BitXorAssign => "BitXorAssign",
192            BinOpKind::BitAndAssign => "BitAndAssign",
193            BinOpKind::BitOrAssign => "BitOrAssign",
194            BinOpKind::ShlAssign => "ShlAssign",
195            BinOpKind::ShrAssign => "ShrAssign",
196            BinOpKind::FloorDiv => "FloorDiv",
197            BinOpKind::Pow => "Pow",
198            BinOpKind::In => "In",
199            BinOpKind::NotIn => "NotIn",
200            BinOpKind::Is => "Is",
201            BinOpKind::IsNot => "IsNot",
202            BinOpKind::FloorDivAssign => "FloorDivAssign",
203            BinOpKind::PowAssign => "PowAssign",
204            BinOpKind::Other => "Other",
205        }
206    }
207
208    const fn un_op_name(op: &UnOpKind) -> &'static str {
209        match op {
210            UnOpKind::Deref => "Deref",
211            UnOpKind::Not => "Not",
212            UnOpKind::Neg => "Neg",
213            UnOpKind::Other => "Other",
214        }
215    }
216}
217
218impl Default for NodeEncoder {
219    fn default() -> Self {
220        Self::new()
221    }
222}