slang_frontend/semantic_analysis/validation/coercion.rs
1use slang_ir::ast::{BinaryExpr, BinaryOperator, Expression, LiteralValue, UnaryOperator};
2use slang_shared::CompilationContext;
3use slang_types::{TypeId, TYPE_NAME_U32, TYPE_NAME_U64};
4
5use super::super::{
6 traits::SemanticResult,
7 error::SemanticAnalysisError,
8};
9
10/// Handles all type coercion rules and operations
11///
12/// This module is responsible for determining when and how types can be
13/// automatically converted or coerced, particularly for unspecified literal types.
14pub struct TypeCoercion<'a> {
15 context: &'a CompilationContext,
16}
17
18impl<'a> TypeCoercion<'a> {
19 /// Creates a new type coercion handler
20 ///
21 /// # Arguments
22 /// * `context` - The compilation context for type information
23 pub fn new(context: &'a CompilationContext) -> Self {
24 Self { context }
25 }
26
27 /// Checks if an unspecified literal can be coerced to a target type
28 ///
29 /// # Arguments
30 /// * `source_type` - The unspecified literal type
31 /// * `target_type` - The target type to coerce to
32 ///
33 /// # Returns
34 /// `true` if coercion is possible, `false` otherwise
35 pub fn can_coerce(&self, source_type: &TypeId, target_type: &TypeId) -> bool {
36 // Unspecified int to specific integer types
37 if *source_type == TypeId::unspecified_int() {
38 return self.context.is_integer_type(target_type);
39 }
40
41 // Unspecified float to specific float types
42 if *source_type == TypeId::unspecified_float() {
43 return self.context.is_float_type(target_type);
44 }
45
46 false
47 }
48
49 /// Checks for mixed-type arithmetic operations with coercion
50 ///
51 /// Handles cases where unspecified literals can be coerced to match
52 /// the other operand's type in arithmetic operations.
53 ///
54 /// # Arguments
55 /// * `left_type` - Type of the left operand
56 /// * `right_type` - Type of the right operand
57 /// * `bin_expr` - The binary expression for context
58 ///
59 /// # Returns
60 /// Result with the resulting type or an error
61 pub fn check_mixed_arithmetic_operation(
62 &self,
63 left_type: &TypeId,
64 right_type: &TypeId,
65 bin_expr: &BinaryExpr,
66 ) -> SemanticResult {
67 // Left operand is unspecified int, right is specific integer
68 if *left_type == TypeId::unspecified_int()
69 && self.context.is_integer_type(right_type)
70 {
71 return check_unspecified_int_for_type(self.context, &bin_expr.left, right_type);
72 }
73
74 // Right operand is unspecified int, left is specific integer
75 if *right_type == TypeId::unspecified_int()
76 && self.context.is_integer_type(left_type)
77 {
78 return check_unspecified_int_for_type(self.context, &bin_expr.right, left_type);
79 }
80
81 // Left operand is unspecified float, right is specific float
82 if *left_type == TypeId::unspecified_float()
83 && self.context.is_float_type(right_type)
84 {
85 return check_unspecified_float_for_type(self.context, &bin_expr.left, right_type);
86 }
87
88 // Right operand is unspecified float, left is specific float
89 if *right_type == TypeId::unspecified_float()
90 && self.context.is_float_type(left_type)
91 {
92 return check_unspecified_float_for_type(self.context, &bin_expr.right, left_type);
93 }
94
95 // String concatenation
96 if bin_expr.operator == BinaryOperator::Add
97 && *left_type == TypeId::string()
98 && *right_type == TypeId::string()
99 {
100 return Ok(TypeId::string());
101 }
102
103 Err(SemanticAnalysisError::OperationTypeMismatch {
104 operator: bin_expr.operator.to_string(),
105 left_type: left_type.clone(),
106 right_type: right_type.clone(),
107 location: bin_expr.location,
108 })
109 }
110}
111
112/// Checks if an unspecified integer literal is in the valid range for a target type.
113/// This is used when coercing an integer literal to a specific integer type.
114///
115/// ### Arguments
116/// * `context` - The compilation context
117/// * `expr` - The expression that might contain an unspecified integer literal
118/// * `target_type` - The specific integer type to check against
119///
120/// ### Returns
121/// * `Ok(target_type)` if the literal is in range for the target type
122/// * `Err` with a descriptive error message if the literal is out of range
123/// * `Ok(target_type)` if the expression isn't an unspecified integer literal
124pub fn check_unspecified_int_for_type(
125 context: &CompilationContext,
126 expr: &Expression,
127 target_type: &TypeId,
128) -> SemanticResult {
129 // Handle negative unspecified integers (unary negation)
130 if let Expression::Unary(unary_expr) = expr {
131 if unary_expr.operator == UnaryOperator::Negate {
132 if let Expression::Literal(lit) = &*unary_expr.right {
133 if let LiteralValue::UnspecifiedInteger(n) = &lit.value {
134 // Unsigned types cannot hold negative values
135 if context.get_type_name(target_type) == TYPE_NAME_U32
136 || context.get_type_name(target_type) == TYPE_NAME_U64
137 {
138 return Err(SemanticAnalysisError::ValueOutOfRange {
139 value: format!("-{}", n),
140 target_type: target_type.clone(),
141 is_float: false,
142 location: expr.location(),
143 });
144 }
145 }
146 }
147 }
148 }
149
150 // Handle positive unspecified integers
151 if let Expression::Literal(lit) = expr {
152 if let LiteralValue::UnspecifiedInteger(n) = &lit.value {
153 let value_in_range = context.check_value_in_range(n, target_type);
154
155 if value_in_range {
156 return Ok(target_type.clone());
157 } else {
158 return Err(SemanticAnalysisError::ValueOutOfRange {
159 value: n.to_string(),
160 target_type: target_type.clone(),
161 is_float: false,
162 location: expr.location(),
163 });
164 }
165 }
166 }
167 Ok(target_type.clone())
168}
169
170/// Checks if an unspecified float literal is in the valid range for a target type.
171/// This is used when coercing a float literal to a specific floating-point type.
172///
173/// ### Arguments
174/// * `context` - The compilation context
175/// * `expr` - The expression that might contain an unspecified float literal
176/// * `target_type` - The specific float type to check against (e.g., f32, f64)
177///
178/// ### Returns
179/// * `Ok(target_type)` if the literal is in range for the target type
180/// * `Err` with a descriptive error message if the literal is out of range
181/// * `Ok(target_type)` if the expression isn't an unspecified float literal
182pub fn check_unspecified_float_for_type(
183 context: &CompilationContext,
184 expr: &Expression,
185 target_type: &TypeId,
186) -> SemanticResult {
187 if let Expression::Literal(lit) = expr {
188 if let LiteralValue::UnspecifiedFloat(f) = &lit.value {
189 let value_in_range = context.check_float_value_in_range(f, target_type);
190
191 if value_in_range {
192 return Ok(target_type.clone());
193 } else {
194 return Err(SemanticAnalysisError::ValueOutOfRange {
195 value: f.to_string(),
196 target_type: target_type.clone(),
197 is_float: true,
198 location: expr.location(),
199 });
200 }
201 }
202 }
203 Ok(target_type.clone())
204}