slang_frontend/semantic_analysis/operations/
relational.rs

1use super::super::traits::SemanticResult;
2use super::helpers::{
3    bool_type, is_unspecified_float_type, is_unspecified_integer_type,
4    operation_type_mismatch_error, types_are_identical,
5};
6use slang_ir::Location;
7use slang_ir::ast::BinaryOperator;
8use slang_shared::CompilationContext;
9use slang_types::TypeId;
10
11use super::super::type_system;
12
13/// Checks if an operator is a relational operator (requires numeric types)
14///
15/// ### Arguments
16/// * `operator` - The binary operator to check
17///
18/// ### Returns
19/// * `true` if the operator requires numeric types, `false` otherwise
20fn is_strictly_relational_operator(operator: &BinaryOperator) -> bool {
21    matches!(
22        operator,
23        BinaryOperator::GreaterThan
24            | BinaryOperator::LessThan
25            | BinaryOperator::GreaterThanOrEqual
26            | BinaryOperator::LessThanOrEqual
27    )
28}
29
30/// Checks if types are compatible for unspecified literal coercion in relational operations
31///
32/// ### Arguments
33/// * `context` - The compilation context
34/// * `left_type` - The type of the left operand
35/// * `right_type` - The type of the right operand
36///
37/// ### Returns
38/// * `true` if one operand is an unspecified literal compatible with the other, `false` otherwise
39fn can_coerce_for_relational(
40    context: &CompilationContext,
41    left_type: &TypeId,
42    right_type: &TypeId,
43) -> bool {
44    (is_unspecified_integer_type(left_type) && type_system::is_integer_type(context, right_type))
45        || (is_unspecified_integer_type(right_type)
46            && type_system::is_integer_type(context, left_type))
47        || (is_unspecified_float_type(left_type) && type_system::is_float_type(context, right_type))
48        || (is_unspecified_float_type(right_type) && type_system::is_float_type(context, left_type))
49}
50
51/// Checks if types are compatible for relational operations (>, <, >=, <=, ==, !=).
52/// Types must be comparable with each other, which means they're either:
53/// - Exactly the same type (except Unit)
54/// - Unspecified integer literal and an integer type
55/// - Unspecified float literal and a float type
56///
57/// ### Arguments
58/// * `context` - The compilation context
59/// * `left_type` - The type of the left operand
60/// * `right_type` - The type of the right operand
61/// * `operator` - The relational operator
62/// * `location` - The source location of the operation
63///
64/// ### Returns
65/// * `Ok(bool_type())` if the types are comparable
66/// * `Err` with a descriptive error message otherwise
67pub fn check_relational_operation(
68    context: &CompilationContext,
69    left_type: &TypeId,
70    right_type: &TypeId,
71    operator: &BinaryOperator,
72    location: &Location,
73) -> SemanticResult {
74    // Strictly relational operators (>, <, >=, <=) require numeric types
75    if is_strictly_relational_operator(operator)
76        && (!context.is_numeric_type(left_type) || !context.is_numeric_type(right_type))
77    {
78        return Err(operation_type_mismatch_error(
79            &operator.to_string(),
80            left_type,
81            right_type,
82            location,
83        ));
84    }
85
86    // Check for type compatibility
87    if (types_are_identical(left_type, right_type) && *left_type != TypeId::unit())
88        || can_coerce_for_relational(context, left_type, right_type)
89    {
90        Ok(bool_type())
91    } else {
92        Err(operation_type_mismatch_error(
93            &operator.to_string(),
94            left_type,
95            right_type,
96            location,
97        ))
98    }
99}