slang_frontend/semantic_analysis/
type_system.rs

1use super::{error::SemanticAnalysisError, traits::SemanticResult};
2use slang_ir::ast::{
3    BinaryExpr, BinaryOperator, Expression, LetStatement, LiteralValue, UnaryOperator,
4};
5use slang_shared::CompilationContext;
6use slang_types::{TypeId, TYPE_NAME_U32, TYPE_NAME_U64};
7
8/// Checks if a type is an integer type
9///
10/// ### Arguments
11/// * `context` - The compilation context
12/// * `type_id` - The type ID to check
13///
14/// ### Returns
15/// True if the type is an integer type, false otherwise
16pub fn is_integer_type(context: &CompilationContext, type_id: &TypeId) -> bool {
17    context.is_integer_type(type_id)
18}
19
20/// Checks if a type is a float type
21///
22/// ### Arguments
23/// * `context` - The compilation context
24/// * `type_id` - The type ID to check
25///
26/// ### Returns
27/// True if the type is a float type, false otherwise
28pub fn is_float_type(context: &CompilationContext, type_id: &TypeId) -> bool {
29    context.is_float_type(type_id)
30}
31
32/// Checks if a type is an unsigned integer type
33///
34/// ### Arguments
35/// * `context` - The compilation context
36/// * `type_id` - The type to check
37///
38/// ### Returns
39/// * `true` if the type is u32 or u64, `false` otherwise
40pub fn is_unsigned_type(context: &CompilationContext, type_id: &TypeId) -> bool {
41    let type_name = context.get_type_name(type_id);
42    type_name == TYPE_NAME_U64 || type_name == TYPE_NAME_U32
43}
44
45/// Checks if an unspecified integer literal is in the valid range for a target type.
46/// This is used when coercing an integer literal to a specific integer type.
47///
48/// ### Arguments
49/// * `context` - The compilation context
50/// * `expr` - The expression that might contain an unspecified integer literal
51/// * `target_type` - The specific integer type to check against
52///
53/// ### Returns
54/// * `Ok(target_type)` if the literal is in range for the target type
55/// * `Err` with a descriptive error message if the literal is out of range
56/// * `Ok(target_type)` if the expression isn't an unspecified integer literal
57pub fn check_unspecified_int_for_type(
58    context: &CompilationContext,
59    expr: &Expression,
60    target_type: &TypeId,
61) -> SemanticResult {
62    if let Expression::Unary(unary_expr) = expr {
63        if unary_expr.operator == UnaryOperator::Negate {
64            if let Expression::Literal(lit) = &*unary_expr.right {
65                if let LiteralValue::UnspecifiedInteger(n) = &lit.value {
66                    if context.get_type_name(target_type) == "u32"
67                        || context.get_type_name(target_type) == "u64"
68                    {
69                        return Err(SemanticAnalysisError::ValueOutOfRange {
70                            value: format!("-{}", n),
71                            target_type: target_type.clone(),
72                            is_float: false,
73                            location: expr.location(),
74                        });
75                    }
76                }
77            }
78        }
79    }
80
81    if let Expression::Literal(lit) = expr {
82        if let LiteralValue::UnspecifiedInteger(n) = &lit.value {
83            let value_in_range = context.check_value_in_range(n, target_type);
84
85            if value_in_range {
86                return Ok(target_type.clone());
87            } else {
88                return Err(SemanticAnalysisError::ValueOutOfRange {
89                    value: n.to_string(),
90                    target_type: target_type.clone(),
91                    is_float: false,
92                    location: expr.location(),
93                });
94            }
95        }
96    }
97    Ok(target_type.clone())
98}
99
100/// Checks if an unspecified float literal is in the valid range for a target type.
101/// This is used when coercing a float literal to a specific floating-point type.
102///
103/// ### Arguments
104/// * `context` - The compilation context
105/// * `expr` - The expression that might contain an unspecified float literal
106/// * `target_type` - The specific float type to check against (e.g., f32, f64)
107///
108/// ### Returns
109/// * `Ok(target_type)` if the literal is in range for the target type
110/// * `Err` with a descriptive error message if the literal is out of range
111/// * `Ok(target_type)` if the expression isn't an unspecified float literal
112pub fn check_unspecified_float_for_type(
113    context: &CompilationContext,
114    expr: &Expression,
115    target_type: &TypeId,
116) -> SemanticResult {
117    if let Expression::Literal(lit) = expr {
118        if let LiteralValue::UnspecifiedFloat(f) = &lit.value {
119            let value_in_range = context.check_float_value_in_range(f, target_type);
120
121            if value_in_range {
122                return Ok(target_type.clone());
123            } else {
124                return Err(SemanticAnalysisError::ValueOutOfRange {
125                    value: f.to_string(),
126                    target_type: target_type.clone(),
127                    is_float: true,
128                    location: expr.location(),
129                });
130            }
131        }
132    }
133    Ok(target_type.clone())
134}
135
136/// Converts unspecified literal types to concrete types.
137/// This is used to assign a default concrete type when an unspecified literal
138/// is used in a context where the type wasn't explicitly given.
139///
140/// ### Arguments
141/// * `type_id` - The type to finalize
142///
143/// ### Returns
144/// * The concrete type (i64 for unspecified integers, f64 for unspecified floats)
145/// * The original type if it wasn't an unspecified literal type
146pub fn finalize_inferred_type(type_id: TypeId) -> TypeId {
147    if type_id == TypeId::unspecified_int() {
148        TypeId::i64()
149    } else if type_id == TypeId::unspecified_float() {
150        TypeId::f64()
151    } else {
152        type_id
153    }
154}
155
156/// Determines the final type of a variable in a let statement based on both the
157/// declared type (if any) and the initialization expression's type.
158/// Handles type inference and coercion of unspecified literals.
159///
160/// ### Arguments
161/// * `context` - The compilation context
162/// * `let_stmt` - The let statement being analyzed
163/// * `expr_type` - The type of the initialization expression
164///
165/// ### Returns
166/// * `Ok(type_id)` with the final determined type if valid
167/// * `Err` with a SemanticAnalysisError if there's a type mismatch
168pub fn determine_let_statement_type(
169    context: &CompilationContext,
170    let_stmt: &LetStatement,
171    expr_type: TypeId,
172) -> SemanticResult {
173    if let_stmt.expr_type == TypeId::unknown() {
174        return Ok(expr_type);
175    }
176
177    if let_stmt.expr_type == expr_type {
178        if is_unsigned_type(context, &let_stmt.expr_type) {
179            check_unspecified_int_for_type(context, &let_stmt.value, &let_stmt.expr_type)?;
180        }
181        return Ok(let_stmt.expr_type.clone());
182    }
183
184    if context.get_function_type(&let_stmt.expr_type).is_some()
185        && context.get_function_type(&expr_type).is_some()
186    {
187        if let_stmt.expr_type == expr_type {
188            return Ok(let_stmt.expr_type.clone());
189        } else {
190            return Err(SemanticAnalysisError::TypeMismatch {
191                expected: let_stmt.expr_type.clone(),
192                actual: expr_type,
193                context: Some(let_stmt.name.clone()),
194                location: let_stmt.location,
195            });
196        }
197    }
198
199    if expr_type == TypeId::unspecified_int() {
200        return handle_unspecified_int_assignment(context, let_stmt, &expr_type);
201    }
202
203    if expr_type == TypeId::unspecified_float() {
204        return handle_unspecified_float_assignment(context, let_stmt, &expr_type);
205    }
206
207    Err(SemanticAnalysisError::TypeMismatch {
208        expected: let_stmt.expr_type.clone(),
209        actual: expr_type,
210        context: Some(let_stmt.name.clone()),
211        location: let_stmt.location,
212    })
213}
214
215/// Handles assignment of an unspecified integer literal to a variable with a declared type.
216///
217/// ### Arguments
218/// * `context` - The compilation context
219/// * `let_stmt` - The let statement being analyzed.
220/// * `expr_type` - The type of the initialization expression (should be unspecified_int_type).
221///
222/// ### Returns
223/// * `Ok(type_id)` with the declared type if the literal is valid for that type.
224/// * `Err` with a SemanticAnalysisError if there's a type mismatch or value out of range.
225pub fn handle_unspecified_int_assignment(
226    context: &CompilationContext,
227    let_stmt: &LetStatement,
228    _expr_type: &TypeId,
229) -> SemanticResult {
230    if is_integer_type(context, &let_stmt.expr_type) {
231        check_unspecified_int_for_type(context, &let_stmt.value, &let_stmt.expr_type)
232    } else {
233        Err(SemanticAnalysisError::TypeMismatch {
234            expected: let_stmt.expr_type.clone(),
235            actual: TypeId::unspecified_int(),
236            context: Some(let_stmt.name.clone()),
237            location: let_stmt.location,
238        })
239    }
240}
241
242/// Handles assignment of an unspecified float literal to a variable with a declared type.
243///
244/// ### Arguments
245/// * `context` - The compilation context
246/// * `let_stmt` - The let statement being analyzed.
247/// * `expr_type` - The type of the initialization expression (should be unspecified_float_type).
248///
249/// ### Returns
250/// * `Ok(type_id)` with the declared type if the literal is valid for that type.
251/// * `Err` with a SemanticAnalysisError if there's a type mismatch or value out of range.
252pub fn handle_unspecified_float_assignment(
253    context: &CompilationContext,
254    let_stmt: &LetStatement,
255    _expr_type: &TypeId,
256) -> SemanticResult {
257    if is_float_type(context, &let_stmt.expr_type) {
258        check_unspecified_float_for_type(context, &let_stmt.value, &let_stmt.expr_type)
259    } else {
260        Err(SemanticAnalysisError::TypeMismatch {
261            expected: let_stmt.expr_type.clone(),
262            actual: TypeId::unspecified_float(),
263            context: Some(let_stmt.name.clone()),
264            location: let_stmt.location,
265        })
266    }
267}
268
269/// Checks if mixed-type arithmetic operations are allowed, particularly handling
270/// unspecified literals that can be coerced to match the other operand's type.
271/// Handles the following cases:
272/// - Unspecified integer literal + specific integer type
273/// - Unspecified float literal + specific float type
274/// - String concatenation with the + operator
275///
276/// ### Arguments
277/// * `context` - The compilation context
278/// * `left_type` - The type of the left operand
279/// * `right_type` - The type of the right operand
280/// * `bin_expr` - The binary expression containing both operands and the operator
281///
282/// ### Returns
283/// * `Ok(type_id)` with the resulting operation type if allowed
284/// * `Err` with a descriptive error message if the operation is not allowed
285pub fn check_mixed_arithmetic_operation(
286    context: &CompilationContext,
287    left_type: &TypeId,
288    right_type: &TypeId,
289    bin_expr: &BinaryExpr,
290) -> SemanticResult {
291    if *left_type == TypeId::unspecified_int() && is_integer_type(context, right_type) {
292        return check_unspecified_int_for_type(context, &bin_expr.left, right_type);
293    }
294
295    if *right_type == TypeId::unspecified_int() && is_integer_type(context, left_type) {
296        return check_unspecified_int_for_type(context, &bin_expr.right, left_type);
297    }
298
299    if *left_type == TypeId::unspecified_float() && is_float_type(context, right_type) {
300        return check_unspecified_float_for_type(context, &bin_expr.left, right_type);
301    }
302
303    if *right_type == TypeId::unspecified_float() && is_float_type(context, left_type) {
304        return check_unspecified_float_for_type(context, &bin_expr.right, left_type);
305    }
306
307    if bin_expr.operator == BinaryOperator::Add
308        && *left_type == TypeId::string()
309        && *right_type == TypeId::string()
310    {
311        return Ok(TypeId::string());
312    }
313
314    Err(SemanticAnalysisError::OperationTypeMismatch {
315        operator: bin_expr.operator.to_string(),
316        left_type: left_type.clone(),
317        right_type: right_type.clone(),
318        location: bin_expr.location,
319    })
320}