Skip to main content

rue_hir/
value.rs

1use std::collections::HashMap;
2
3use rue_types::{BuiltinTypes, TypeId};
4
5use crate::{Database, HirId, SymbolId, TypePath};
6
7pub type Mappings = HashMap<SymbolId, HashMap<Vec<TypePath>, TypeId>>;
8
9#[derive(Debug, Clone)]
10#[must_use]
11pub struct Value {
12    pub hir: HirId,
13    pub ty: TypeId,
14    pub reference: Option<SymbolPath>,
15    pub then_map: Mappings,
16    pub else_map: Mappings,
17}
18
19impl Value {
20    pub fn new(hir: HirId, ty: TypeId) -> Self {
21        Self {
22            hir,
23            ty,
24            reference: None,
25            then_map: Mappings::new(),
26            else_map: Mappings::new(),
27        }
28    }
29
30    pub fn with_hir(self, hir: HirId) -> Self {
31        Self { hir, ..self }
32    }
33
34    pub fn with_type(self, ty: TypeId) -> Self {
35        Self { ty, ..self }
36    }
37
38    pub fn with_reference(self, reference: SymbolPath) -> Self {
39        Self {
40            reference: Some(reference),
41            ..self
42        }
43    }
44
45    pub fn with_mappings(self, then_map: Mappings, else_map: Mappings) -> Self {
46        Self {
47            then_map,
48            else_map,
49            ..self
50        }
51    }
52
53    pub fn flip_mappings(self) -> Self {
54        Self {
55            then_map: self.else_map,
56            else_map: self.then_map,
57            ..self
58        }
59    }
60}
61
62#[derive(Debug, Clone)]
63pub struct SymbolPath {
64    pub symbol: SymbolId,
65    pub path: Vec<TypePath>,
66}
67
68impl SymbolPath {
69    pub fn new(symbol: SymbolId, path: Vec<TypePath>) -> Self {
70        Self { symbol, path }
71    }
72}
73
74/// Combines two sets of narrowing facts which are known to hold at the same time.
75///
76/// If both sets narrow the same path of the same symbol, the result is narrowed to the
77/// intersection of both types rather than letting one side overwrite the other.
78pub fn merge_mappings(
79    db: &mut Database,
80    builtins: &BuiltinTypes,
81    a: &Mappings,
82    b: &Mappings,
83) -> Mappings {
84    let mut map = a.clone();
85
86    for (&symbol, mappings) in b {
87        for (path, &ty) in mappings {
88            let paths = map.entry(symbol).or_default();
89
90            let ty = if let Some(&existing) = paths.get(path) {
91                rue_types::intersect(db.types_mut(), builtins, existing, ty)
92            } else {
93                ty
94            };
95
96            paths.insert(path.clone(), ty);
97        }
98    }
99
100    map
101}
102
103#[cfg(test)]
104mod tests {
105    use rue_types::{Pair, Type, Union};
106
107    use crate::Symbol;
108
109    use super::*;
110
111    fn mappings(entries: &[(SymbolId, Vec<TypePath>, TypeId)]) -> Mappings {
112        let mut map = Mappings::new();
113        for (symbol, path, ty) in entries {
114            map.entry(*symbol).or_default().insert(path.clone(), *ty);
115        }
116        map
117    }
118
119    fn setup() -> (Database, BuiltinTypes, SymbolId, SymbolId) {
120        let mut db = Database::new();
121        let builtins = BuiltinTypes::new(db.types_mut());
122        let x = db.alloc_symbol(Symbol::Unresolved);
123        let y = db.alloc_symbol(Symbol::Unresolved);
124        (db, builtins, x, y)
125    }
126
127    #[test]
128    fn test_merge_disjoint() {
129        let (mut db, builtins, x, y) = setup();
130
131        let left = mappings(&[(x, vec![], builtins.int)]);
132        let right = mappings(&[
133            (y, vec![], builtins.bytes32),
134            (x, vec![TypePath::First], builtins.nil),
135        ]);
136        let merged = merge_mappings(&mut db, &builtins, &left, &right);
137
138        assert_eq!(merged.len(), 2);
139        assert_eq!(merged[&x].len(), 2);
140        assert_eq!(merged[&x][&vec![]], builtins.int);
141        assert_eq!(merged[&x][&vec![TypePath::First]], builtins.nil);
142        assert_eq!(merged[&y][&vec![]], builtins.bytes32);
143    }
144
145    #[test]
146    fn test_merge_empty() {
147        let (mut db, builtins, x, _) = setup();
148
149        let left = mappings(&[(x, vec![], builtins.int)]);
150        let merged = merge_mappings(&mut db, &builtins, &left, &Mappings::new());
151        assert_eq!(merged[&x][&vec![]], builtins.int);
152
153        let merged = merge_mappings(&mut db, &builtins, &Mappings::new(), &left);
154        assert_eq!(merged[&x][&vec![]], builtins.int);
155    }
156
157    #[test]
158    fn test_merge_same_path_intersects() {
159        let (mut db, builtins, x, _) = setup();
160
161        // `x: Bytes32 | nil | (Int, Int)`, after `(x is nil) | (x is Bytes32)` is false
162        let pair = db.alloc_type(Type::Pair(Pair::new(builtins.int, builtins.int)));
163        let not_nil = db.alloc_type(Type::Union(Union::new(vec![builtins.bytes32, pair])));
164        let not_bytes32 = db.alloc_type(Type::Union(Union::new(vec![builtins.nil, pair])));
165
166        let left = mappings(&[(x, vec![], not_nil)]);
167        let right = mappings(&[(x, vec![], not_bytes32)]);
168        let merged = merge_mappings(&mut db, &builtins, &left, &right);
169
170        let ty = merged[&x][&vec![]];
171        assert_eq!(rue_types::stringify(db.types_mut(), ty), "(Int, Int)");
172    }
173
174    #[test]
175    fn test_merge_same_path_subset() {
176        let (mut db, builtins, x, _) = setup();
177
178        let atom_or_nil = db.alloc_type(Type::Union(Union::new(vec![
179            builtins.bytes32,
180            builtins.nil,
181        ])));
182
183        // Whichever side is more specific wins, regardless of order
184        let left = mappings(&[(x, vec![TypePath::Rest], builtins.bytes32)]);
185        let right = mappings(&[(x, vec![TypePath::Rest], atom_or_nil)]);
186        assert_eq!(
187            merge_mappings(&mut db, &builtins, &left, &right)[&x][&vec![TypePath::Rest]],
188            builtins.bytes32
189        );
190        assert_eq!(
191            merge_mappings(&mut db, &builtins, &right, &left)[&x][&vec![TypePath::Rest]],
192            builtins.bytes32
193        );
194    }
195
196    #[test]
197    fn test_merge_same_path_disjoint() {
198        let (mut db, builtins, x, _) = setup();
199
200        // Both facts can't hold at once, so the value can't exist
201        let pair = db.alloc_type(Type::Pair(Pair::new(builtins.int, builtins.int)));
202        let left = mappings(&[(x, vec![], builtins.bytes32)]);
203        let right = mappings(&[(x, vec![], pair)]);
204        let merged = merge_mappings(&mut db, &builtins, &left, &right);
205
206        assert_eq!(merged[&x][&vec![]], builtins.never);
207    }
208}