slang_frontend/semantic_analysis/operations/
unary.rs

1use super::super::{error::SemanticAnalysisError, traits::SemanticResult};
2use super::helpers;
3use slang_ir::Location;
4use slang_ir::ast::{Expression, LiteralValue, UnaryExpr, UnaryOperator};
5use slang_shared::CompilationContext;
6use slang_types::TypeId;
7
8use super::super::type_system;
9
10/// Checks if a unary operation is valid for the given operand type.
11/// Handles both arithmetic negation (-) and logical negation (!).
12///
13/// ### Arguments
14/// * `context` - The compilation context
15/// * `unary_expr` - The unary expression to check
16/// * `operand_type` - The type of the operand
17///
18/// ### Returns
19/// * `Ok(type_id)` with the resulting type if the operation is valid
20/// * `Err` with a descriptive error message if the operation is invalid
21pub fn check_unary_operation(
22    context: &CompilationContext,
23    unary_expr: &UnaryExpr,
24    operand_type: &TypeId,
25) -> SemanticResult {
26    match unary_expr.operator {
27        UnaryOperator::Negate => check_negation_operation(context, unary_expr, operand_type),
28        UnaryOperator::Not => check_logical_not_operation(operand_type, &unary_expr.location),
29    }
30}
31
32/// Checks if arithmetic negation (-) is valid for the given operand type.
33/// Supports signed integers, floats, and unspecified numeric literals.
34/// Unsigned integers cannot be negated.
35///
36/// ### Arguments
37/// * `context` - The compilation context
38/// * `unary_expr` - The unary negation expression
39/// * `operand_type` - The type of the operand
40///
41/// ### Returns
42/// * `Ok(type_id)` with the operand type if negation is valid
43/// * `Err` with a descriptive error message if negation is invalid
44pub fn check_negation_operation(
45    context: &CompilationContext,
46    unary_expr: &UnaryExpr,
47    operand_type: &TypeId,
48) -> SemanticResult {
49    // Handle unspecified integer literals
50    if *operand_type == TypeId::unspecified_int() {
51        if let Expression::Literal(lit) = &*unary_expr.right {
52            if let LiteralValue::UnspecifiedInteger(_value) = &lit.value {
53                return Ok(TypeId::unspecified_int());
54            }
55            return Ok(TypeId::unspecified_float());
56        }
57    }
58
59    // Handle unspecified float literals
60    if *operand_type == TypeId::unspecified_float() {
61        if let Expression::Literal(_) = &*unary_expr.right {
62            return Ok(TypeId::unspecified_float());
63        }
64    }
65
66    // Check if the type is numeric
67    let is_numeric = type_system::is_integer_type(context, operand_type)
68        || type_system::is_float_type(context, operand_type);
69
70    if is_numeric {
71        // Signed types can be negated
72        if is_signed_numeric_type(operand_type) {
73            return Ok(operand_type.clone());
74        }
75
76        // Unsigned types cannot be negated
77        if is_unsigned_integer_type(operand_type) {
78            return Err(SemanticAnalysisError::InvalidUnaryOperation {
79                operator: "-".to_string(),
80                operand_type: operand_type.clone(),
81                location: unary_expr.location,
82            });
83        }
84    }
85
86    Err(SemanticAnalysisError::InvalidUnaryOperation {
87        operator: "-".to_string(),
88        operand_type: operand_type.clone(),
89        location: unary_expr.location,
90    })
91}
92
93/// Checks if logical negation (!) is valid for the given operand type.
94/// Only boolean types can be logically negated.
95///
96/// ### Arguments
97/// * `operand_type` - The type of the operand
98/// * `location` - The source location of the operation
99///
100/// ### Returns
101/// * `Ok(bool_type)` if the operand is boolean
102/// * `Err` with a descriptive error message if the operand is not boolean
103pub fn check_logical_not_operation(operand_type: &TypeId, location: &Location) -> SemanticResult {
104    if helpers::is_boolean_type(operand_type) {
105        Ok(helpers::bool_type())
106    } else {
107        Err(SemanticAnalysisError::InvalidUnaryOperation {
108            operator: "!".to_string(),
109            operand_type: operand_type.clone(),
110            location: *location,
111        })
112    }
113}
114
115/// Checks if a type is a signed numeric type that can be negated.
116/// Includes signed integers (i32, i64) and floating point types (f32, f64).
117///
118/// ### Arguments
119/// * `type_id` - The type to check
120///
121/// ### Returns
122/// * `true` if the type is a signed numeric type
123/// * `false` otherwise
124pub fn is_signed_numeric_type(type_id: &TypeId) -> bool {
125    *type_id == TypeId::i32()
126        || *type_id == TypeId::i64()
127        || *type_id == TypeId::f32()
128        || *type_id == TypeId::f64()
129}
130
131/// Checks if a type is an unsigned integer type that cannot be negated.
132/// Includes u32 and u64 types.
133///
134/// ### Arguments
135/// * `type_id` - The type to check
136///
137/// ### Returns
138/// * `true` if the type is an unsigned integer type
139/// * `false` otherwise
140pub fn is_unsigned_integer_type(type_id: &TypeId) -> bool {
141    *type_id == TypeId::u32() || *type_id == TypeId::u64()
142}