11use rustc_hash:: FxHashMap ;
22use serde:: Serialize ;
3- use std:: { borrow:: Cow , collections:: HashSet , mem} ;
4- use unicase:: Ascii ;
3+ use std:: { collections:: HashSet , mem} ;
54
65use crate :: arena:: Arena ;
76use crate :: typing:: { Record , Type } ;
@@ -49,6 +48,7 @@ pub type AnalysisResult<A> = Result<A, AnalysisError>;
4948/// This structure contains the type information needed to perform static analysis
5049/// on EventQL queries, including the default scope with built-in functions and
5150/// the type information for event records.
51+ #[ derive( Default ) ]
5252pub struct AnalysisOptions {
5353 /// The default scope containing built-in functions and their type signatures.
5454 pub default_scope : Scope ,
@@ -59,40 +59,7 @@ pub struct AnalysisOptions {
5959 /// This set allows users to register custom type names that can be used
6060 /// in type conversion expressions (e.g., `field AS CustomType`). Custom
6161 /// type names are case-insensitive.
62- pub custom_types : HashSet < Ascii < String > > ,
63- }
64-
65- impl AnalysisOptions {
66- /// Adds a custom type name to the analysis options.
67- ///
68- /// Custom types allow you to use type conversion syntax with types that are
69- /// not part of the standard EventQL type system. The type name is stored
70- /// case-insensitively.
71- ///
72- /// # Arguments
73- ///
74- /// * `value` - The custom type name to register
75- ///
76- /// # Returns
77- ///
78- /// Returns `self` to allow for method chaining.
79- pub fn add_custom_type < ' a > ( mut self , value : impl Into < Cow < ' a , str > > ) -> Self {
80- match value. into ( ) {
81- Cow :: Borrowed ( t) => self . custom_types . insert ( Ascii :: new ( t. to_owned ( ) ) ) ,
82- Cow :: Owned ( t) => self . custom_types . insert ( Ascii :: new ( t) ) ,
83- } ;
84-
85- self
86- }
87-
88- /// Creates empty analysis options with no functions, no event type, and no custom types.
89- pub fn empty ( ) -> Self {
90- Self {
91- default_scope : Scope :: default ( ) ,
92- event_type_info : Type :: default ( ) ,
93- custom_types : HashSet :: default ( ) ,
94- }
95- }
62+ pub custom_types : HashSet < StrRef > ,
9663}
9764
9865/// Represents a variable scope during static analysis.
@@ -1125,7 +1092,7 @@ impl<'a> Analysis<'a> {
11251092 Operator :: As => {
11261093 let rhs = self . arena . exprs . get ( binary. rhs ) ;
11271094 if let Value :: Id ( name) = rhs. value {
1128- return if let Some ( tpe) = name_to_type ( self . arena , self . options , name) {
1095+ return if let Some ( tpe) = resolve_type ( self . arena , self . options , name) {
11291096 // NOTE - we could check if it's safe to convert the left branch to that type
11301097 Ok ( tpe)
11311098 } else {
@@ -1466,7 +1433,7 @@ impl<'a> Analysis<'a> {
14661433 Operator :: Add | Operator :: Sub | Operator :: Mul | Operator :: Div => Type :: Number ,
14671434 Operator :: As => {
14681435 if let Value :: Id ( n) = self . arena . exprs . get ( binary. rhs ) . value
1469- && let Some ( tpe) = name_to_type ( self . arena , self . options , n)
1436+ && let Some ( tpe) = resolve_type ( self . arena , self . options , n)
14701437 {
14711438 tpe
14721439 } else {
@@ -1644,34 +1611,46 @@ impl Arena {
16441611/// - `"date"` → [`Type::Date`]
16451612/// - `"time"` → [`Type::Time`]
16461613/// - `"datetime"` → [`Type::DateTime`]
1647- pub ( crate ) fn name_to_type (
1614+ ///
1615+ /// note: Registered custom types are also recognized (case-insensitive).
1616+ pub ( crate ) fn resolve_type_from_str (
16481617 arena : & Arena ,
16491618 opts : & AnalysisOptions ,
1650- name_ref : StrRef ,
1619+ name : & str ,
16511620) -> Option < Type > {
1652- let name = arena. strings . get ( name_ref) ;
1653-
16541621 if name. eq_ignore_ascii_case ( "string" ) {
16551622 Some ( Type :: String )
1656- } else if name. eq_ignore_ascii_case ( "int" ) || name. eq_ignore_ascii_case ( "float64" ) {
1623+ } else if name. eq_ignore_ascii_case ( "int" )
1624+ || name. eq_ignore_ascii_case ( "float64" )
1625+ || name. eq_ignore_ascii_case ( "number" )
1626+ {
16571627 Some ( Type :: Number )
1658- } else if name. eq_ignore_ascii_case ( "boolean" ) {
1628+ } else if name. eq_ignore_ascii_case ( "boolean" ) || name . eq_ignore_ascii_case ( "bool" ) {
16591629 Some ( Type :: Bool )
16601630 } else if name. eq_ignore_ascii_case ( "date" ) {
16611631 Some ( Type :: Date )
16621632 } else if name. eq_ignore_ascii_case ( "time" ) {
16631633 Some ( Type :: Time )
16641634 } else if name. eq_ignore_ascii_case ( "datetime" ) {
16651635 Some ( Type :: DateTime )
1666- } else if opts . custom_types . contains ( & Ascii :: new ( name. to_owned ( ) ) ) {
1667- // ^ Sad we have to allocate here for no reason
1668-
1669- Some ( Type :: Custom ( name_ref ) )
1636+ } else if let Some ( str_ref ) = arena . strings . str_ref_no_case ( name)
1637+ && opts . custom_types . contains ( & str_ref )
1638+ {
1639+ Some ( Type :: Custom ( str_ref ) )
16701640 } else {
16711641 None
16721642 }
16731643}
16741644
1645+ pub ( crate ) fn resolve_type (
1646+ arena : & Arena ,
1647+ opts : & AnalysisOptions ,
1648+ name_ref : StrRef ,
1649+ ) -> Option < Type > {
1650+ let name = arena. strings . get ( name_ref) ;
1651+ resolve_type_from_str ( arena, opts, name)
1652+ }
1653+
16751654pub ( crate ) fn display_type ( arena : & Arena , tpe : Type ) -> String {
16761655 fn go ( buffer : & mut String , arena : & Arena , tpe : Type ) {
16771656 match tpe {
0 commit comments