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}