From 25476d1cea07fa96874dbdfafaf30c2c4e9d1b3a Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Tue, 27 Sep 2022 08:52:39 +0800 Subject: [PATCH 1/3] Mark debug functions cold. --- src/ast/ast.rs | 2 ++ src/ast/expr.rs | 9 ++++++++- src/ast/ident.rs | 2 ++ src/ast/namespace.rs | 2 ++ src/ast/stmt.rs | 7 ++++++- src/engine.rs | 3 ++- src/eval/global_state.rs | 3 ++- src/func/callable_function.rs | 2 ++ src/module/mod.rs | 2 ++ src/packages/iter_basic.rs | 2 ++ src/tokenizer.rs | 5 ++++- src/types/custom_types.rs | 2 ++ src/types/dynamic.rs | 2 ++ src/types/fn_ptr.rs | 2 ++ src/types/immutable_string.rs | 3 ++- src/types/scope.rs | 1 - 16 files changed, 42 insertions(+), 7 deletions(-) diff --git a/src/ast/ast.rs b/src/ast/ast.rs index 902a6856..f6ebb50c 100644 --- a/src/ast/ast.rs +++ b/src/ast/ast.rs @@ -42,6 +42,8 @@ impl Default for AST { } impl fmt::Debug for AST { + #[cold] + #[inline(never)] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let mut fp = f.debug_struct("AST"); diff --git a/src/ast/expr.rs b/src/ast/expr.rs index e29745a7..037079e8 100644 --- a/src/ast/expr.rs +++ b/src/ast/expr.rs @@ -117,6 +117,8 @@ pub struct FnCallHashes { } impl fmt::Debug for FnCallHashes { + #[cold] + #[inline(never)] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { #[cfg(not(feature = "no_function"))] if self.script != 0 { @@ -199,6 +201,8 @@ pub struct FnCallExpr { } impl fmt::Debug for FnCallExpr { + #[cold] + #[inline(never)] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let mut ff = f.debug_struct("FnCallExpr"); #[cfg(not(feature = "no_module"))] @@ -294,7 +298,8 @@ impl DerefMut for FloatWrapper { #[cfg(not(feature = "no_float"))] impl fmt::Debug for FloatWrapper { - #[inline(always)] + #[cold] + #[inline(never)] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Debug::fmt(&self.0, f) } @@ -448,6 +453,8 @@ impl Default for Expr { } impl fmt::Debug for Expr { + #[cold] + #[inline(never)] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let mut display_pos = format!(" @ {:?}", self.start_position()); diff --git a/src/ast/ident.rs b/src/ast/ident.rs index 8a6d98aa..eb84c432 100644 --- a/src/ast/ident.rs +++ b/src/ast/ident.rs @@ -20,6 +20,8 @@ pub struct Ident { } impl fmt::Debug for Ident { + #[cold] + #[inline(never)] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{:?}", self.name)?; self.pos.debug_print(f) diff --git a/src/ast/namespace.rs b/src/ast/namespace.rs index 9dba5b18..ee36d3bd 100644 --- a/src/ast/namespace.rs +++ b/src/ast/namespace.rs @@ -29,6 +29,8 @@ pub struct Namespace { } impl fmt::Debug for Namespace { + #[cold] + #[inline(never)] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { if self.is_empty() { return f.write_str("NONE"); diff --git a/src/ast/stmt.rs b/src/ast/stmt.rs index 3bbb0177..ba6f0821 100644 --- a/src/ast/stmt.rs +++ b/src/ast/stmt.rs @@ -106,6 +106,8 @@ impl OpAssignment { } impl fmt::Debug for OpAssignment { + #[cold] + #[inline(never)] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { if self.is_op_assignment() { f.debug_struct("OpAssignment") @@ -178,7 +180,8 @@ pub enum RangeCase { } impl fmt::Debug for RangeCase { - #[inline] + #[cold] + #[inline(never)] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::ExclusiveInt(r, n) => write!(f, "{}..{} => {}", r.start, r.end, n), @@ -454,6 +457,8 @@ impl AsMut<[Stmt]> for StmtBlock { } impl fmt::Debug for StmtBlock { + #[cold] + #[inline(never)] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str("Block")?; fmt::Debug::fmt(&self.block, f)?; diff --git a/src/engine.rs b/src/engine.rs index 72f95c6b..fb905cde 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -150,7 +150,8 @@ pub struct Engine { } impl fmt::Debug for Engine { - #[inline] + #[cold] + #[inline(never)] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let mut f = f.debug_struct("Engine"); diff --git a/src/eval/global_state.rs b/src/eval/global_state.rs index 775fdda0..20ee2614 100644 --- a/src/eval/global_state.rs +++ b/src/eval/global_state.rs @@ -354,7 +354,8 @@ impl, M: Into>> Ext } impl fmt::Debug for GlobalRuntimeState<'_> { - #[inline] + #[cold] + #[inline(never)] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let mut f = f.debug_struct("GlobalRuntimeState"); diff --git a/src/func/callable_function.rs b/src/func/callable_function.rs index babf1ebf..3973b1b1 100644 --- a/src/func/callable_function.rs +++ b/src/func/callable_function.rs @@ -27,6 +27,8 @@ pub enum CallableFunction { } impl fmt::Debug for CallableFunction { + #[cold] + #[inline(never)] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Pure(..) => write!(f, "NativePureFunction"), diff --git a/src/module/mod.rs b/src/module/mod.rs index a07a82d3..fb94e590 100644 --- a/src/module/mod.rs +++ b/src/module/mod.rs @@ -202,6 +202,8 @@ impl Default for Module { } impl fmt::Debug for Module { + #[cold] + #[inline(never)] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let mut d = f.debug_struct("Module"); diff --git a/src/packages/iter_basic.rs b/src/packages/iter_basic.rs index b9c849fe..4473cfbb 100644 --- a/src/packages/iter_basic.rs +++ b/src/packages/iter_basic.rs @@ -47,6 +47,8 @@ pub struct StepRange { } impl Debug for StepRange { + #[cold] + #[inline(never)] fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_tuple(&format!("StepRange<{}>", type_name::())) .field(&self.from) diff --git a/src/tokenizer.rs b/src/tokenizer.rs index 7cea143e..81893066 100644 --- a/src/tokenizer.rs +++ b/src/tokenizer.rs @@ -241,6 +241,8 @@ impl fmt::Display for Position { } impl fmt::Debug for Position { + #[cold] + #[inline(never)] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { if self.is_none() { f.write_str("none") @@ -333,7 +335,6 @@ impl Span { } impl fmt::Display for Span { - #[inline] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let _f = f; @@ -360,6 +361,8 @@ impl fmt::Display for Span { } impl fmt::Debug for Span { + #[cold] + #[inline(never)] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Display::fmt(self, f) } diff --git a/src/types/custom_types.rs b/src/types/custom_types.rs index 3b759edc..ca43e1e8 100644 --- a/src/types/custom_types.rs +++ b/src/types/custom_types.rs @@ -17,6 +17,8 @@ pub struct CustomTypeInfo { pub struct CustomTypesCollection(BTreeMap); impl fmt::Debug for CustomTypesCollection { + #[cold] + #[inline(never)] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str("CustomTypesCollection ")?; f.debug_map().entries(self.0.iter()).finish() diff --git a/src/types/dynamic.rs b/src/types/dynamic.rs index db88b9fa..79e63abf 100644 --- a/src/types/dynamic.rs +++ b/src/types/dynamic.rs @@ -507,6 +507,8 @@ impl fmt::Display for Dynamic { } impl fmt::Debug for Dynamic { + #[cold] + #[inline(never)] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self.0 { Union::Unit(ref v, ..) => fmt::Debug::fmt(v, f), diff --git a/src/types/fn_ptr.rs b/src/types/fn_ptr.rs index 3ac75077..ab39e9b9 100644 --- a/src/types/fn_ptr.rs +++ b/src/types/fn_ptr.rs @@ -23,6 +23,8 @@ pub struct FnPtr { } impl fmt::Debug for FnPtr { + #[cold] + #[inline(never)] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { if self.is_curried() { self.curry diff --git a/src/types/immutable_string.rs b/src/types/immutable_string.rs index 507c33d6..7353f151 100644 --- a/src/types/immutable_string.rs +++ b/src/types/immutable_string.rs @@ -197,7 +197,8 @@ impl fmt::Display for ImmutableString { } impl fmt::Debug for ImmutableString { - #[inline(always)] + #[cold] + #[inline(never)] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Debug::fmt(self.as_str(), f) } diff --git a/src/types/scope.rs b/src/types/scope.rs index 4cc5a91c..169830b4 100644 --- a/src/types/scope.rs +++ b/src/types/scope.rs @@ -81,7 +81,6 @@ pub struct Scope<'a, const N: usize = SCOPE_ENTRIES_INLINED> { } impl fmt::Display for Scope<'_> { - #[inline] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { for (i, (name, constant, value)) in self.iter_raw().enumerate() { #[cfg(not(feature = "no_closure"))] From a518ab62bb22f8055a2b8aa89d10b5779839984d Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Tue, 27 Sep 2022 08:52:51 +0800 Subject: [PATCH 2/3] Simplify strings interner. --- src/parser.rs | 25 ++++++++------ src/types/interner.rs | 76 ++++++++++++++++++++++++------------------- 2 files changed, 57 insertions(+), 44 deletions(-) diff --git a/src/parser.rs b/src/parser.rs index 9941fc53..6cdf3d18 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -47,9 +47,6 @@ const NEVER_ENDS: &str = "`Token`"; /// Unroll `switch` ranges no larger than this. const SMALL_SWITCH_RANGE: INT = 16; -/// Number of string interners used: two additional for property getters/setters if not `no_object` -const NUM_INTERNERS: usize = if cfg!(feature = "no_object") { 1 } else { 3 }; - /// _(internals)_ A type that encapsulates the current state of the parser. /// Exported under the `internals` feature only. pub struct ParseState<'e> { @@ -58,7 +55,7 @@ pub struct ParseState<'e> { /// Controls whether parsing of an expression should stop given the next token. pub expr_filter: fn(&Token) -> bool, /// String interners. - interned_strings: [StringsInterner<'e>; NUM_INTERNERS], + interned_strings: StringsInterner<'e>, /// External [scope][Scope] with constants. pub scope: &'e Scope<'e>, /// Global runtime state. @@ -88,6 +85,8 @@ pub struct ParseState<'e> { } impl fmt::Debug for ParseState<'_> { + #[cold] + #[inline(never)] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let mut f = f.debug_struct("ParseState"); @@ -116,7 +115,7 @@ impl<'e> ParseState<'e> { pub fn new( engine: &Engine, scope: &'e Scope, - interned_strings: [StringsInterner<'e>; NUM_INTERNERS], + interned_strings: StringsInterner<'e>, tokenizer_control: TokenizerControl, ) -> Self { Self { @@ -254,7 +253,7 @@ impl<'e> ParseState<'e> { &mut self, text: impl AsRef + Into, ) -> ImmutableString { - self.interned_strings[0].get(text) + self.interned_strings.get(text) } /// Get an interned property getter, creating one if it is not yet interned. @@ -265,8 +264,11 @@ impl<'e> ParseState<'e> { &mut self, text: impl AsRef + Into, ) -> ImmutableString { - self.interned_strings[1] - .get_with_mapper(|s| crate::engine::make_getter(s.as_ref()).into(), text) + self.interned_strings.get_with_mapper( + crate::engine::FN_GET, + |s| crate::engine::make_getter(s.as_ref()).into(), + text, + ) } /// Get an interned property setter, creating one if it is not yet interned. @@ -277,8 +279,11 @@ impl<'e> ParseState<'e> { &mut self, text: impl AsRef + Into, ) -> ImmutableString { - self.interned_strings[2] - .get_with_mapper(|s| crate::engine::make_setter(s.as_ref()).into(), text) + self.interned_strings.get_with_mapper( + crate::engine::FN_SET, + |s| crate::engine::make_setter(s.as_ref()).into(), + text, + ) } } diff --git a/src/types/interner.rs b/src/types/interner.rs index 3dfa1048..7ae35a35 100644 --- a/src/types/interner.rs +++ b/src/types/interner.rs @@ -1,3 +1,4 @@ +use super::BloomFilterU64; use crate::func::{hashing::get_hasher, StraightHashMap}; use crate::ImmutableString; #[cfg(feature = "no_std")] @@ -14,7 +15,7 @@ use std::{ }; /// Maximum number of strings interned. -pub const MAX_INTERNED_STRINGS: usize = 256; +pub const MAX_INTERNED_STRINGS: usize = 1024; /// Maximum length of strings interned. pub const MAX_STRING_LEN: usize = 24; @@ -28,8 +29,10 @@ pub struct StringsInterner<'a> { pub capacity: usize, /// Maximum string length. pub max_string_len: usize, - /// Normal strings. - strings: StraightHashMap, + /// Cached strings. + cache: StraightHashMap, + /// Bloom filter to avoid caching "one-hit wonders". + filter: BloomFilterU64, /// Take care of the lifetime parameter. dummy: PhantomData<&'a ()>, } @@ -42,9 +45,10 @@ impl Default for StringsInterner<'_> { } impl fmt::Debug for StringsInterner<'_> { - #[inline] + #[cold] + #[inline(never)] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_list().entries(self.strings.values()).finish() + f.debug_list().entries(self.cache.values()).finish() } } @@ -56,7 +60,8 @@ impl StringsInterner<'_> { Self { capacity: MAX_INTERNED_STRINGS, max_string_len: MAX_STRING_LEN, - strings: StraightHashMap::default(), + cache: StraightHashMap::default(), + filter: BloomFilterU64::new(), dummy: PhantomData, } } @@ -65,7 +70,7 @@ impl StringsInterner<'_> { #[inline(always)] #[must_use] pub fn get + Into>(&mut self, text: S) -> ImmutableString { - self.get_with_mapper(Into::into, text) + self.get_with_mapper("", Into::into, text) } /// Get an identifier from a text string, adding it to the interner if necessary. @@ -73,20 +78,23 @@ impl StringsInterner<'_> { #[must_use] pub fn get_with_mapper>( &mut self, + id: &str, mapper: impl Fn(S) -> ImmutableString, text: S, ) -> ImmutableString { let key = text.as_ref(); - if key.len() > MAX_STRING_LEN { + let hasher = &mut get_hasher(); + id.hash(hasher); + key.hash(hasher); + let hash = hasher.finish(); + + // Cache long strings only on the second try to avoid caching "one-hit wonders". + if key.len() > MAX_STRING_LEN && self.filter.is_absent_and_set(hash) { return mapper(text); } - let hasher = &mut get_hasher(); - key.hash(hasher); - let key = hasher.finish(); - - let result = match self.strings.entry(key) { + let result = match self.cache.entry(hash) { Entry::Occupied(e) => return e.get().clone(), Entry::Vacant(e) => { let value = mapper(text); @@ -100,7 +108,7 @@ impl StringsInterner<'_> { }; // If the interner is over capacity, remove the longest entry that has the lowest count - if self.strings.len() > self.capacity { + if self.cache.len() > self.capacity { // Leave some buffer to grow when shrinking the cache. // We leave at least two entries, one for the empty string, and one for the string // that has just been inserted. @@ -110,21 +118,21 @@ impl StringsInterner<'_> { self.capacity - 3 }; - while self.strings.len() > max { - let (_, _, n) = - self.strings - .iter() - .fold((0, usize::MAX, 0), |(x, c, n), (&k, v)| { - if k != key - && (v.strong_count() < c || (v.strong_count() == c && v.len() > x)) - { - (v.len(), v.strong_count(), k) - } else { - (x, c, n) - } - }); + while self.cache.len() > max { + let (_, _, n) = self + .cache + .iter() + .fold((0, usize::MAX, 0), |(x, c, n), (&k, v)| { + if k != hash + && (v.strong_count() < c || (v.strong_count() == c && v.len() > x)) + { + (v.len(), v.strong_count(), k) + } else { + (x, c, n) + } + }); - self.strings.remove(&n); + self.cache.remove(&n); } } @@ -136,7 +144,7 @@ impl StringsInterner<'_> { #[must_use] #[allow(dead_code)] pub fn len(&self) -> usize { - self.strings.len() + self.cache.len() } /// Returns `true` if there are no interned strings. @@ -144,28 +152,28 @@ impl StringsInterner<'_> { #[must_use] #[allow(dead_code)] pub fn is_empty(&self) -> bool { - self.strings.is_empty() + self.cache.is_empty() } /// Clear all interned strings. #[inline(always)] #[allow(dead_code)] pub fn clear(&mut self) { - self.strings.clear(); + self.cache.clear(); } } impl AddAssign for StringsInterner<'_> { #[inline(always)] fn add_assign(&mut self, rhs: Self) { - self.strings.extend(rhs.strings.into_iter()); + self.cache.extend(rhs.cache.into_iter()); } } impl AddAssign<&Self> for StringsInterner<'_> { #[inline(always)] fn add_assign(&mut self, rhs: &Self) { - self.strings - .extend(rhs.strings.iter().map(|(&k, v)| (k, v.clone()))); + self.cache + .extend(rhs.cache.iter().map(|(&k, v)| (k, v.clone()))); } } From 2ecf44a48e791e1576586075477c88ad635d2af1 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Tue, 27 Sep 2022 13:23:47 +0800 Subject: [PATCH 3/3] Remove stable_hash feature and use environment variable. --- CHANGELOG.md | 4 +++- Cargo.toml | 1 - build.rs | 27 +++++++++++++++++++++++++++ src/config.rs | 4 ++++ src/func/hashing.rs | 5 +++-- src/lib.rs | 7 ++++--- 6 files changed, 41 insertions(+), 7 deletions(-) create mode 100644 build.rs create mode 100644 src/config.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 9340630c..70fc8550 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,9 @@ Version 1.11.0 New features ------------ -* A new feature flag, `stable_hash`, is added that forces hashing to be consistent using a fixed seed. +### Stable hashing + +* It is now possible to specify a fixed _seed_ for use with the `ahash` hasher in order to force stable (i.e. deterministic) hashes for function signatures. Enhancements ------------ diff --git a/Cargo.toml b/Cargo.toml index 6451af51..1034f923 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -62,7 +62,6 @@ metadata = ["serde", "serde_json", "rhai_codegen/metadata", "smartstring/serde"] internals = [] # expose internal data structures debugging = ["internals"] # enable debugging serde = ["dep:serde", "smartstring/serde", "smallvec/serde"] # implement serde for rhai types -stable_hash = [] # perform all hashing with fixed seed value # compiling for no-std no_std = ["no-std-compat", "num-traits/libm", "core-error", "libm", "ahash/compile-time-rng", "hashbrown/ahash-compile-time-rng"] diff --git a/build.rs b/build.rs new file mode 100644 index 00000000..d17bb3da --- /dev/null +++ b/build.rs @@ -0,0 +1,27 @@ +use std::{env, fs::File, io::Write}; + +const WRITE_ERROR: &str = "cannot write to `config.rs`"; + +fn main() { + // Tell Cargo that if the given environment variable changes, to rerun this build script. + println!("cargo:rerun-if-env-changed=RHAI_AHASH_SEED"); + + let mut f = File::create("src/config.rs").expect("cannot create `config.rs`"); + + f.write_fmt(format_args!( + "//! Configuration settings for this Rhai build +#![allow(dead_code)] + +" + )) + .expect(WRITE_ERROR); + + let seed = env::var("RHAI_AHASH_SEED").map_or_else(|_| "None".into(), |s| format!("Some({s})")); + + f.write_fmt(format_args!( + "pub const AHASH_SEED: Option<[u64; 4]> = {seed};\n" + )) + .expect(WRITE_ERROR); + + f.flush().expect("cannot flush `config.rs`"); +} diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 00000000..43d49b6a --- /dev/null +++ b/src/config.rs @@ -0,0 +1,4 @@ +//! Configuration settings for this Rhai build +#![allow(dead_code)] + +pub const AHASH_SEED: Option<[u64; 4]> = None; diff --git a/src/func/hashing.rs b/src/func/hashing.rs index ae0a57bc..53eac8c3 100644 --- a/src/func/hashing.rs +++ b/src/func/hashing.rs @@ -1,5 +1,6 @@ //! Module containing utilities to hash functions and function calls. +use crate::config; #[cfg(feature = "no_std")] use std::prelude::v1::*; use std::{ @@ -74,8 +75,8 @@ impl BuildHasher for StraightHasherBuilder { #[inline(always)] #[must_use] pub fn get_hasher() -> ahash::AHasher { - if cfg!(feature = "stable_hash") { - ahash::RandomState::with_seeds(42, 999, 123, 0).build_hasher() + if let Some([seed1, seed2, seed3, seed4]) = config::AHASH_SEED { + ahash::RandomState::with_seeds(seed1, seed2, seed3, seed4).build_hasher() } else { ahash::AHasher::default() } diff --git a/src/lib.rs b/src/lib.rs index bb26d72b..4976d4b7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -83,6 +83,7 @@ use std::prelude::v1::*; // Internal modules mod api; mod ast; +mod config; mod engine; mod eval; mod func; @@ -224,7 +225,7 @@ pub mod debugger { /// An identifier in Rhai. [`SmartString`](https://crates.io/crates/smartstring) is used because most /// identifiers are ASCII and short, fewer than 23 characters, so they can be stored inline. #[cfg(not(feature = "internals"))] -pub(crate) type Identifier = SmartString; +type Identifier = SmartString; /// An identifier in Rhai. [`SmartString`](https://crates.io/crates/smartstring) is used because most /// identifiers are ASCII and short, fewer than 23 characters, so they can be stored inline. @@ -237,7 +238,7 @@ pub use func::Shared; /// Alias to [`RefCell`][std::cell::RefCell] or [`RwLock`][std::sync::RwLock] depending on the `sync` feature flag. pub use func::Locked; -pub(crate) use func::{calc_fn_hash, calc_fn_params_hash, calc_var_hash, combine_hashes}; +use func::{calc_fn_hash, calc_fn_params_hash, calc_var_hash, combine_hashes}; pub use rhai_codegen::*; @@ -429,7 +430,7 @@ type FnArgsVec = smallvec::SmallVec<[T; 5]>; #[cfg(feature = "no_closure")] type FnArgsVec = crate::StaticVec; -pub(crate) type SmartString = smartstring::SmartString; +type SmartString = smartstring::SmartString; // Compiler guards against mutually-exclusive feature flags