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}