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}