Skip to main content

elo_rust/codegen/
types.rs

1//! Type system mapping between ELO and Rust types
2
3use std::collections::HashMap;
4
5/// Represents the Rust type equivalent of an ELO type
6#[derive(Debug, Clone, PartialEq, Eq)]
7pub enum RustType {
8    /// String type (&str or String)
9    String,
10    /// Integer type (i64 or i32)
11    Integer,
12    /// Float type (f64 or f32)
13    Float,
14    /// Boolean type
15    Bool,
16    /// Date type (chrono::NaiveDate)
17    Date,
18    /// Time type (chrono::NaiveTime)
19    Time,
20    /// Duration type (chrono::Duration)
21    Duration,
22    /// Option type
23    Option(Box<RustType>),
24    /// Array/slice type
25    Array(Box<RustType>),
26    /// Custom user-defined type
27    Custom(String),
28    /// Unknown/unresolved type
29    Unknown,
30}
31
32impl RustType {
33    /// Get the Rust type string representation
34    pub fn to_rust_string(&self) -> String {
35        match self {
36            Self::String => "&str".to_string(),
37            Self::Integer => "i64".to_string(),
38            Self::Float => "f64".to_string(),
39            Self::Bool => "bool".to_string(),
40            Self::Date => "chrono::NaiveDate".to_string(),
41            Self::Time => "chrono::NaiveTime".to_string(),
42            Self::Duration => "chrono::Duration".to_string(),
43            Self::Option(inner) => format!("Option<{}>", inner.to_rust_string()),
44            Self::Array(inner) => format!("&[{}]", inner.to_rust_string()),
45            Self::Custom(name) => name.clone(),
46            Self::Unknown => "()".to_string(),
47        }
48    }
49
50    /// Check if this type is compatible with another type
51    pub fn is_compatible_with(&self, other: &RustType) -> bool {
52        match (self, other) {
53            // Same types are compatible
54            (Self::String, Self::String)
55            | (Self::Integer, Self::Integer)
56            | (Self::Float, Self::Float)
57            | (Self::Bool, Self::Bool)
58            | (Self::Date, Self::Date)
59            | (Self::Time, Self::Time)
60            | (Self::Duration, Self::Duration)
61            | (Self::Unknown, _)
62            | (_, Self::Unknown) => true,
63
64            // Option types are compatible if inner types are
65            (Self::Option(a), Self::Option(b)) => a.is_compatible_with(b),
66
67            // Array types are compatible if element types are
68            (Self::Array(a), Self::Array(b)) => a.is_compatible_with(b),
69
70            // Custom types are compatible if names match
71            (Self::Custom(a), Self::Custom(b)) => a == b,
72
73            // Different types are not compatible
74            _ => false,
75        }
76    }
77}
78
79/// Information about a custom type (struct/enum)
80#[derive(Debug, Clone)]
81pub struct TypeInfo {
82    /// The name of the type
83    pub name: String,
84    /// Field names and their types
85    fields: HashMap<String, RustType>,
86}
87
88impl TypeInfo {
89    /// Create a new type info with the given name
90    pub fn new(name: &str) -> Self {
91        Self {
92            name: name.to_string(),
93            fields: HashMap::new(),
94        }
95    }
96
97    /// Add a field to the type
98    pub fn add_field(&mut self, name: &str, field_type: RustType) {
99        self.fields.insert(name.to_string(), field_type);
100    }
101
102    /// Get the type of a field
103    pub fn get_field(&self, name: &str) -> Option<&RustType> {
104        self.fields.get(name)
105    }
106
107    /// Get all fields
108    pub fn fields(&self) -> &HashMap<String, RustType> {
109        &self.fields
110    }
111}
112
113/// Context for type resolution and type checking
114///
115/// Maintains a registry of user-defined types and provides field lookup,
116/// type compatibility checking, and type inference from literal values.
117///
118/// # Example
119///
120/// ```ignore
121/// use elo_rust::codegen::types::{TypeContext, TypeInfo, RustType};
122///
123/// let mut context = TypeContext::new();
124///
125/// let mut user_type = TypeInfo::new("User");
126/// user_type.add_field("email", RustType::String);
127/// user_type.add_field("age", RustType::Integer);
128///
129/// context.register_type("User", user_type);
130///
131/// // Look up field types
132/// assert_eq!(
133///     context.get_field_type("User", "email"),
134///     Some(&RustType::String)
135/// );
136/// ```
137#[derive(Debug, Clone, Default)]
138pub struct TypeContext {
139    /// Registered user-defined types (name -> type info)
140    types: HashMap<String, TypeInfo>,
141}
142
143impl TypeContext {
144    /// Create a new empty type context
145    pub fn new() -> Self {
146        Self {
147            types: HashMap::new(),
148        }
149    }
150
151    /// Register a new custom type in the context
152    ///
153    /// # Arguments
154    ///
155    /// * `name` - The name of the type (e.g., "User", "Product")
156    /// * `type_info` - The type information including field definitions
157    pub fn register_type(&mut self, name: &str, type_info: TypeInfo) {
158        self.types.insert(name.to_string(), type_info);
159    }
160
161    /// Look up the type of a field in a registered type
162    ///
163    /// # Arguments
164    ///
165    /// * `type_name` - The name of the type (e.g., "User")
166    /// * `field_name` - The name of the field (e.g., "email")
167    ///
168    /// # Returns
169    ///
170    /// `Some(&RustType)` if the type and field exist, `None` otherwise
171    pub fn get_field_type(&self, type_name: &str, field_name: &str) -> Option<&RustType> {
172        self.types
173            .get(type_name)
174            .and_then(|t| t.get_field(field_name))
175    }
176
177    /// Check if this context has any registered types
178    pub fn is_empty(&self) -> bool {
179        self.types.is_empty()
180    }
181
182    /// Get all registered type names
183    pub fn list_all_type_names(&self) -> Vec<String> {
184        self.types.keys().cloned().collect()
185    }
186
187    /// Infer the type from a literal value
188    ///
189    /// Attempts to determine the Rust type of a literal string by:
190    /// 1. Trying to parse as integer
191    /// 2. Trying to parse as float
192    /// 3. Checking for boolean keywords (true/false)
193    /// 4. Checking for quoted strings
194    ///
195    /// # Arguments
196    ///
197    /// * `literal` - The literal value as a string
198    ///
199    /// # Returns
200    ///
201    /// The inferred `RustType`, or `RustType::Unknown` if unable to infer
202    pub fn infer_from_literal(&self, literal: &str) -> RustType {
203        // Try to parse as integer
204        if literal.parse::<i64>().is_ok() {
205            return RustType::Integer;
206        }
207
208        // Try to parse as float
209        if literal.parse::<f64>().is_ok() {
210            return RustType::Float;
211        }
212
213        // Check for boolean literals
214        if literal == "true" || literal == "false" {
215            return RustType::Bool;
216        }
217
218        // Check for string literals (quoted)
219        if (literal.starts_with('"') && literal.ends_with('"'))
220            || (literal.starts_with('\'') && literal.ends_with('\''))
221        {
222            return RustType::String;
223        }
224
225        RustType::Unknown
226    }
227}
228
229#[cfg(test)]
230mod tests {
231    use super::*;
232
233    #[test]
234    fn test_basic_type_strings() {
235        assert_eq!(RustType::String.to_rust_string(), "&str");
236        assert_eq!(RustType::Integer.to_rust_string(), "i64");
237        assert_eq!(RustType::Bool.to_rust_string(), "bool");
238    }
239
240    #[test]
241    fn test_option_type_string() {
242        let opt_string = RustType::Option(Box::new(RustType::String));
243        assert_eq!(opt_string.to_rust_string(), "Option<&str>");
244    }
245
246    #[test]
247    fn test_array_type_string() {
248        let array_int = RustType::Array(Box::new(RustType::Integer));
249        assert_eq!(array_int.to_rust_string(), "&[i64]");
250    }
251}