async_codegen/rust/mod.rs
1/*
2 * Copyright © 2025 Anand Beh
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17//!
18//! Rust syntax elements.
19//!
20//! Note that no checking exists to make sure the elements are used correctly, i.e. the correct
21//! combination of structs. Instead, the library user is expected to have basic knowledge of how
22//! Rust syntax is composed, and to combine the structs in this module likewise.
23//!
24//! Example:
25//!
26//! ```
27//! # use async_codegen::common::{CombinedSeq, NoOpSeq, SingularSeq, Str};
28//! # use async_codegen::{Output, Writable};
29//! # use async_codegen::rust::{CanHaveAttributes, CfgAttr, Deprecated, FunctionBodyImplement, FunctionDef, FunctionParam, ModPub, MustUse, NoMangle, Parameterized};
30//!
31//! async fn write_function<O>(output: &mut O) -> Result<(), O::Error> where O: Output {
32//! // For more advanced usage, you can replace Str("") by other Writable implementations
33//! let function_def = FunctionDef {
34//! mods: SingularSeq(ModPub),
35//! name: Str("my_func"),
36//! args: CombinedSeq(
37//! SingularSeq(FunctionParam(Str("var1"), Str("Type"))),
38//! SingularSeq(FunctionParam(Str("var2"), Parameterized::new(Str("Option"), SingularSeq(Str("bool")))))
39//! ),
40//! return_type: Parameterized::new(Str("Box"), SingularSeq(Str("str"))),
41//! where_conds: NoOpSeq,
42//! body: FunctionBodyImplement(Str("todo!()"))
43//! };
44//! function_def.write_to(output).await
45//! // Will render as:
46//! /*
47//! pub fn my_func(var1: Type, var2: Option<bool>) -> Box<str> {
48//! todo!()
49//! }
50//! */
51//! }
52//! ```
53//!
54
55use crate::common::{Combined, NoOp, NoOpSeq, Str, SurroundingSeqAccept};
56use crate::{Output, SequenceAccept, Writable};
57use std::fmt::Debug;
58
59mod syntax;
60#[cfg(test)]
61mod tests;
62
63/// All possible Rust editions.
64/// This is the only type in this module meant to be used as context, and not as a writable itself.
65#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
66#[non_exhaustive]
67pub enum Edition {
68 /// This Rust edition is declared for usability purposes. However, not all [crate::Writable]
69 /// implementations are guaranteed to work with it.
70 Rust2015,
71 Rust2018,
72 Rust2021,
73 Rust2024,
74}
75
76/// Imports a single type so that it can be used later.
77/// Renders as `use Type;`. Adds a new line after the semicolon.
78pub struct UseType<Type>(pub Type);
79
80/// An attribute enabled conditionally, i.e. `#[cfg_attr(Cond, Attr)]`
81#[derive(Clone, Debug)]
82pub struct CfgAttr<Cond, Attr>(pub Cond, pub Attr);
83
84/// A cfg attribute. Renders as `cfg(Cond)`.
85#[derive(Clone, Debug)]
86pub struct Cfg<Cond>(pub Cond);
87
88/// A cfg condition for targeting an OS, OS family, or architecture. For example:
89/// ```
90/// # use async_codegen::common::{NoOpSeq, SingularSeq, Str};
91/// # use async_codegen::context::EmptyContext;
92/// # use async_codegen::rust::{FunctionBodyDeclare, Cfg, FunctionDef, Target, CanHaveAttributes};
93/// # use async_codegen::util::InMemoryOutput;
94/// let function = FunctionDef {
95/// mods: NoOpSeq,
96/// name: Str("conditional_func"),
97/// args: NoOpSeq,
98/// return_type: Str("()"),
99/// where_conds: NoOpSeq,
100/// body: FunctionBodyDeclare
101/// }.with_attributes(
102/// SingularSeq(Cfg(Target::Os(Str("linux"))))
103/// );
104/// let string = InMemoryOutput::print_output(EmptyContext, &function);
105/// assert_eq!("#[cfg(target_os = \"linux\")]\nfn conditional_func() -> ();\n", string);
106/// ```
107#[derive(Clone, Debug)]
108pub enum Target<Value> {
109 Os(Value),
110 Family(Value),
111 Arch(Value),
112}
113
114/// The link attribute.
115#[derive(Clone, Debug)]
116pub struct Link<Arg>(pub Arg);
117
118/// The no mangle attribute.
119///
120/// Requires that the context satisfies [ContextProvides] for [Edition], because in Rust 2024 and
121/// beyond, the no-mangle attribute is an unsafe attribute.
122#[derive(Clone, Debug)]
123pub struct NoMangle;
124
125/// The attribute content for `allow(...)`. The tuple value must be a sequence.
126#[derive(Clone, Debug)]
127pub struct AllowLints<Lints>(pub Lints);
128
129/// The deprecated attribute. The three variants of this enum correspond to the deprecated
130/// attribute's multiple ways of being specified. See:
131/// https://doc.rust-lang.org/reference/attributes/diagnostics.html#the-deprecated-attribute
132#[derive(Clone, Debug)]
133pub enum Deprecated<Msg, Since = NoOp> {
134 Basic,
135 Message(Msg),
136 Full { since: Since, note: Msg },
137}
138
139impl Default for Deprecated<NoOp, NoOp> {
140 fn default() -> Self {
141 Self::Basic
142 }
143}
144
145impl Deprecated<NoOp, NoOp> {
146 pub fn basic() -> Self {
147 Self::Basic
148 }
149}
150
151impl<Msg> Deprecated<Msg> {
152 pub fn with_message(msg: Msg) -> Self {
153 Self::Message(msg)
154 }
155}
156
157/// The must_use attribute
158#[derive(Clone, Debug)]
159pub struct MustUse;
160
161/// The public modifier
162#[derive(Clone, Debug)]
163pub struct ModPub;
164
165/// The extern modifier, with the ABI selected as the tuple value.
166///
167/// This struct includes `unsafe`. Since Rust 2024, the unsafe keyword is required for extern
168/// functions, and before Rust 2024 it is optional. To make it easy to generate code targeting
169/// multiple editions, we unconditionally emit the "unsafe" keyword alongside "extern".
170#[derive(Clone, Debug)]
171pub struct ModUnsafeExtern<Abi>(pub Abi);
172
173/// A let statement. This statement includes the semicolon and a new line.
174#[derive(Clone, Debug)]
175pub struct LetStmt<Variable, Expr>(pub Variable, pub Expr);
176
177/// An assignation. This statement includes the semicolon and a new line.
178#[derive(Clone, Debug)]
179pub struct AssignStmt<Variable, Expr>(pub Variable, pub Expr);
180
181/// An item attached to an associated container, via "::".
182/// The output will look like `Cont::Item`.
183#[derive(Clone, Debug)]
184pub struct AssociatedItem<Cont, Item>(pub Cont, pub Item);
185
186/// A question mark following another expression.
187#[derive(Clone, Debug)]
188pub struct QuestionMarkAfter<Expr>(pub Expr);
189
190/// Wraps an expression in `Ok(EXPR)`.
191#[derive(Clone, Debug)]
192pub struct OkResultOf<Expr>(pub Expr);
193
194/// Uses the `as` expression to perform a qualified trait cast (ready for a method call).
195/// I.e., this will render as `<Type as Trait>`.
196#[derive(Clone, Debug)]
197pub struct TypeAsTrait<Type, Trait>(pub Type, pub Trait);
198
199/// Declaration of an extern block, i.e. for FFI.
200/// In Rust 2024 and later, the unsafe keyword must be added for extern blocks. Thus, this struct
201/// requires that the context satisfies [ContextProvides] for [Edition].
202#[derive(Clone, Debug)]
203pub struct ExternBlock<Abi, Body> {
204 /// The ABI chosen. Must be writable
205 pub abi: Abi,
206 /// The body of the extern block. Must be writable
207 pub body: Body,
208}
209
210impl<Abi, Body> CanHaveAttributes for ExternBlock<Abi, Body> {
211 fn with_attributes<Attr>(self, attr: Attr) -> WithAttributes<Attr, Self> {
212 WithAttributes {
213 attr,
214 separator: "\n",
215 value: self,
216 }
217 }
218}
219
220/// Places the expression inside an unsafe block.
221/// Adds new lines inside the brackets, wrapping the inner expression.
222#[derive(Clone, Debug)]
223pub struct UnsafeBlock<Expr>(pub Expr);
224
225/// Writes a closure.
226/// Adds new lines inside the brackets, wrapping the inner expression.
227#[derive(Clone, Debug)]
228pub struct Closure<InputVars, Expr> {
229 /// The input variables.
230 /// Should be a sequence. They will be comma separated and placed within the pipes.
231 /// To use no input variables, use [NoOpSeq].
232 pub input_vars: InputVars,
233 /// The expression inside the closure block.
234 pub inside_block: Expr,
235}
236
237/// Performs a call to a function inside code.
238#[derive(Clone, Debug)]
239pub struct FunctionCall<Recv, Name, Args> {
240 /// The function receiver
241 pub receiver: Recv,
242 /// Whether the function is associated, false if it's a method
243 pub is_assoc: bool,
244 /// The function name
245 pub name: Name,
246 /// The arguments. Must be a sequence
247 pub args: Args,
248}
249
250/// Provides access to the "turbofish" syntax, i.e. `Name::<Args>`.
251/// The first tuple value must be writable, and the second must be a sequence.
252///
253/// Note that if the sequence outputs nothing, this struct will behave as if no args were
254/// specified. I.e. `Turbofish(Name, NoOpSeq)` is equivalent to just `Name`.
255#[derive(Clone, Debug)]
256pub struct Turbofish<Name, Args>(pub Name, pub Args);
257
258/// A function declaration
259#[derive(Clone, Debug)]
260pub struct FunctionDef<Mods, Name, Args, Return, Where, Body> {
261 /// The modifiers. Must be a sequence.
262 pub mods: Mods,
263 /// The function name. Type variables can be declared here via [Parameterized]
264 pub name: Name,
265 /// The arguments. Must be a sequence
266 pub args: Args,
267 /// The return type, i.e. after the `->` arrow
268 pub return_type: Return,
269 /// The "where" conditions. Must be a sequence. Set to [NoOp] to disable.
270 /// Will render as `where C1, C2, C3, ...` where CX is a value in the sequence.
271 pub where_conds: Where,
272 /// The function body.
273 /// To only declare the function, this must be `;` so use [FunctionBodyDeclare]
274 /// To implement the function, use [FunctionBodyImplement]
275 pub body: Body,
276}
277
278impl<Mods, Name, Args, Return, Where, Body> CanHaveAttributes
279 for FunctionDef<Mods, Name, Args, Return, Where, Body>
280{
281 fn with_attributes<Attr>(self, attr: Attr) -> WithAttributes<Attr, Self> {
282 WithAttributes {
283 attr,
284 separator: "\n",
285 value: self,
286 }
287 }
288}
289
290/// Declares a function body. This is equivalent to just a semicolon.
291#[derive(Clone, Debug)]
292pub struct FunctionBodyDeclare;
293
294/// Implements a function body. Places the contents inside brackets
295#[derive(Clone, Debug)]
296pub struct FunctionBodyImplement<Inner>(pub Inner);
297
298/// A function pointer. Can be used for `fn`, `Fn`, `FnMut`, and `FnOnce`.
299///
300/// Example:
301/// ```
302/// # use async_codegen::common::{SingularSeq, Str};
303/// # use async_codegen::context::EmptyContext;
304/// # use async_codegen::rust::{FunctionPtr, FunctionPtrKind};
305/// # use async_codegen::util::InMemoryOutput;
306/// let function_ptr = FunctionPtr {
307/// kind: FunctionPtrKind::FnMut,
308/// args: SingularSeq(Str("String")),
309/// return_type: Str("bool")
310/// };
311/// let string = InMemoryOutput::print_output(EmptyContext, &function_ptr);
312/// assert_eq!("FnMut(String) -> bool", string);
313/// ```
314#[derive(Clone, Debug)]
315pub struct FunctionPtr<Args, Return> {
316 /// The function pointer kind
317 pub kind: FunctionPtrKind,
318 /// The arguments. Must be a sequence
319 pub args: Args,
320 /// The return type, i.e. after the `->` arrow
321 pub return_type: Return,
322}
323
324/// The kind of function type
325#[derive(Clone, Debug)]
326pub enum FunctionPtrKind {
327 /// An `fn` pointer. E.g. `fn(String) -> bool`.
328 FnPtr,
329 /// Represents [Fn]
330 Fn,
331 /// Represents [FnMut]
332 FnMut,
333 /// Represents [FnOnce]
334 FnOnce,
335}
336
337/// Renders as `Type=Value`. Intended to be used as a type argument, to specify associated types.
338#[derive(Clone, Debug)]
339pub struct AssociatedTypeEquals<Type, Value>(pub Type, pub Value);
340
341/// Adds a "dyn " before a type expression.
342#[derive(Clone, Debug)]
343pub struct DynOf<Type>(pub Type);
344
345/// Adds a "&" before a type expression
346#[derive(Clone, Debug)]
347pub struct RefOf<Type>(pub Type);
348
349/// Adds an "impl " before a type expression
350pub struct ImplOf<Type>(pub Type);
351
352/// Adds a reference with a lifetime before a type expression, i.e. `&'<lifetime> <type>`
353#[derive(Clone, Debug)]
354pub struct LifetimedRefOf<'l, Type>(pub &'l str, pub Type);
355
356/// Declares an associated type, rendering as `type VarName = Value;`.
357/// Adds new lines before and after.
358#[derive(Clone, Debug)]
359pub struct AssociatedTypeDef<VarName, Value>(pub VarName, pub Value);
360
361/// The declaration of a trait
362#[derive(Clone, Debug)]
363pub struct TraitDef<Mods, Name, TypeVars, SuperTraits, Body> {
364 /// The trait modifiers, e.g. visibility. Must be a sequence.
365 pub mods: Mods,
366 /// The name of the trait
367 pub name: Name,
368 /// The type variables. Must be a sequence
369 pub type_variables: TypeVars,
370 /// The super traits. Must be a sequence
371 pub super_traits: SuperTraits,
372 /// The trait definition's body. Use [NoOp] if none exists.
373 pub body: Body,
374}
375
376impl<Mods, Name, TypeVars, SuperTraits, Body> CanHaveAttributes
377 for TraitDef<Mods, Name, TypeVars, SuperTraits, Body>
378{
379 fn with_attributes<Attr>(self, attr: Attr) -> WithAttributes<Attr, Self> {
380 WithAttributes {
381 attr,
382 separator: "\n",
383 value: self,
384 }
385 }
386}
387
388/// The implementation declaration for a trait, applying to a certain receiver.
389#[derive(Clone, Debug)]
390pub struct TraitImpl<TypeVars, Trait, Recv, Where, Body> {
391 /// The type variables to use for the impl block itself. All type variables that appear later
392 /// on the trait or the receiver must be declared here, per Rust language rules.
393 ///
394 /// This field must be a sequence.
395 pub type_variables: TypeVars,
396 /// The trait being implemented
397 pub the_trait: Trait,
398 /// The receiver for which it is implemented
399 pub receiver: Recv,
400 /// The "where" conditions. Must be a sequence. Set to [NoOpSeq] to disable.
401 /// Will render as `where C1, C2, C3, ...` where CX is a value in the sequence.
402 pub where_conds: Where,
403 /// The body. Use [NoOp] if none exists.
404 pub body: Body,
405}
406
407impl<TypeVars, Trait, Recv, Where, Body> CanHaveAttributes
408 for TraitImpl<TypeVars, Trait, Recv, Where, Body>
409{
410 fn with_attributes<Attr>(self, attr: Attr) -> WithAttributes<Attr, Self> {
411 WithAttributes {
412 attr,
413 separator: "\n",
414 value: self,
415 }
416 }
417}
418
419/// The declaration of a struct.
420#[derive(Clone, Debug)]
421pub struct StructDef<Mods, Name, Elements> {
422 /// The struct modifiers. Must be a sequence.
423 pub mods: Mods,
424 /// The kind of the struct.
425 ///
426 /// It is suggested to use either a [NamedTuple] or [StructCall]. A semicolon will be
427 /// automatically added afterward, as is needed for tuple structs, and this semicolon will not
428 /// affect structs with named fields.
429 pub kind: StructKind<Name, Elements>,
430}
431
432impl<Mods, Name, Elements> CanHaveAttributes for StructDef<Mods, Name, Elements> {
433 fn with_attributes<Attr>(self, attr: Attr) -> WithAttributes<Attr, Self> {
434 WithAttributes {
435 attr,
436 separator: "\n",
437 value: self,
438 }
439 }
440}
441
442/// Completes the struct definition as either a named tuple or a struct with named fields.
443#[derive(Clone, Debug)]
444pub enum StructKind<Name, Elements> {
445 /// A named tuple. This will function similarly to [NamedTuple], except a semicolon will
446 /// be added afterward.
447 ///
448 /// `Name` must be writable, and `Elements` must be a writable sequence for the tuple arguments.
449 Tuple(Name, Elements),
450 /// A struct with named fields. This will function similarly to [StructCall].
451 ///
452 /// `Name` must be writable, and `Elements` must be writable sequence for the struct fields.
453 NamedFields(Name, Elements),
454}
455
456/// The construction or deconstruction of a struct.
457///
458/// When rendered, will use the format `Name { Body }`. Spaces will be added automatically.
459///
460/// This should **not** be used for tuple structs, for that see [NamedTuple].
461#[derive(Clone, Debug)]
462pub struct StructCall<Name, Body> {
463 /// The struct name. Must be writable.
464 ///
465 /// If you are declaring a struct for the first time, you can use [Parameterized] in order
466 /// to declare type variables.
467 pub name: Name,
468 /// The body. Must be writable.
469 ///
470 /// It is suggested to use [StructFields] for multiple fields, or [DeclareField] for just one.
471 pub body: Body,
472}
473
474/// Named struct fields. This will place every field on a new line with a comma afterward.
475/// It is recommended that the sequence should pass [DeclareField].
476///
477/// If you have a single field, you can skip using a sequence and just use [DeclareField] directly.
478pub struct StructFields<Fields>(pub Fields);
479
480/// Declares a single field within a struct. Renders as `Name: Value`.
481///
482/// Does not add attributes. If you want to use attributes for declaration purposes, you can use
483/// [CanHaveAttributes::with_attributes] on this field.
484pub struct DeclareField<Name, Value>(pub Name, pub Value);
485
486impl<Name, Value> CanHaveAttributes for DeclareField<Name, Value> {
487 fn with_attributes<Attr>(self, attr: Attr) -> WithAttributes<Attr, Self> {
488 WithAttributes {
489 attr,
490 separator: "\n",
491 value: self,
492 }
493 }
494}
495
496/// A named tuple type.
497///
498/// Renders as `Name(A1, A2, A3, ...)` where AX is part of the argument sequence.
499/// If no arguments exist, will render only as `Name` (i.e., a unit struct).
500pub struct NamedTuple<Name, Args> {
501 pub name: Name,
502 pub args: Args,
503}
504
505/// An anonymous tuple type. This struct's tuple value must be a sequence.
506///
507/// Renders as `(A1, A2, A3, ...)` where AX is part of the argument sequence.
508#[derive(Clone, Debug)]
509pub struct AnonTuple<Args>(pub Args);
510
511/// The unit type, i.e. `()`
512pub type UnitType = AnonTuple<NoOpSeq>;
513
514impl AnonTuple<NoOpSeq> {
515 /// Creates
516 pub fn unit() -> Self {
517 Self(NoOpSeq)
518 }
519}
520
521/// Adds attributes to ANY item.
522///
523/// The first tuple value must be a sequence. The second must be a writable value. This struct
524/// is typically constructed via [CanHaveAttributes::with_attributes].
525///
526/// Rust attributes can be put in many places, so this enables you to add attributes to any
527/// writable item. For example, adding attributes to function parameters can be done like so:
528///
529/// ```rust
530/// # use async_codegen::common::{SingularSeq, Str};
531/// # use async_codegen::context::EmptyContext;
532/// # use async_codegen::rust::{Cfg, FunctionParam, MustUse, Target, WithAttributes, CanHaveAttributes};
533/// # use async_codegen::util::InMemoryOutput;
534///
535/// let function_param = FunctionParam(Str("conditional_param"), Str("Fd")).with_attributes(
536/// SingularSeq(Cfg(Target::Os(Str("linux"))))
537/// );
538/// let string = InMemoryOutput::print_output(EmptyContext, &function_param);
539/// assert_eq!("#[cfg(target_os = \"linux\")] conditional_param: Fd", string);
540/// ```
541#[derive(Clone, Debug)]
542pub struct WithAttributes<Attr, Value> {
543 pub attr: Attr,
544 /// The separator. Usually a space or a new line, depending on what the target value is
545 pub separator: &'static str,
546 /// The value
547 pub value: Value,
548}
549
550/// A writable that can have attributes attached to it
551pub trait CanHaveAttributes: Sized {
552 /// Adds attributes to this writable
553 fn with_attributes<Attr>(self, attr: Attr) -> WithAttributes<Attr, Self>;
554}
555
556/// Defines an enum.
557///
558/// In order to use or refer to an enum, you can use [AssociatedItem] together with [NamedTuple]
559/// or [StructCall].
560pub struct EnumDef<Mods, Name, Entries> {
561 /// The modifiers on the type. Must be a sequence.
562 pub mods: Mods,
563 /// The name of the enum
564 pub name: Name,
565 /// The enum entries. Must be a sequence, each entry will be written on a new line with a comma
566 ///
567 /// As for the entries themselves, it is suggested to use [NamedTuple] or [StructCall]
568 /// depending on which kind of enum entry you want to create.
569 pub entries: Entries,
570}
571
572impl<Mods, Name, Entries> CanHaveAttributes for EnumDef<Mods, Name, Entries> {
573 fn with_attributes<Attr>(self, attr: Attr) -> WithAttributes<Attr, Self> {
574 WithAttributes {
575 attr,
576 separator: "\n",
577 value: self,
578 }
579 }
580}
581
582/// A type argument-parameterized expression. Used in relation to parameterized names and their
583/// arguments. Examples: `function_name<args>`, `TypeName<'lifetime, args>`, `MyType<Assoc=Value>`.
584///
585/// If no type args exist, [NoOpSeq] should be used.
586#[derive(Clone, Debug)]
587pub struct Parameterized<Name, TypeArgs> {
588 name: Name,
589 type_args: TypeArgs,
590}
591
592impl<Name, TypeArgs> Parameterized<Name, TypeArgs> {
593 /// Initializes an instance
594 pub fn new(name: Name, type_args: TypeArgs) -> Self {
595 Self { name, type_args }
596 }
597}
598
599/// A type variable with a sequence of bounds.
600/// Will render as `TypeVar: B1 + B2 + ...`
601#[derive(Clone, Debug)]
602pub struct BoundedTypeVar<TypeVar, Bounds>(pub TypeVar, pub Bounds);
603
604/// A standalone lifetime, intended to be used as a type argument or variable
605#[derive(Clone, Debug)]
606pub struct Lifetime<'l>(pub &'l str);
607
608/// Renders an individual function parameter, `Name: Type`
609#[derive(Clone, Debug)]
610pub struct FunctionParam<Name, Type>(pub Name, pub Type);
611
612impl<Name, Type> CanHaveAttributes for FunctionParam<Name, Type> {
613 fn with_attributes<Attr>(self, attr: Attr) -> WithAttributes<Attr, Self> {
614 WithAttributes {
615 attr,
616 separator: " ",
617 value: self,
618 }
619 }
620}
621
622/// A sequence acceptor that writes attributes. Every attribute will be surrounded with "#[]"
623#[derive(Debug)]
624pub struct AttributesAccept<'o, O, Sep> {
625 inner: SurroundingSeqAccept<'o, O, Str<&'static str>, Combined<Str<&'static str>, Sep>>,
626}
627
628impl<'o, O> AttributesAccept<'o, O, Str<&'static str>> {
629 pub fn multiline(output: &'o mut O) -> Self {
630 Self::with_separator(output, "\n")
631 }
632}
633
634impl<'o, O> AttributesAccept<'o, O, Str<&'static str>> {
635 pub fn with_separator(output: &'o mut O, separator: &'static str) -> Self {
636 Self {
637 inner: SurroundingSeqAccept::new(output, Str("#["), Combined(Str("]"), Str(separator))),
638 }
639 }
640}
641
642impl<'o, O, Sep> SequenceAccept<O> for AttributesAccept<'o, O, Sep>
643where
644 O: Output,
645 Sep: Writable<O>,
646{
647 async fn accept<W: Writable<O>>(&mut self, writable: &W) -> Result<(), O::Error> {
648 self.inner.accept(writable).await
649 }
650}