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}