1
#![allow(clippy::legacy_numeric_constants)]
2
use crate::field;
3
use std::collections::BTreeSet;
4
use tree_sitter::Node;
5

            
6
use super::ParseContext;
7
use super::domain::parse_domain;
8
use super::keyword_checks::is_keyword_identifier;
9
use super::util::named_children;
10
use crate::diagnostics::diagnostics_api::SymbolKind;
11
use crate::diagnostics::source_map::{HoverInfo, span_with_hover};
12
use crate::errors::{FatalParseError, RecoverableParseError};
13
use crate::expression::parse_expression;
14
use conjure_cp_core::ast::DeclarationPtr;
15
use conjure_cp_core::ast::enumerated::{EnumVariant, EnumeratedType};
16
use conjure_cp_core::ast::{Atom, Metadata};
17
use conjure_cp_core::ast::{
18
    Domain, DomainPtr, Expression, IntVal, Literal, Moo, Name, Range, ReturnType, SymbolTable,
19
    Typeable, eval_constant,
20
};
21

            
22
/// Infer and retain the domain of a value letting when it enters the symbol table.
23
///
24
/// Integer lettings denote a single (possibly parameter-dependent) value:
25
///
26
/// - Constant integers become a ground singleton so dependent domains such as
27
///   `int(1..10**n)` stay tight after the letting is installed.
28
/// - Non-constant integers become a symbolic [`IntVal::Expr`] singleton. This remains
29
///   resolvable after referenced `given` declarations are instantiated and, unlike eager
30
///   interval evaluation, also works when those declarations currently have unbounded or
31
///   full-width (`int`) domains.
32
///
33
/// Eager `Expression::domain_of` must not run here for non-constant integers: domains of
34
/// parameter-dependent arithmetic may remain unresolved until instantiation
35
/// (e.g. BIBD `letting b be (l*v*(v-1))/(k*(k-1))`).
36
332734
fn value_letting_domain(expr: &Expression) -> Option<DomainPtr> {
37
332734
    match expr.return_type() {
38
16728
        ReturnType::Bool => Some(Domain::bool()),
39
        ReturnType::Int => {
40
139319
            if let Some(Literal::Int(value)) = eval_constant(expr) {
41
135048
                return Some(Domain::int(vec![Range::Single(value)]));
42
4271
            }
43
4271
            IntVal::new_expr(Moo::new(expr.clone()))
44
4271
                .ok()
45
4271
                .map(|value| Domain::int(vec![Range::Single(value)]))
46
        }
47
176687
        _ => expr.domain_of(),
48
    }
49
332734
}
50

            
51
/// Parse a letting statement into a SymbolTable containing the declared symbols
52
398354
pub fn parse_letting_statement(
53
398354
    ctx: &mut ParseContext,
54
398354
    letting_statement: Node,
55
398354
) -> Result<Option<SymbolTable>, FatalParseError> {
56
398354
    let Some(keyword) = field!(recover, ctx, letting_statement, "letting_keyword") else {
57
        return Ok(None);
58
    };
59
398354
    span_with_hover(
60
398354
        &keyword,
61
398354
        ctx.source_code,
62
398354
        ctx.source_map,
63
398354
        HoverInfo {
64
398354
            description: "Letting keyword".to_string(),
65
398354
            doc_key: None,
66
398354
            kind: Some(SymbolKind::Letting),
67
398354
            ty: None,
68
398354
            decl_span: None,
69
398354
        },
70
398354
    );
71

            
72
398354
    let mut symbol_table = SymbolTable::new();
73

            
74
398354
    for variable_decl in named_children(&letting_statement) {
75
398402
        let mut temp_symbols = BTreeSet::new();
76

            
77
398402
        let Some(variable_list) = field!(recover, ctx, variable_decl, "variable_list") else {
78
            return Ok(None);
79
        };
80
398402
        for variable in named_children(&variable_list) {
81
398450
            let variable_name = &ctx.source_code[variable.start_byte()..variable.end_byte()];
82

            
83
398450
            if is_keyword_identifier(variable_name) {
84
                ctx.errors.push(RecoverableParseError::new(
85
                    format!("Keyword '{variable_name}' used as identifier"),
86
                    Some(variable.range()),
87
                ));
88
                // still add variable to symbol table to avoid follow-up errors
89
398450
            }
90

            
91
            // Check for duplicate within the same statement
92
398450
            if temp_symbols.contains(variable_name) {
93
24
                ctx.errors.push(RecoverableParseError::new(
94
24
                    format!(
95
24
                        "Variable '{}' is already declared in this letting statement",
96
24
                        variable_name
97
24
                    ),
98
24
                    Some(variable.range()),
99
24
                ));
100
                // don't return here, as we can still add the other variables to the symbol table
101
24
                continue;
102
398426
            }
103

            
104
            // Check for duplicate declaration across statements
105
398426
            let name = Name::user(variable_name);
106
398426
            if let Some(symbols) = &ctx.symbols
107
398426
                && symbols.read().lookup(&name).is_some()
108
            {
109
72
                let previous_line = ctx.lookup_decl_line(&name);
110
72
                ctx.errors.push(RecoverableParseError::new(
111
72
                    match previous_line {
112
72
                        Some(line) => format!(
113
                            "Variable '{}' is already declared in a previous statement on line {}",
114
72
                            variable_name, line
115
                        ),
116
                        None => format!(
117
                            "Variable '{}' is already declared in a previous statement",
118
                            variable_name
119
                        ),
120
                    },
121
72
                    Some(variable.range()),
122
                ));
123
                // don't return here, as we can still add the other variables to the symbol table
124
72
                continue;
125
398354
            }
126

            
127
398354
            temp_symbols.insert(variable_name);
128
398354
            let hover = HoverInfo {
129
398354
                description: format!("Letting variable: {variable_name}"),
130
398354
                doc_key: None,
131
398354
                kind: Some(SymbolKind::LettingVar),
132
398354
                ty: None,
133
398354
                decl_span: None,
134
398354
            };
135
398354
            let span_id = span_with_hover(&variable, ctx.source_code, ctx.source_map, hover);
136
398354
            ctx.save_decl_span(name, span_id);
137
        }
138

            
139
398402
        let Some(expr_or_domain) = field!(recover, ctx, variable_decl, "expr_or_domain") else {
140
            return Ok(None);
141
        };
142

            
143
398402
        if expr_or_domain.kind() == "unnamed_type_declaration" {
144
24
            let Some(size_node) = field!(recover, ctx, expr_or_domain, "size") else {
145
                return Ok(None);
146
            };
147
24
            let Some(size_expr) = parse_expression(ctx, size_node)? else {
148
                return Ok(None);
149
            };
150
24
            let Some(Literal::Int(size)) = eval_constant(&size_expr) else {
151
                ctx.record_error(RecoverableParseError::new(
152
                    "Unnamed type size must be a constant integer".to_string(),
153
                    Some(size_node.range()),
154
                ));
155
                return Ok(None);
156
            };
157
24
            if size < 1 {
158
                ctx.record_error(RecoverableParseError::new(
159
                    "Unnamed type size must be positive".to_string(),
160
                    Some(size_node.range()),
161
                ));
162
                return Ok(None);
163
24
            }
164
24
            for name in temp_symbols {
165
24
                symbol_table.insert(DeclarationPtr::new_unnamed(Name::user(name), size as u32));
166
            }
167
398378
        } else if expr_or_domain.kind() == "enum_type_declaration" {
168
20
            let Some(members) = field!(recover, ctx, expr_or_domain, "members") else {
169
                return Ok(None);
170
            };
171
20
            let variants = named_children(&members)
172
32
                .map(|node| Name::user(&ctx.source_code[node.start_byte()..node.end_byte()]))
173
                .collect::<Vec<_>>();
174
20
            for name in temp_symbols {
175
20
                let ty = Moo::new(EnumeratedType::new(Name::user(name), variants.clone()));
176
20
                for (index, variant) in variants.iter().enumerate() {
177
32
                    if symbol_table.lookup(variant).is_some()
178
32
                        || ctx
179
32
                            .symbols
180
32
                            .as_ref()
181
32
                            .is_some_and(|symbols| symbols.read().lookup(variant).is_some())
182
32
                        || variant == &ty.name
183
                    {
184
                        ctx.record_error(RecoverableParseError::new(
185
                            format!("Enum member '{variant}' is already declared"),
186
                            Some(members.range()),
187
                        ));
188
                        continue;
189
32
                    }
190
32
                    let value = Expression::Atomic(
191
32
                        Metadata::new(),
192
32
                        Atom::Literal(Literal::EnumVariant(EnumVariant::new(
193
32
                            ty.clone(),
194
32
                            index as u32,
195
32
                        ))),
196
32
                    );
197
32
                    symbol_table.insert(DeclarationPtr::new_value_letting(variant.clone(), value));
198
                }
199
20
                symbol_table.insert(DeclarationPtr::new_enumerated(Name::user(name), ty));
200
            }
201
398358
        } else if variable_decl.child_by_field_name("domain").is_some() {
202
65504
            for name in temp_symbols {
203
65528
                let Some(domain) = parse_domain(ctx, expr_or_domain)? else {
204
24
                    continue;
205
                };
206

            
207
65504
                symbol_table.insert(DeclarationPtr::new_domain_letting(Name::user(name), domain));
208
            }
209
        } else {
210
332854
            for name in temp_symbols {
211
332782
                let Some(expr) = parse_expression(ctx, expr_or_domain)? else {
212
48
                    continue;
213
                };
214
332734
                let declaration = match value_letting_domain(&expr) {
215
323565
                    Some(domain) => DeclarationPtr::new_value_letting_with_domain(
216
323565
                        Name::user(name),
217
323565
                        expr,
218
323565
                        domain,
219
323565
                    ),
220
9169
                    None => DeclarationPtr::new_value_letting(Name::user(name), expr),
221
                };
222
332734
                symbol_table.insert(declaration);
223
            }
224
        }
225
    }
226

            
227
398354
    Ok(Some(symbol_table))
228
398354
}
229

            
230
#[cfg(test)]
231
mod tests {
232
    use super::*;
233
    use crate::parser::parse_model::parse_essence;
234
    use conjure_cp_core::ast::{DeclarationKind, Name};
235
    use std::ops::Deref;
236
    use std::time::{Duration, Instant};
237

            
238
    /// Arithmetic lettings over bare `int` givens must use a symbolic singleton domain and
239
    /// finish parsing quickly (not enumerate the full Oxide integer range).
240
    #[test]
241
1
    fn arithmetic_letting_over_bare_int_givens_uses_symbolic_domain() {
242
1
        let src = r#"
243
1
language ESSENCE' 1.0
244
1
given v, k, l : int
245
1
letting b be (l*v*(v-1))/(k*(k-1))
246
1
find x: bool
247
1
such that x
248
1
"#;
249
1
        let started = Instant::now();
250
1
        let (model, _) = parse_essence(src).expect("model should parse");
251
1
        assert!(
252
1
            started.elapsed() < Duration::from_secs(2),
253
            "parse hung for {:?}; eager domain_of over bare int is likely back",
254
            started.elapsed()
255
        );
256

            
257
1
        let symbols = model.symbols();
258
1
        let decl = symbols
259
1
            .lookup(&Name::user("b"))
260
1
            .expect("letting b should be in the symbol table");
261
1
        match decl.kind().deref() {
262
            DeclarationKind::ValueLetting(_, Some(domain)) => {
263
1
                let ranges = domain.as_int().expect("letting b domain should be int");
264
1
                assert_eq!(
265
1
                    ranges.len(),
266
1
                    1,
267
                    "expected a singleton domain, got {ranges:?}"
268
                );
269
1
                assert!(
270
1
                    matches!(&ranges[0], Range::Single(IntVal::Expr(_))),
271
                    "expected symbolic IntVal::Expr singleton, got {:?}",
272
                    ranges[0]
273
                );
274
            }
275
            other => panic!("expected ValueLetting with retained domain, got {other:?}"),
276
        }
277
1
    }
278

            
279
    /// Constant integer lettings must keep a ground singleton so dependent domains stay tight.
280
    #[test]
281
1
    fn constant_int_letting_keeps_ground_singleton_domain() {
282
1
        let src = r#"
283
1
language ESSENCE' 1.0
284
1
letting n be 3
285
1
find x: int(1..10**n)
286
1
such that x = 1
287
1
"#;
288
1
        let (model, _) = parse_essence(src).expect("model should parse");
289
1
        let symbols = model.symbols();
290
1
        let decl = symbols
291
1
            .lookup(&Name::user("n"))
292
1
            .expect("letting n should be in the symbol table");
293
1
        match decl.kind().deref() {
294
            DeclarationKind::ValueLetting(_, Some(domain)) => {
295
1
                let ranges = domain.as_int().expect("letting n domain should be int");
296
1
                assert_eq!(
297
1
                    ranges.len(),
298
1
                    1,
299
                    "expected a singleton domain, got {ranges:?}"
300
                );
301
1
                assert!(
302
1
                    matches!(&ranges[0], Range::Single(IntVal::Const(3))),
303
                    "expected ground IntVal::Const(3) singleton, got {:?}",
304
                    ranges[0]
305
                );
306
            }
307
            other => panic!("expected ValueLetting with retained domain, got {other:?}"),
308
        }
309
1
    }
310
}