1
0

nodes.rs 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614
  1. use crate::{
  2. any_props::AnyProps, arena::ElementId, Element, Event, LazyNodes, ScopeId, ScopeState,
  3. };
  4. use bumpalo::boxed::Box as BumpBox;
  5. use bumpalo::Bump;
  6. use std::{
  7. any::{Any, TypeId},
  8. cell::{Cell, RefCell},
  9. fmt::Arguments,
  10. future::Future,
  11. };
  12. pub type TemplateId = &'static str;
  13. /// The actual state of the component's most recent computation
  14. ///
  15. /// Because Dioxus accepts components in the form of `async fn(Scope) -> Result<VNode>`, we need to support both
  16. /// sync and async versions.
  17. ///
  18. /// Dioxus will do its best to immediately resolve any async components into a regular Element, but as an implementor
  19. /// you might need to handle the case where there's no node immediately ready.
  20. pub enum RenderReturn<'a> {
  21. /// A currently-available element
  22. Ready(VNode<'a>),
  23. /// The component aborted rendering early. It might've thrown an error.
  24. ///
  25. /// In its place we've produced a placeholder to locate its spot in the dom when
  26. /// it recovers.
  27. Aborted(VPlaceholder),
  28. /// An ongoing future that will resolve to a [`Element`]
  29. Async(BumpBox<'a, dyn Future<Output = Element<'a>> + 'a>),
  30. }
  31. impl<'a> Default for RenderReturn<'a> {
  32. fn default() -> Self {
  33. RenderReturn::Aborted(VPlaceholder::default())
  34. }
  35. }
  36. /// A reference to a template along with any context needed to hydrate it
  37. ///
  38. /// The dynamic parts of the template are stored separately from the static parts. This allows faster diffing by skipping
  39. /// static parts of the template.
  40. #[derive(Debug, Clone)]
  41. pub struct VNode<'a> {
  42. /// The key given to the root of this template.
  43. ///
  44. /// In fragments, this is the key of the first child. In other cases, it is the key of the root.
  45. pub key: Option<&'a str>,
  46. /// When rendered, this template will be linked to its parent manually
  47. pub parent: Option<ElementId>,
  48. /// The static nodes and static descriptor of the template
  49. pub template: Template<'static>,
  50. /// The IDs for the roots of this template - to be used when moving the template around and removing it from
  51. /// the actual Dom
  52. pub root_ids: &'a [Cell<Option<ElementId>>],
  53. /// The dynamic parts of the template
  54. pub dynamic_nodes: &'a [DynamicNode<'a>],
  55. /// The dynamic parts of the template
  56. pub dynamic_attrs: &'a [Attribute<'a>],
  57. }
  58. impl<'a> VNode<'a> {
  59. /// Create a template with no nodes that will be skipped over during diffing
  60. pub fn empty() -> Element<'a> {
  61. Some(VNode {
  62. key: None,
  63. parent: None,
  64. root_ids: &[],
  65. dynamic_nodes: &[],
  66. dynamic_attrs: &[],
  67. template: Template {
  68. name: "dioxus-empty",
  69. roots: &[],
  70. node_paths: &[],
  71. attr_paths: &[],
  72. },
  73. })
  74. }
  75. /// Load a dynamic root at the given index
  76. ///
  77. /// Returns [`None`] if the root is actually a static node (Element/Text)
  78. pub fn dynamic_root(&self, idx: usize) -> Option<&'a DynamicNode<'a>> {
  79. match &self.template.roots[idx] {
  80. TemplateNode::Element { .. } | TemplateNode::Text { text: _ } => None,
  81. TemplateNode::Dynamic { id } | TemplateNode::DynamicText { id } => {
  82. Some(&self.dynamic_nodes[*id])
  83. }
  84. }
  85. }
  86. pub(crate) fn clear_listeners(&self) {
  87. for attr in self.dynamic_attrs {
  88. if let AttributeValue::Listener(l) = &attr.value {
  89. l.borrow_mut().take();
  90. }
  91. }
  92. }
  93. }
  94. /// A static layout of a UI tree that describes a set of dynamic and static nodes.
  95. ///
  96. /// This is the core innovation in Dioxus. Most UIs are made of static nodes, yet participate in diffing like any
  97. /// dynamic node. This struct can be created at compile time. It promises that its name is unique, allow Dioxus to use
  98. /// its static description of the UI to skip immediately to the dynamic nodes during diffing.
  99. ///
  100. /// For this to work properly, the [`Template::name`] *must* be unique across your entire project. This can be done via variety of
  101. /// ways, with the suggested approach being the unique code location (file, line, col, etc).
  102. #[cfg_attr(feature = "serialize", derive(serde::Serialize))]
  103. #[derive(Debug, Clone, Copy, PartialEq, Hash, Eq, PartialOrd, Ord)]
  104. pub struct Template<'a> {
  105. /// The name of the template. This must be unique across your entire program for template diffing to work properly
  106. ///
  107. /// If two templates have the same name, it's likely that Dioxus will panic when diffing.
  108. pub name: &'a str,
  109. /// The list of template nodes that make up the template
  110. ///
  111. /// Unlike react, calls to `rsx!` can have multiple roots. This list supports that paradigm.
  112. pub roots: &'a [TemplateNode<'a>],
  113. /// The paths of each node relative to the root of the template.
  114. ///
  115. /// These will be one segment shorter than the path sent to the renderer since those paths are relative to the
  116. /// topmost element, not the `roots` field.
  117. pub node_paths: &'a [&'a [u8]],
  118. /// The paths of each dynamic attribute relative to the root of the template
  119. ///
  120. /// These will be one segment shorter than the path sent to the renderer since those paths are relative to the
  121. /// topmost element, not the `roots` field.
  122. pub attr_paths: &'a [&'a [u8]],
  123. }
  124. impl<'a> Template<'a> {
  125. /// Is this template worth caching at all, since it's completely runtime?
  126. ///
  127. /// There's no point in saving templates that are completely dynamic, since they'll be recreated every time anyway.
  128. pub fn is_completely_dynamic(&self) -> bool {
  129. use TemplateNode::*;
  130. self.roots
  131. .iter()
  132. .all(|root| matches!(root, Dynamic { .. } | DynamicText { .. }))
  133. }
  134. }
  135. /// A statically known node in a layout.
  136. ///
  137. /// This can be created at compile time, saving the VirtualDom time when diffing the tree
  138. #[derive(Debug, Clone, Copy, PartialEq, Hash, Eq, PartialOrd, Ord)]
  139. #[cfg_attr(feature = "serialize", derive(serde::Serialize), serde(tag = "type"))]
  140. pub enum TemplateNode<'a> {
  141. /// An statically known element in the dom.
  142. ///
  143. /// In HTML this would be something like `<div id="123"> </div>`
  144. Element {
  145. /// The name of the element
  146. ///
  147. /// IE for a div, it would be the string "div"
  148. tag: &'a str,
  149. /// The namespace of the element
  150. ///
  151. /// In HTML, this would be a valid URI that defines a namespace for all elements below it
  152. /// SVG is an example of this namespace
  153. namespace: Option<&'a str>,
  154. /// A list of possibly dynamic attribues for this element
  155. ///
  156. /// An attribute on a DOM node, such as `id="my-thing"` or `href="https://example.com"`.
  157. attrs: &'a [TemplateAttribute<'a>],
  158. /// A list of template nodes that define another set of template nodes
  159. children: &'a [TemplateNode<'a>],
  160. },
  161. /// This template node is just a piece of static text
  162. Text {
  163. /// The actual text
  164. text: &'a str,
  165. },
  166. /// This template node is unknown, and needs to be created at runtime.
  167. Dynamic {
  168. /// The index of the dynamic node in the VNode's dynamic_nodes list
  169. id: usize,
  170. },
  171. /// This template node is known to be some text, but needs to be created at runtime
  172. ///
  173. /// This is separate from the pure Dynamic variant for various optimizations
  174. DynamicText {
  175. /// The index of the dynamic node in the VNode's dynamic_nodes list
  176. id: usize,
  177. },
  178. }
  179. /// A node created at runtime
  180. ///
  181. /// This node's index in the DynamicNode list on VNode should match its repsective `Dynamic` index
  182. #[derive(Debug)]
  183. pub enum DynamicNode<'a> {
  184. /// A component node
  185. ///
  186. /// Most of the time, Dioxus will actually know which component this is as compile time, but the props and
  187. /// assigned scope are dynamic.
  188. ///
  189. /// The actual VComponent can be dynamic between two VNodes, though, allowing implementations to swap
  190. /// the render function at runtime
  191. Component(VComponent<'a>),
  192. /// A text node
  193. Text(VText<'a>),
  194. /// A placeholder
  195. ///
  196. /// Used by suspense when a node isn't ready and by fragments that don't render anything
  197. ///
  198. /// In code, this is just an ElementId whose initial value is set to 0 upon creation
  199. Placeholder(VPlaceholder),
  200. /// A list of VNodes.
  201. ///
  202. /// Note that this is not a list of dynamic nodes. These must be VNodes and created through conditional rendering
  203. /// or iterators.
  204. Fragment(&'a [VNode<'a>]),
  205. }
  206. impl Default for DynamicNode<'_> {
  207. fn default() -> Self {
  208. Self::Placeholder(Default::default())
  209. }
  210. }
  211. /// An instance of a child component
  212. pub struct VComponent<'a> {
  213. /// The name of this component
  214. pub name: &'static str,
  215. /// Are the props valid for the 'static lifetime?
  216. ///
  217. /// Internally, this is used as a guarantee. Externally, this might be incorrect, so don't count on it.
  218. ///
  219. /// This flag is assumed by the [`crate::Properties`] trait which is unsafe to implement
  220. pub static_props: bool,
  221. /// The assigned Scope for this component
  222. pub scope: Cell<Option<ScopeId>>,
  223. /// The function pointer of the component, known at compile time
  224. ///
  225. /// It is possible that components get folded at comppile time, so these shouldn't be really used as a key
  226. pub render_fn: *const (),
  227. pub(crate) props: RefCell<Option<Box<dyn AnyProps<'a> + 'a>>>,
  228. }
  229. impl<'a> std::fmt::Debug for VComponent<'a> {
  230. fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
  231. f.debug_struct("VComponent")
  232. .field("name", &self.name)
  233. .field("static_props", &self.static_props)
  234. .field("scope", &self.scope)
  235. .finish()
  236. }
  237. }
  238. /// An instance of some text, mounted to the DOM
  239. #[derive(Debug)]
  240. pub struct VText<'a> {
  241. /// The actual text itself
  242. pub value: &'a str,
  243. /// The ID of this node in the real DOM
  244. pub id: Cell<Option<ElementId>>,
  245. }
  246. /// A placeholder node, used by suspense and fragments
  247. #[derive(Debug, Default)]
  248. pub struct VPlaceholder {
  249. /// The ID of this node in the real DOM
  250. pub id: Cell<Option<ElementId>>,
  251. }
  252. /// An attribute of the TemplateNode, created at compile time
  253. #[derive(Debug, PartialEq, Hash, Eq, PartialOrd, Ord)]
  254. #[cfg_attr(
  255. feature = "serialize",
  256. derive(serde::Serialize, serde::Deserialize),
  257. serde(tag = "type")
  258. )]
  259. pub enum TemplateAttribute<'a> {
  260. /// This attribute is entirely known at compile time, enabling
  261. Static {
  262. /// The name of this attribute.
  263. ///
  264. /// For example, the `href` attribute in `href="https://example.com"`, would have the name "href"
  265. name: &'a str,
  266. /// The value of this attribute, known at compile time
  267. ///
  268. /// Currently this only accepts &str, so values, even if they're known at compile time, are not known
  269. value: &'a str,
  270. /// The namespace of this attribute. Does not exist in the HTML spec
  271. namespace: Option<&'a str>,
  272. },
  273. /// The attribute in this position is actually determined dynamically at runtime
  274. ///
  275. /// This is the index into the dynamic_attributes field on the container VNode
  276. Dynamic {
  277. /// The index
  278. id: usize,
  279. },
  280. }
  281. /// An attribute on a DOM node, such as `id="my-thing"` or `href="https://example.com"`
  282. #[derive(Debug)]
  283. pub struct Attribute<'a> {
  284. /// The name of the attribute.
  285. pub name: &'a str,
  286. /// The value of the attribute
  287. pub value: AttributeValue<'a>,
  288. /// The namespace of the attribute.
  289. ///
  290. /// Doesn’t exist in the html spec. Used in Dioxus to denote “style” tags and other attribute groups.
  291. pub namespace: Option<&'static str>,
  292. /// The element in the DOM that this attribute belongs to
  293. pub mounted_element: Cell<ElementId>,
  294. /// An indication of we should always try and set the attribute. Used in controlled components to ensure changes are propagated
  295. pub volatile: bool,
  296. }
  297. /// Any of the built-in values that the Dioxus VirtualDom supports as dynamic attributes on elements
  298. ///
  299. /// These are built-in to be faster during the diffing process. To use a custom value, use the [`AttributeValue::Any`]
  300. /// variant.
  301. pub enum AttributeValue<'a> {
  302. /// Text attribute
  303. Text(&'a str),
  304. /// A float
  305. Float(f64),
  306. /// Signed integer
  307. Int(i64),
  308. /// Boolean
  309. Bool(bool),
  310. /// A listener, like "onclick"
  311. Listener(RefCell<Option<ListenerCb<'a>>>),
  312. /// An arbitrary value that implements PartialEq and is static
  313. Any(BumpBox<'a, dyn AnyValue>),
  314. /// A "none" value, resulting in the removal of an attribute from the dom
  315. None,
  316. }
  317. type ListenerCb<'a> = BumpBox<'a, dyn FnMut(Event<dyn Any>) + 'a>;
  318. impl<'a> std::fmt::Debug for AttributeValue<'a> {
  319. fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
  320. match self {
  321. Self::Text(arg0) => f.debug_tuple("Text").field(arg0).finish(),
  322. Self::Float(arg0) => f.debug_tuple("Float").field(arg0).finish(),
  323. Self::Int(arg0) => f.debug_tuple("Int").field(arg0).finish(),
  324. Self::Bool(arg0) => f.debug_tuple("Bool").field(arg0).finish(),
  325. Self::Listener(_) => f.debug_tuple("Listener").finish(),
  326. Self::Any(_) => f.debug_tuple("Any").finish(),
  327. Self::None => write!(f, "None"),
  328. }
  329. }
  330. }
  331. impl<'a> PartialEq for AttributeValue<'a> {
  332. fn eq(&self, other: &Self) -> bool {
  333. match (self, other) {
  334. (Self::Text(l0), Self::Text(r0)) => l0 == r0,
  335. (Self::Float(l0), Self::Float(r0)) => l0 == r0,
  336. (Self::Int(l0), Self::Int(r0)) => l0 == r0,
  337. (Self::Bool(l0), Self::Bool(r0)) => l0 == r0,
  338. (Self::Listener(_), Self::Listener(_)) => true,
  339. (Self::Any(l0), Self::Any(r0)) => l0.any_cmp(r0.as_ref()),
  340. _ => core::mem::discriminant(self) == core::mem::discriminant(other),
  341. }
  342. }
  343. }
  344. #[doc(hidden)]
  345. pub trait AnyValue {
  346. fn any_cmp(&self, other: &dyn AnyValue) -> bool;
  347. fn our_typeid(&self) -> TypeId;
  348. }
  349. impl<T: PartialEq + Any> AnyValue for T {
  350. fn any_cmp(&self, other: &dyn AnyValue) -> bool {
  351. if self.type_id() != other.our_typeid() {
  352. return false;
  353. }
  354. self == unsafe { &*(other as *const _ as *const T) }
  355. }
  356. fn our_typeid(&self) -> TypeId {
  357. self.type_id()
  358. }
  359. }
  360. #[doc(hidden)]
  361. pub trait ComponentReturn<'a, A = ()> {
  362. fn into_return(self, cx: &'a ScopeState) -> RenderReturn<'a>;
  363. }
  364. impl<'a> ComponentReturn<'a> for Element<'a> {
  365. fn into_return(self, _cx: &ScopeState) -> RenderReturn<'a> {
  366. match self {
  367. Some(node) => RenderReturn::Ready(node),
  368. None => RenderReturn::Aborted(VPlaceholder::default()),
  369. }
  370. }
  371. }
  372. #[doc(hidden)]
  373. pub struct AsyncMarker;
  374. impl<'a, F> ComponentReturn<'a, AsyncMarker> for F
  375. where
  376. F: Future<Output = Element<'a>> + 'a,
  377. {
  378. fn into_return(self, cx: &'a ScopeState) -> RenderReturn<'a> {
  379. let f: &mut dyn Future<Output = Element<'a>> = cx.bump().alloc(self);
  380. RenderReturn::Async(unsafe { BumpBox::from_raw(f) })
  381. }
  382. }
  383. impl<'a> RenderReturn<'a> {
  384. pub(crate) unsafe fn extend_lifetime_ref<'c>(&self) -> &'c RenderReturn<'c> {
  385. unsafe { std::mem::transmute(self) }
  386. }
  387. pub(crate) unsafe fn extend_lifetime<'c>(self) -> RenderReturn<'c> {
  388. unsafe { std::mem::transmute(self) }
  389. }
  390. }
  391. /// A trait that allows various items to be converted into a dynamic node for the rsx macro
  392. pub trait IntoDynNode<'a, A = ()> {
  393. /// Consume this item along with a scopestate and produce a DynamicNode
  394. ///
  395. /// You can use the bump alloactor of the scopestate to creat the dynamic node
  396. fn into_vnode(self, cx: &'a ScopeState) -> DynamicNode<'a>;
  397. }
  398. impl<'a> IntoDynNode<'a> for () {
  399. fn into_vnode(self, _cx: &'a ScopeState) -> DynamicNode<'a> {
  400. DynamicNode::default()
  401. }
  402. }
  403. impl<'a> IntoDynNode<'a> for VNode<'a> {
  404. fn into_vnode(self, _cx: &'a ScopeState) -> DynamicNode<'a> {
  405. DynamicNode::Fragment(_cx.bump().alloc([self]))
  406. }
  407. }
  408. impl<'a> IntoDynNode<'a> for DynamicNode<'a> {
  409. fn into_vnode(self, _cx: &'a ScopeState) -> DynamicNode<'a> {
  410. self
  411. }
  412. }
  413. impl<'a, T: IntoDynNode<'a>> IntoDynNode<'a> for Option<T> {
  414. fn into_vnode(self, _cx: &'a ScopeState) -> DynamicNode<'a> {
  415. match self {
  416. Some(val) => val.into_vnode(_cx),
  417. None => DynamicNode::default(),
  418. }
  419. }
  420. }
  421. impl<'a> IntoDynNode<'a> for &Element<'a> {
  422. fn into_vnode(self, _cx: &'a ScopeState) -> DynamicNode<'a> {
  423. match self.as_ref() {
  424. Some(val) => val.clone().into_vnode(_cx),
  425. _ => DynamicNode::default(),
  426. }
  427. }
  428. }
  429. impl<'a, 'b> IntoDynNode<'a> for LazyNodes<'a, 'b> {
  430. fn into_vnode(self, cx: &'a ScopeState) -> DynamicNode<'a> {
  431. DynamicNode::Fragment(cx.bump().alloc([self.call(cx)]))
  432. }
  433. }
  434. impl<'a> IntoDynNode<'_> for &'a str {
  435. fn into_vnode(self, cx: &ScopeState) -> DynamicNode {
  436. cx.text_node(format_args!("{}", self))
  437. }
  438. }
  439. impl IntoDynNode<'_> for String {
  440. fn into_vnode(self, cx: &ScopeState) -> DynamicNode {
  441. cx.text_node(format_args!("{}", self))
  442. }
  443. }
  444. impl<'b> IntoDynNode<'b> for Arguments<'_> {
  445. fn into_vnode(self, cx: &'b ScopeState) -> DynamicNode<'b> {
  446. cx.text_node(self)
  447. }
  448. }
  449. impl<'a> IntoDynNode<'a> for &'a VNode<'a> {
  450. fn into_vnode(self, _cx: &'a ScopeState) -> DynamicNode<'a> {
  451. DynamicNode::Fragment(_cx.bump().alloc([VNode {
  452. parent: self.parent,
  453. template: self.template,
  454. root_ids: self.root_ids,
  455. key: self.key,
  456. dynamic_nodes: self.dynamic_nodes,
  457. dynamic_attrs: self.dynamic_attrs,
  458. }]))
  459. }
  460. }
  461. pub trait IntoTemplate<'a> {
  462. fn into_template(self, _cx: &'a ScopeState) -> VNode<'a>;
  463. }
  464. impl<'a> IntoTemplate<'a> for VNode<'a> {
  465. fn into_template(self, _cx: &'a ScopeState) -> VNode<'a> {
  466. self
  467. }
  468. }
  469. impl<'a> IntoTemplate<'a> for Element<'a> {
  470. fn into_template(self, _cx: &'a ScopeState) -> VNode<'a> {
  471. match self {
  472. Some(val) => val.into_template(_cx),
  473. _ => VNode::empty().unwrap(),
  474. }
  475. }
  476. }
  477. impl<'a, 'b> IntoTemplate<'a> for LazyNodes<'a, 'b> {
  478. fn into_template(self, cx: &'a ScopeState) -> VNode<'a> {
  479. self.call(cx)
  480. }
  481. }
  482. // Note that we're using the E as a generic but this is never crafted anyways.
  483. #[doc(hidden)]
  484. pub struct FromNodeIterator;
  485. impl<'a, T, I> IntoDynNode<'a, FromNodeIterator> for T
  486. where
  487. T: Iterator<Item = I>,
  488. I: IntoTemplate<'a>,
  489. {
  490. fn into_vnode(self, cx: &'a ScopeState) -> DynamicNode<'a> {
  491. let mut nodes = bumpalo::collections::Vec::new_in(cx.bump());
  492. nodes.extend(self.into_iter().map(|node| node.into_template(cx)));
  493. match nodes.into_bump_slice() {
  494. children if children.is_empty() => DynamicNode::default(),
  495. children => DynamicNode::Fragment(children),
  496. }
  497. }
  498. }
  499. /// A value that can be converted into an attribute value
  500. pub trait IntoAttributeValue<'a> {
  501. /// Convert into an attribute value
  502. fn into_value(self, bump: &'a Bump) -> AttributeValue<'a>;
  503. }
  504. impl<'a> IntoAttributeValue<'a> for &'a str {
  505. fn into_value(self, _: &'a Bump) -> AttributeValue<'a> {
  506. AttributeValue::Text(self)
  507. }
  508. }
  509. impl<'a> IntoAttributeValue<'a> for f64 {
  510. fn into_value(self, _: &'a Bump) -> AttributeValue<'a> {
  511. AttributeValue::Float(self)
  512. }
  513. }
  514. impl<'a> IntoAttributeValue<'a> for i64 {
  515. fn into_value(self, _: &'a Bump) -> AttributeValue<'a> {
  516. AttributeValue::Int(self)
  517. }
  518. }
  519. impl<'a> IntoAttributeValue<'a> for bool {
  520. fn into_value(self, _: &'a Bump) -> AttributeValue<'a> {
  521. AttributeValue::Bool(self)
  522. }
  523. }
  524. impl<'a> IntoAttributeValue<'a> for Arguments<'_> {
  525. fn into_value(self, bump: &'a Bump) -> AttributeValue<'a> {
  526. use bumpalo::core_alloc::fmt::Write;
  527. let mut str_buf = bumpalo::collections::String::new_in(bump);
  528. str_buf.write_fmt(self).unwrap();
  529. AttributeValue::Text(str_buf.into_bump_str())
  530. }
  531. }