slang_frontend/semantic_analysis/operations/
arithmetic.rs

1use super::super::traits::SemanticResult;
2use super::helpers;
3use slang_ir::Location;
4use slang_ir::ast::{BinaryExpr, BinaryOperator};
5use slang_shared::CompilationContext;
6use slang_types::TypeId;
7
8/// Checks if a type is compatible with an arithmetic operation when both operands have the same type.
9/// Boolean types are not allowed for any arithmetic operation.
10/// String types are only allowed for the Add operator (concatenation).
11/// Unit types and function types are not allowed for any arithmetic operation.
12///
13/// ### Arguments
14/// * `context` - The compilation context
15/// * `type_id` - The type of both operands
16/// * `operator` - The arithmetic operator (+, -, *, /)
17/// * `location` - The source location of the operation
18///
19/// ### Returns
20/// * `Ok(type_id)` if the operation is allowed
21/// * `Err` with a descriptive error message otherwise
22pub fn check_same_type_arithmetic(
23    context: &CompilationContext,
24    type_id: &TypeId,
25    operator: &BinaryOperator,
26    location: &Location,
27) -> SemanticResult {
28    if helpers::is_boolean_type(type_id)
29        || helpers::is_unit_type(type_id)
30        || context.is_function_type(type_id)
31    {
32        return Err(helpers::operation_type_mismatch_error(
33            &operator.to_string(),
34            type_id,
35            type_id,
36            location,
37        ));
38    }
39
40    if helpers::is_string_type(type_id) && operator != &BinaryOperator::Add {
41        return Err(helpers::operation_type_mismatch_error(
42            &operator.to_string(),
43            type_id,
44            type_id,
45            location,
46        ));
47    }
48
49    Ok(type_id.clone())
50}
51
52/// Checks if mixed-type arithmetic operations are allowed, particularly handling
53/// unspecified literals that can be coerced to match the other operand's type.
54/// This function handles type coercion for arithmetic operations when operands have different types.
55///
56/// ### Supported Cases
57/// - Unspecified integer literal + specific integer type
58/// - Unspecified float literal + specific float type  
59/// - String concatenation with the + operator
60///
61/// ### Arguments
62/// * `context` - The compilation context
63/// * `left_type` - The type of the left operand
64/// * `right_type` - The type of the right operand
65/// * `bin_expr` - The binary expression containing both operands and the operator
66///
67/// ### Returns
68/// * `Ok(type_id)` with the resulting operation type if allowed
69/// * `Err` with a descriptive error message if the operation is not allowed
70pub fn check_mixed_arithmetic_operation(
71    context: &CompilationContext,
72    left_type: &TypeId,
73    right_type: &TypeId,
74    bin_expr: &BinaryExpr,
75) -> SemanticResult {
76    if helpers::is_unspecified_integer_type(left_type) && is_integer_type(context, right_type) {
77        return check_unspecified_int_for_type(context, &bin_expr.left, right_type);
78    }
79
80    if helpers::is_unspecified_integer_type(right_type) && is_integer_type(context, left_type) {
81        return check_unspecified_int_for_type(context, &bin_expr.right, left_type);
82    }
83
84    if helpers::is_unspecified_float_type(left_type) && is_float_type(context, right_type) {
85        return check_unspecified_float_for_type(context, &bin_expr.left, right_type);
86    }
87
88    if helpers::is_unspecified_float_type(right_type) && is_float_type(context, left_type) {
89        return check_unspecified_float_for_type(context, &bin_expr.right, left_type);
90    }
91
92    if bin_expr.operator == BinaryOperator::Add
93        && helpers::is_string_type(left_type)
94        && helpers::is_string_type(right_type)
95    {
96        return Ok(left_type.clone());
97    }
98
99    Err(helpers::operation_type_mismatch_error(
100        &bin_expr.operator.to_string(),
101        left_type,
102        right_type,
103        &bin_expr.location,
104    ))
105}
106
107/// Helper function to check if a type is an integer type.
108/// This includes all signed and unsigned integer types but not unspecified integers.
109///
110/// ### Arguments
111/// * `context` - The compilation context
112/// * `type_id` - The type to check
113///
114/// ### Returns
115/// * `true` if the type is a specific integer type
116/// * `false` otherwise
117fn is_integer_type(context: &CompilationContext, type_id: &TypeId) -> bool {
118    helpers::is_numeric_type(context, type_id)
119        && !helpers::is_unspecified_integer_type(type_id)
120        && !is_float_type(context, type_id)
121}
122
123/// Helper function to check if a type is a float type.
124/// This includes all floating-point types but not unspecified floats.
125///
126/// ### Arguments
127/// * `_context` - The compilation context (unused but kept for API consistency)
128/// * `type_id` - The type to check
129///
130/// ### Returns
131/// * `true` if the type is a specific float type
132/// * `false` otherwise
133fn is_float_type(_context: &CompilationContext, type_id: &TypeId) -> bool {
134    use slang_types::PrimitiveType;
135
136    if let Some(primitive) = PrimitiveType::from_int(type_id.0) {
137        matches!(primitive, PrimitiveType::F32 | PrimitiveType::F64)
138    } else {
139        false
140    }
141}
142
143/// Checks if an unspecified integer literal is in the valid range for a target type.
144/// This is used when coercing an integer literal to a specific integer type.
145///
146/// ### Arguments
147/// * `context` - The compilation context
148/// * `expr` - The expression that might contain an unspecified integer literal
149/// * `target_type` - The target integer type to coerce to
150///
151/// ### Returns
152/// * `Ok(target_type)` if the coercion is valid
153/// * `Err` with a range error if the literal is out of bounds
154fn check_unspecified_int_for_type(
155    context: &CompilationContext,
156    expr: &slang_ir::ast::Expression,
157    target_type: &TypeId,
158) -> SemanticResult {
159    // For now, we'll delegate to the type system module
160    // In a future refactor, this logic could be moved here for better encapsulation
161    super::super::type_system::check_unspecified_int_for_type(context, expr, target_type)
162}
163
164/// Checks if an unspecified float literal is in the valid range for a target type.
165/// This is used when coercing a float literal to a specific float type.
166///
167/// ### Arguments
168/// * `context` - The compilation context
169/// * `expr` - The expression that might contain an unspecified float literal
170/// * `target_type` - The target float type to coerce to
171///
172/// ### Returns
173/// * `Ok(target_type)` if the coercion is valid
174/// * `Err` with a range error if the literal is out of bounds
175fn check_unspecified_float_for_type(
176    context: &CompilationContext,
177    expr: &slang_ir::ast::Expression,
178    target_type: &TypeId,
179) -> SemanticResult {
180    // For now, we'll delegate to the type system module
181    // In a future refactor, this logic could be moved here for better encapsulation
182    super::super::type_system::check_unspecified_float_for_type(context, expr, target_type)
183}