Skip to main content

dada_ir_sym/ir/
module.rs

1use dada_ir_ast::{
2    ast::{AstItem, AstModule, AstUse, Identifier},
3    diagnostic::{Diagnostic, Level},
4    inputs::SourceFile,
5    span::{SourceSpanned, Span, Spanned},
6};
7use dada_parser::prelude::SourceFileParse;
8use dada_util::{FromImpls, Map, SalsaSerialize};
9
10use crate::{
11    check::{
12        scope::Scope,
13        scope_tree::{ScopeItem, ScopeTreeNode},
14    },
15    ir::{
16        classes::SymAggregate, functions::SymFunction, primitive::SymPrimitive,
17        variables::SymVariable,
18    },
19    prelude::Symbol,
20    well_known,
21};
22
23#[derive(SalsaSerialize)]
24#[salsa::tracked(debug)]
25pub struct SymModule<'db> {
26    pub source: AstModule<'db>,
27
28    // Order of fields reflects the precedence we give during name resolution.
29    #[tracked]
30    #[return_ref]
31    pub(crate) class_map: Map<Identifier<'db>, SymAggregate<'db>>,
32    #[tracked]
33    #[return_ref]
34    pub(crate) function_map: Map<Identifier<'db>, SymFunction<'db>>,
35    #[tracked]
36    #[return_ref]
37    pub(crate) ast_use_map: Map<Identifier<'db>, AstUse<'db>>,
38}
39
40impl<'db> Spanned<'db> for SymModule<'db> {
41    fn span(&self, db: &'db dyn dada_ir_ast::Db) -> Span<'db> {
42        self.source(db).span(db)
43    }
44}
45
46/// A "prelude" is a set of item names automatically imported into scope.
47#[salsa::interned(debug)]
48pub struct SymPrelude<'db> {
49    pub items: Vec<SymItem<'db>>,
50}
51
52#[salsa::tracked]
53impl<'db> SymModule<'db> {
54    pub fn name(self, db: &'db dyn crate::Db) -> Identifier<'db> {
55        self.source(db).name(db)
56    }
57
58    /// Name resolution scope for items in this module.
59    pub fn mod_scope(self, db: &'db dyn crate::Db) -> Scope<'db, 'db> {
60        Scope::new(db, self.span(db)).with_link(self)
61    }
62
63    /// Returns a list of all top-level items in the module
64    pub fn items(self, db: &'db dyn crate::Db) -> impl Iterator<Item = SymItem<'db>> {
65        self.class_map(db)
66            .values()
67            .copied()
68            .map(SymItem::from)
69            .chain(self.function_map(db).values().copied().map(SymItem::from))
70    }
71
72    /// Returns the function named `name` in this module, if any.
73    pub fn function_named(
74        self,
75        db: &'db dyn crate::Db,
76        name: Identifier<'db>,
77    ) -> Option<SymFunction<'db>> {
78        self.function_map(db).get(&name).copied()
79    }
80}
81
82#[salsa::tracked]
83impl<'db> ScopeTreeNode<'db> for SymModule<'db> {
84    fn direct_super_scope(self, _db: &'db dyn crate::Db) -> Option<ScopeItem<'db>> {
85        None // FIXME
86    }
87
88    #[salsa::tracked(return_ref)]
89    fn direct_generic_parameters(self, _db: &'db dyn crate::Db) -> Vec<SymVariable<'db>> {
90        vec![] // FIXME: we expect to add these in the future
91    }
92
93    fn into_scope(self, db: &'db dyn crate::Db) -> Scope<'db, 'db> {
94        self.mod_scope(db)
95    }
96
97    fn push_direct_ast_where_clauses(
98        self,
99        _db: &'db dyn crate::Db,
100        _out: &mut Vec<dada_ir_ast::ast::AstWhereClause<'db>>,
101    ) {
102        // FIXME: we expect to add these in the future
103    }
104}
105
106impl<'db> Symbol<'db> for SourceFile {
107    type Output = SymModule<'db>;
108
109    fn symbol(self, db: &'db dyn crate::Db) -> Self::Output {
110        self.parse(db).symbol(db)
111    }
112}
113
114#[salsa::tracked]
115impl<'db> Symbol<'db> for AstModule<'db> {
116    type Output = SymModule<'db>;
117
118    #[salsa::tracked]
119    fn symbol(self, db: &'db dyn crate::Db) -> SymModule<'db> {
120        let mut class_map = Map::default();
121        let mut function_map = Map::default();
122        let mut ast_use_map = Map::default();
123        for item in self.items(db) {
124            match *item {
125                AstItem::SourceFile(_) => {}
126                AstItem::Use(ast_use) => {
127                    let id = match ast_use.as_id(db) {
128                        Some(as_id) => as_id.id,
129                        None => ast_use.path(db).last_id(db).id,
130                    };
131
132                    insert(db, &mut ast_use_map, id, ast_use);
133                }
134                AstItem::Aggregate(ast_class_item) => {
135                    insert(
136                        db,
137                        &mut class_map,
138                        ast_class_item.name(db),
139                        SymAggregate::new(db, self.into(), ast_class_item),
140                    );
141                }
142                AstItem::Function(func) => {
143                    insert(
144                        db,
145                        &mut function_map,
146                        func.name(db).id,
147                        SymFunction::new(db, self.into(), func.into()),
148                    );
149                }
150                AstItem::MainFunction(mfunc) => {
151                    insert(
152                        db,
153                        &mut function_map,
154                        Identifier::main(db),
155                        SymFunction::new(db, self.into(), mfunc.into()),
156                    );
157                }
158            }
159        }
160
161        // Detect duplicates between maps. The order is significant here;
162        // when resolving names, we prefer the maps that come earlier in this list.
163        let canonical_map = &mut Map::default();
164        insert_into_canonical_map(db, canonical_map, &class_map);
165        insert_into_canonical_map(db, canonical_map, &function_map);
166        insert_into_canonical_map(db, canonical_map, &ast_use_map);
167
168        SymModule::new(db, self, class_map, function_map, ast_use_map)
169    }
170}
171
172impl<'db> ScopeTreeNode<'db> for AstModule<'db> {
173    fn direct_super_scope(self, db: &'db dyn crate::Db) -> Option<ScopeItem<'db>> {
174        self.symbol(db).direct_super_scope(db)
175    }
176
177    fn direct_generic_parameters(self, db: &'db dyn crate::Db) -> &'db Vec<SymVariable<'db>> {
178        self.symbol(db).direct_generic_parameters(db)
179    }
180
181    fn into_scope(self, db: &'db dyn crate::Db) -> Scope<'db, 'db> {
182        self.symbol(db).into_scope(db)
183    }
184
185    fn push_direct_ast_where_clauses(
186        self,
187        db: &'db dyn crate::Db,
188        out: &mut Vec<dada_ir_ast::ast::AstWhereClause<'db>>,
189    ) {
190        self.symbol(db).push_direct_ast_where_clauses(db, out);
191    }
192}
193
194fn insert<'db, V: Spanned<'db>>(
195    db: &'db dyn crate::Db,
196    map: &mut Map<Identifier<'db>, V>,
197    id: Identifier<'db>,
198    value: V,
199) {
200    if let Some(other_value) = map.get(&id) {
201        report_duplicate(db, id, value.span(db), other_value.span(db));
202    } else {
203        map.insert(id, value);
204    }
205}
206
207fn insert_into_canonical_map<'db>(
208    db: &'db dyn crate::Db,
209    canonical_map: &mut Map<Identifier<'db>, Span<'db>>,
210    map: &Map<Identifier<'db>, impl Spanned<'db>>,
211) {
212    for (id, value) in map.iter() {
213        let id = *id;
214        let value_span = value.span(db);
215        if let Some(canonical_span) = canonical_map.get(&id) {
216            report_duplicate(db, id, value_span, *canonical_span);
217        } else {
218            canonical_map.insert(id, value_span);
219        }
220    }
221}
222
223fn report_duplicate<'db>(
224    db: &'db dyn crate::Db,
225    id: Identifier<'db>,
226    value_span: Span<'db>,
227    canonical_span: Span<'db>,
228) {
229    Diagnostic::error(
230        db,
231        value_span,
232        "this definition is a duplicate and will be ignored",
233    )
234    .label(db, Level::Error, value_span, "duplicate definition")
235    .label(
236        db,
237        Level::Info,
238        canonical_span,
239        format!("we will map `{id:?}` to this other definition"),
240    )
241    .report(db);
242}
243
244#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, FromImpls)]
245pub enum SymItem<'db> {
246    SymClass(SymAggregate<'db>),
247    SymFunction(SymFunction<'db>),
248    SymPrimitive(SymPrimitive<'db>),
249}
250
251impl<'db> SymItem<'db> {
252    pub fn name(self, db: &'db dyn crate::Db) -> Identifier<'db> {
253        match self {
254            SymItem::SymClass(sym_class) => sym_class.name(db),
255            SymItem::SymFunction(sym_function) => sym_function.name(db),
256            SymItem::SymPrimitive(sym_primitive) => sym_primitive.name(db),
257        }
258    }
259}
260
261impl<'db> Spanned<'db> for SymItem<'db> {
262    fn span(&self, db: &'db dyn dada_ir_ast::Db) -> Span<'db> {
263        match self {
264            SymItem::SymClass(sym_class) => sym_class.span(db),
265            SymItem::SymFunction(sym_function) => sym_function.span(db),
266            SymItem::SymPrimitive(_) => well_known::prelude_span(db),
267        }
268    }
269}
270
271impl<'db> SourceSpanned<'db> for SymItem<'db> {
272    fn source_span(&self, db: &'db dyn dada_ir_ast::Db) -> Span<'db> {
273        match self {
274            SymItem::SymClass(a) => a.source_span(db),
275            SymItem::SymFunction(f) => f.source_span(db),
276            SymItem::SymPrimitive(_) => well_known::prelude_span(db),
277        }
278    }
279}