[rust][wip] swc->estree for jsx
Joe Savona committed
Jul 28, 2023 at 16:12 UTC
0e4656ac1ce6c37e2292025fa7bc52db02c0327b
7 files changed
+409
-41
compiler/forget/crates/forget_build_hir/src/build.rs
+56
-3
@@ -1,6 +1,6 @@
1
use std::collections::HashSet;
2
3
-use bumpalo::collections::String;
3
+use bumpalo::collections::{CollectIn, String};
4
use forget_diagnostics::Diagnostic;
5
use forget_estree::{
6
AssignmentTarget, BinaryExpression, BlockStatement, Expression, ExpressionOrSpread,
@@ -9,8 +9,8 @@ use forget_estree::{
9
};
10
use forget_hir::{
11
BlockKind, BranchTerminal, Environment, ForTerminal, GotoKind, IdentifierOperand, InstrIx,
12
- InstructionKind, InstructionValue, LValue, LoadGlobal, LoadLocal, Operand, PlaceOrSpread,
13
- PrimitiveValue, TerminalValue,
12
+ InstructionKind, InstructionValue, JSXAttribute, JSXElement, LValue, LoadGlobal, LoadLocal,
13
+ Operand, PlaceOrSpread, PrimitiveValue, TerminalValue,
14
};
15
16
use crate::builder::{Binding, Builder, LoopScope};
@@ -468,6 +468,10 @@ fn lower_expression<'a>(
468
})
469
}
470
471
+ Expression::JSXElement(expr) => {
472
+ InstructionValue::JSXElement(lower_jsx_element(env, builder, *expr)?)
473
+ }
474
+
475
_ => todo!("Lower expr {expr:#?}"),
476
};
477
Ok(builder.push(value))
@@ -530,6 +534,55 @@ fn lower_function<'a>(
534
})
535
}
536
537
+fn lower_jsx_element<'a>(
538
+ env: &'a Environment<'a>,
539
+ builder: &mut Builder<'a>,
540
+ expr: forget_estree::JSXElement,
541
+) -> Result<JSXElement<'a>, Diagnostic> {
542
+ let props: Result<Vec<JSXAttribute<'_>>, Diagnostic> = expr
543
+ .opening_element
544
+ .attributes
545
+ .into_iter()
546
+ .map(|attr| lower_jsx_attribute(env, builder, attr))
547
+ .collect();
548
+ let props = props?;
549
+ let children: Result<Vec<Operand>, Diagnostic> = expr
550
+ .children
551
+ .into_iter()
552
+ .map(|child| {
553
+ let ix = lower_jsx_child(env, builder, child)?;
554
+ Ok(Operand { effect: None, ix })
555
+ })
556
+ .collect();
557
+ let children = children?;
558
+ todo!("lower jsx element");
559
+ // Ok(JSXElement {
560
+ // tag: todo!(),
561
+ // props,
562
+ // children: if children.is_empty() {
563
+ // None
564
+ // } else {
565
+ // Some(children)
566
+ // },
567
+ // })
568
+}
569
+
570
+fn lower_jsx_attribute<'a>(
571
+ env: &'a Environment<'a>,
572
+ builder: &mut Builder<'a>,
573
+ attr: forget_estree::JSXAttributeOrSpread,
574
+) -> Result<JSXAttribute<'a>, Diagnostic> {
575
+ todo!("lower jsx attribute")
576
+}
577
+
578
+fn lower_jsx_child<'a>(
579
+ env: &'a Environment<'a>,
580
+ builder: &mut Builder<'a>,
581
+ child: forget_estree::JSXChildItem,
582
+) -> Result<InstrIx, Diagnostic> {
583
+ todo!("lower jsx child")
584
+}
585
+
586
fn lower_assignment<'a>(
587
env: &'a Environment<'a>,
588
builder: &mut Builder<'a>,
compiler/forget/crates/forget_estree/src/generated.rs
+3
@@ -1429,6 +1429,9 @@ impl Serialize for ExportAllDeclaration {
1429
#[derive(Deserialize, Clone, Debug)]
1430
pub struct JSXIdentifier {
1431
pub name: String,
1432
+ #[serde(skip)]
1433
+ #[serde(default)]
1434
+ pub binding: Option<Binding>,
1435
#[serde(default)]
1436
pub loc: Option<SourceLocation>,
1437
#[serde(default)]
compiler/forget/crates/forget_estree/src/snapshots/forget_estree__tests__fixtures@test.json.snap
+15
@@ -395,7 +395,10 @@ Output:
395
"type": "JSXMemberExpression",
396
"object": {
397
"type": "JSXIdentifier",
398
+ "type": "Identifier",
399
"name": "Foo",
400
+ "loc": null,
401
+ "range": null,
402
"loc": {
403
"source": null,
404
"start": {
@@ -414,7 +417,10 @@ Output:
417
},
418
"property": {
419
"type": "JSXIdentifier",
420
+ "type": "Identifier",
421
"name": "Bar",
422
+ "loc": null,
423
+ "range": null,
424
"loc": {
425
"source": null,
426
"start": {
@@ -452,7 +458,10 @@ Output:
458
"type": "JSXAttribute",
459
"name": {
460
"type": "JSXIdentifier",
461
+ "type": "Identifier",
462
"name": "a",
463
+ "loc": null,
464
+ "range": null,
465
"loc": {
466
"source": null,
467
"start": {
@@ -552,7 +561,10 @@ Output:
561
"type": "JSXMemberExpression",
562
"object": {
563
"type": "JSXIdentifier",
564
+ "type": "Identifier",
565
"name": "Foo",
566
+ "loc": null,
567
+ "range": null,
568
"loc": {
569
"source": null,
570
"start": {
@@ -571,7 +583,10 @@ Output:
583
},
584
"property": {
585
"type": "JSXIdentifier",
586
+ "type": "Identifier",
587
"name": "Bar",
588
+ "loc": null,
589
+ "range": null,
590
"loc": {
591
"source": null,
592
"start": {
compiler/forget/crates/forget_estree_codegen/src/ecmascript.json
+5
@@ -681,6 +681,11 @@
681
"fields": {
682
"name": {
683
"type": "String"
684
+ },
685
+ "binding": {
686
+ "type": "Option<Binding>",
687
+ "optional": true,
688
+ "skip": true
689
}
690
}
691
},
compiler/forget/crates/forget_estree_swc/src/lib.rs
+290
-38
@@ -8,11 +8,13 @@ use swc_core::common::errors::Handler;
8
use swc_core::common::source_map::Pos;
9
use swc_core::common::{FileName, FilePathMapping, Mark, SourceMap, Span, SyntaxContext, GLOBALS};
10
use swc_core::ecma::ast::{
11
- AssignOp, BinaryOp, BlockStmt, BlockStmtOrExpr, CallExpr, Callee, Decl, EsVersion, Expr,
12
- ExprOrSpread, Function, Ident, Lit, MemberExpr, MemberProp, ModuleItem, OptChainBase, Pat,
13
- PatOrExpr, Program, Stmt, UnaryOp, VarDecl, VarDeclKind, VarDeclOrExpr,
11
+ AssignOp, BinaryOp, BlockStmt, BlockStmtOrExpr, Callee, Decl, EsVersion, Expr, ExprOrSpread,
12
+ Function, Ident, JSXAttr, JSXAttrName, JSXAttrOrSpread, JSXAttrValue, JSXElement,
13
+ JSXElementChild, JSXElementName, JSXExpr, JSXMemberExpr, JSXObject, Lit, MemberExpr,
14
+ MemberProp, ModuleItem, OptChainBase, Pat, PatOrExpr, Program, Stmt, UnaryOp, VarDecl,
15
+ VarDeclKind, VarDeclOrExpr,
16
};
15
-use swc_core::ecma::parser::Syntax;
17
+use swc_core::ecma::parser::{Syntax, TsConfig};
18
use swc_core::ecma::transforms::base::resolver;
19
use swc_core::ecma::visit::FoldWith;
20
@@ -35,7 +37,10 @@ pub fn parse(
37
fm.clone(),
38
&handler,
39
EsVersion::Es5,
38
- Syntax::Typescript(Default::default()),
40
+ Syntax::Typescript(TsConfig {
41
+ tsx: true,
42
+ ..Default::default()
43
+ }),
44
swc::config::IsModule::Bool(true),
45
Some(&comments),
46
)?;
@@ -340,13 +345,20 @@ fn convert_expression(cx: &Context, expr: &Expr) -> forget_estree::Expression {
345
range: convert_span(&expr.span),
346
}))
347
}
343
- Expr::Await(_expr) => {
344
- // forget_estree::Expression::AwaitExpression(Box::new(forget_estree::AwaitExpression {
345
- // argument: convert_expression(cx, &expr.arg),
346
- // loc: None,
347
- // range: convert_span(&expr.span),
348
- // }))
349
- todo!("await expression")
348
+ Expr::Await(expr) => {
349
+ forget_estree::Expression::AwaitExpression(Box::new(forget_estree::AwaitExpression {
350
+ argument: convert_expression(cx, &expr.arg),
351
+ loc: None,
352
+ range: convert_span(&expr.span),
353
+ }))
354
+ }
355
+ Expr::Yield(expr) => {
356
+ forget_estree::Expression::YieldExpression(Box::new(forget_estree::YieldExpression {
357
+ argument: expr.arg.as_ref().map(|arg| convert_expression(cx, arg)),
358
+ is_delegate: expr.delegate,
359
+ loc: None,
360
+ range: convert_span(&expr.span),
361
+ }))
362
}
363
Expr::Unary(expr) => {
364
forget_estree::Expression::UnaryExpression(Box::new(forget_estree::UnaryExpression {
@@ -377,32 +389,7 @@ fn convert_expression(cx: &Context, expr: &Expr) -> forget_estree::Expression {
389
},
390
)),
391
},
380
- Expr::Lit(expr) => {
381
- let (value, range) = match expr {
382
- Lit::Bool(expr) => (
383
- forget_estree::JsValue::Bool(expr.value),
384
- convert_span(&expr.span),
385
- ),
386
- Lit::Num(expr) => (
387
- forget_estree::JsValue::Number(expr.value.into()),
388
- convert_span(&expr.span),
389
- ),
390
- Lit::Str(expr) => (
391
- forget_estree::JsValue::String(expr.value.to_string()),
392
- convert_span(&expr.span),
393
- ),
394
- Lit::Null(expr) => (forget_estree::JsValue::Null, convert_span(&expr.span)),
395
- _ => todo!(),
396
- };
397
- forget_estree::Expression::Literal(Box::new(forget_estree::Literal {
398
- value,
399
- raw: None,
400
- loc: None,
401
- regex: None,
402
- range,
403
- bigint: None,
404
- }))
405
- }
392
+ Expr::Lit(expr) => forget_estree::Expression::Literal(Box::new(convert_literal(cx, expr))),
393
Expr::Assign(expr) => forget_estree::Expression::AssignmentExpression(Box::new(
394
forget_estree::AssignmentExpression {
395
operator: convert_assignment_operator(expr.op),
@@ -470,10 +457,265 @@ fn convert_expression(cx: &Context, expr: &Expr) -> forget_estree::Expression {
457
forget_estree::Expression::MemberExpression(Box::new(member))
458
}
459
},
460
+ Expr::JSXElement(expr) => {
461
+ forget_estree::Expression::JSXElement(Box::new(convert_jsx_element(cx, expr)))
462
+ }
463
+ Expr::Paren(expr) => convert_expression(cx, &expr.expr),
464
_ => todo!("translate expression {:#?}", expr),
465
}
466
}
467
468
+fn convert_jsx_element(cx: &Context, expr: &JSXElement) -> forget_estree::JSXElement {
469
+ let attributes = expr
470
+ .opening
471
+ .attrs
472
+ .iter()
473
+ .map(|attr| match attr {
474
+ JSXAttrOrSpread::JSXAttr(attr) => forget_estree::JSXAttributeOrSpread::JSXAttribute(
475
+ Box::new(convert_jsx_attribute(cx, attr)),
476
+ ),
477
+ JSXAttrOrSpread::SpreadElement(attr) => {
478
+ forget_estree::JSXAttributeOrSpread::JSXSpreadAttribute(Box::new(
479
+ forget_estree::JSXSpreadAttribute {
480
+ argument: convert_expression(cx, &attr.expr),
481
+ loc: None,
482
+ range: None, // sigh, no span
483
+ },
484
+ ))
485
+ }
486
+ })
487
+ .collect();
488
+ let opening_element = forget_estree::JSXOpeningElement {
489
+ attributes,
490
+ name: convert_jsx_name(cx, &expr.opening.name),
491
+ self_closing: expr.opening.self_closing,
492
+ loc: None,
493
+ range: convert_span(&expr.opening.span),
494
+ };
495
+ let children = expr
496
+ .children
497
+ .iter()
498
+ .map(|child| convert_jsx_child(cx, child))
499
+ .collect();
500
+ let closing_element = expr
501
+ .closing
502
+ .as_ref()
503
+ .map(|closing| forget_estree::JSXClosingElement {
504
+ name: convert_jsx_name(cx, &closing.name),
505
+ loc: None,
506
+ range: convert_span(&closing.span),
507
+ });
508
+ forget_estree::JSXElement {
509
+ opening_element,
510
+ children,
511
+ closing_element,
512
+ loc: None,
513
+ range: convert_span(&expr.span),
514
+ }
515
+}
516
+
517
+fn convert_jsx_name(cx: &Context, name: &JSXElementName) -> forget_estree::JSXElementName {
518
+ match name {
519
+ JSXElementName::Ident(name) => {
520
+ forget_estree::JSXElementName::JSXIdentifier(Box::new(convert_jsx_identifier(cx, name)))
521
+ }
522
+ JSXElementName::JSXMemberExpr(name) => forget_estree::JSXElementName::JSXMemberExpression(
523
+ Box::new(convert_jsx_member_expression(cx, name)),
524
+ ),
525
+ JSXElementName::JSXNamespacedName(name) => {
526
+ forget_estree::JSXElementName::JSXNamespacedName(Box::new(
527
+ forget_estree::JSXNamespacedName {
528
+ namespace: convert_jsx_identifier(cx, &name.ns),
529
+ name: convert_jsx_identifier(cx, &name.name),
530
+ loc: None,
531
+ range: None, // sigh, swc doesn't have a span for the entire name
532
+ },
533
+ ))
534
+ }
535
+ }
536
+}
537
+
538
+fn convert_jsx_member_expression(
539
+ cx: &Context,
540
+ expr: &JSXMemberExpr,
541
+) -> forget_estree::JSXMemberExpression {
542
+ forget_estree::JSXMemberExpression {
543
+ object: match &expr.obj {
544
+ JSXObject::JSXMemberExpr(obj) => {
545
+ forget_estree::JSXMemberExpressionOrIdentifier::JSXMemberExpression(Box::new(
546
+ convert_jsx_member_expression(cx, obj),
547
+ ))
548
+ }
549
+ JSXObject::Ident(obj) => forget_estree::JSXMemberExpressionOrIdentifier::JSXIdentifier(
550
+ Box::new(convert_jsx_identifier(cx, obj)),
551
+ ),
552
+ },
553
+ property: convert_jsx_identifier(cx, &expr.prop),
554
+ loc: None,
555
+ range: None, // sigh, swc doesn't have a span for the member expr
556
+ }
557
+}
558
+
559
+fn convert_jsx_child(cx: &Context, child: &JSXElementChild) -> forget_estree::JSXChildItem {
560
+ match child {
561
+ JSXElementChild::JSXText(child) => {
562
+ forget_estree::JSXChildItem::JSXText(Box::new(forget_estree::JSXText {
563
+ value: child.value.to_string(),
564
+ raw: child.raw.to_string(),
565
+ loc: None,
566
+ range: convert_span(&child.span),
567
+ }))
568
+ }
569
+ JSXElementChild::JSXElement(child) => {
570
+ forget_estree::JSXChildItem::JSXElement(Box::new(convert_jsx_element(cx, child)))
571
+ }
572
+ JSXElementChild::JSXExprContainer(child) => {
573
+ forget_estree::JSXChildItem::JSXExpressionContainer(Box::new(
574
+ forget_estree::JSXExpressionContainer {
575
+ expression: match &child.expr {
576
+ JSXExpr::Expr(expr) => forget_estree::JSXExpressionOrEmpty::Expression(
577
+ convert_expression(cx, expr),
578
+ ),
579
+ JSXExpr::JSXEmptyExpr(expr) => {
580
+ forget_estree::JSXExpressionOrEmpty::JSXEmptyExpression(Box::new(
581
+ forget_estree::JSXEmptyExpression {
582
+ loc: None,
583
+ range: convert_span(&expr.span),
584
+ },
585
+ ))
586
+ }
587
+ },
588
+ loc: None,
589
+ range: convert_span(&child.span),
590
+ },
591
+ ))
592
+ }
593
+ _ => todo!("handle other jsx child types"),
594
+ }
595
+}
596
+
597
+fn convert_jsx_attribute(cx: &Context, attr: &JSXAttr) -> forget_estree::JSXAttribute {
598
+ let name = match &attr.name {
599
+ JSXAttrName::Ident(name) => {
600
+ forget_estree::JSXIdentifierOrNamespacedName::JSXIdentifier(Box::new({
601
+ let ident = convert_identifier(cx, name);
602
+ forget_estree::JSXIdentifier {
603
+ name: ident.name,
604
+ binding: ident.binding,
605
+ loc: ident.loc,
606
+ range: ident.range,
607
+ }
608
+ }))
609
+ }
610
+ JSXAttrName::JSXNamespacedName(name) => {
611
+ forget_estree::JSXIdentifierOrNamespacedName::JSXNamespacedName(Box::new(
612
+ forget_estree::JSXNamespacedName {
613
+ namespace: convert_jsx_identifier(cx, &name.ns),
614
+ name: convert_jsx_identifier(cx, &name.name),
615
+ loc: None,
616
+ range: None, // sigh, swc doesn't have a span for the entire name
617
+ },
618
+ ))
619
+ }
620
+ };
621
+ let value = attr.value.as_ref().map(|attr| match attr {
622
+ JSXAttrValue::Lit(value) => {
623
+ forget_estree::JSXAttributeValue::Literal(Box::new(convert_literal(cx, value)))
624
+ }
625
+ JSXAttrValue::JSXElement(value) => {
626
+ forget_estree::JSXAttributeValue::JSXElement(Box::new(convert_jsx_element(cx, value)))
627
+ }
628
+ JSXAttrValue::JSXExprContainer(value) => {
629
+ forget_estree::JSXAttributeValue::JSXExpressionContainer(Box::new(
630
+ forget_estree::JSXExpressionContainer {
631
+ expression: match &value.expr {
632
+ JSXExpr::Expr(expr) => forget_estree::JSXExpressionOrEmpty::Expression(
633
+ convert_expression(cx, expr),
634
+ ),
635
+ JSXExpr::JSXEmptyExpr(expr) => {
636
+ forget_estree::JSXExpressionOrEmpty::JSXEmptyExpression(Box::new(
637
+ forget_estree::JSXEmptyExpression {
638
+ loc: None,
639
+ range: convert_span(&expr.span),
640
+ },
641
+ ))
642
+ }
643
+ },
644
+ loc: None,
645
+ range: convert_span(&value.span),
646
+ },
647
+ ))
648
+ }
649
+ _ => todo!("handle other attribute value types"),
650
+ });
651
+ forget_estree::JSXAttribute {
652
+ name,
653
+ value,
654
+ loc: None,
655
+ range: convert_span(&attr.span),
656
+ }
657
+}
658
+
659
+fn convert_literal(_cx: &Context, expr: &Lit) -> forget_estree::Literal {
660
+ let (value, range, regex, bigint) = match expr {
661
+ Lit::Bool(expr) => (
662
+ forget_estree::JsValue::Bool(expr.value),
663
+ convert_span(&expr.span),
664
+ None,
665
+ None,
666
+ ),
667
+ Lit::Num(expr) => (
668
+ forget_estree::JsValue::Number(expr.value.into()),
669
+ convert_span(&expr.span),
670
+ None,
671
+ None,
672
+ ),
673
+ Lit::Str(expr) => (
674
+ forget_estree::JsValue::String(expr.value.to_string()),
675
+ convert_span(&expr.span),
676
+ None,
677
+ None,
678
+ ),
679
+ Lit::Null(expr) => (
680
+ forget_estree::JsValue::Null,
681
+ convert_span(&expr.span),
682
+ None,
683
+ None,
684
+ ),
685
+ Lit::Regex(expr) => (
686
+ forget_estree::JsValue::Undefined,
687
+ convert_span(&expr.span),
688
+ Some(forget_estree::RegExpValue {
689
+ pattern: expr.exp.to_string(),
690
+ flags: expr.flags.to_string(),
691
+ }),
692
+ None,
693
+ ),
694
+ Lit::BigInt(expr) => (
695
+ forget_estree::JsValue::Undefined,
696
+ convert_span(&expr.span),
697
+ None,
698
+ Some(
699
+ expr.raw
700
+ .as_ref()
701
+ .expect("Expected bigint to have a raw value")
702
+ .to_string(),
703
+ ),
704
+ ),
705
+ Lit::JSXText(_) => {
706
+ panic!("Unexpected expression: JSXText is not an expression")
707
+ }
708
+ };
709
+ forget_estree::Literal {
710
+ value,
711
+ raw: None,
712
+ loc: None,
713
+ regex,
714
+ range,
715
+ bigint,
716
+ }
717
+}
718
+
719
fn convert_arguments(
720
cx: &Context,
721
arguments: &[ExprOrSpread],
@@ -638,6 +880,16 @@ fn convert_identifier(cx: &Context, identifier: &Ident) -> forget_estree::Identi
880
}
881
}
882
883
+fn convert_jsx_identifier(cx: &Context, identifier: &Ident) -> forget_estree::JSXIdentifier {
884
+ let name = identifier.sym.as_ref().to_string();
885
+ forget_estree::JSXIdentifier {
886
+ name,
887
+ binding: convert_binding(cx, identifier.span.ctxt),
888
+ loc: None,
889
+ range: convert_span(&identifier.span),
890
+ }
891
+}
892
+
893
#[cfg(test)]
894
mod tests {
895
#[test]
compiler/forget/crates/forget_fixtures/tests/fixtures/jsx.js
new
+9
@@ -0,0 +1,9 @@
1
+function Component(props) {
2
+ return (
3
+ <Foo bool={true} str="string" number={3.14} var={props.variable}>
4
+ Hello
5
+ {props.foo}
6
+ <div>{props.bar}</div>
7
+ </Foo>
8
+ );
9
+}
compiler/forget/crates/forget_hir/src/instruction.rs
+31
@@ -36,6 +36,7 @@ impl<'a> Instruction<'a> {
36
| InstructionValue::LoadLocal(_)
37
| InstructionValue::Primitive(_)
38
| InstructionValue::Function(_)
39
+ | InstructionValue::JSXElement(_)
40
| InstructionValue::Tombstone => {}
41
}
42
}
@@ -62,6 +63,7 @@ impl<'a> Instruction<'a> {
63
| InstructionValue::LoadLocal(_)
64
| InstructionValue::Primitive(_)
65
| InstructionValue::Function(_)
66
+ | InstructionValue::JSXElement(_)
67
| InstructionValue::Tombstone => {}
68
}
69
Ok(())
@@ -83,6 +85,7 @@ impl<'a> Instruction<'a> {
85
| InstructionValue::Primitive(_)
86
| InstructionValue::StoreLocal(_)
87
| InstructionValue::Function(_)
88
+ | InstructionValue::JSXElement(_)
89
| InstructionValue::Tombstone => {}
90
}
91
}
@@ -122,6 +125,20 @@ impl<'a> Instruction<'a> {
125
f(dep)
126
}
127
}
128
+ InstructionValue::JSXElement(value) => {
129
+ f(&mut value.tag);
130
+ for attr in &mut value.props {
131
+ match attr {
132
+ JSXAttribute::Spread { argument } => f(argument),
133
+ JSXAttribute::Attribute { name: _, value } => f(value),
134
+ }
135
+ }
136
+ if let Some(children) = &mut value.children {
137
+ for child in children {
138
+ f(child)
139
+ }
140
+ }
141
+ }
142
InstructionValue::DeclareContext(_)
143
| InstructionValue::LoadContext(_)
144
| InstructionValue::LoadGlobal(_)
@@ -147,6 +164,7 @@ pub enum InstructionValue<'a> {
164
DeclareLocal(DeclareLocal<'a>),
165
// Destructure(Destructure<'a>),
166
Function(FunctionExpression<'a>),
167
+ JSXElement(JSXElement<'a>),
168
// JsxFragment(JsxFragment<'a>),
169
// JsxText(JsxText<'a>),
170
LoadContext(LoadContext),
@@ -382,6 +400,19 @@ pub struct StoreLocal<'a> {
400
pub value: Operand,
401
}
402
403
+#[derive(Debug)]
404
+pub struct JSXElement<'a> {
405
+ pub tag: Operand,
406
+ pub props: Vec<'a, JSXAttribute<'a>>,
407
+ pub children: Option<Vec<'a, Operand>>,
408
+}
409
+
410
+#[derive(Debug)]
411
+pub enum JSXAttribute<'a> {
412
+ Spread { argument: Operand },
413
+ Attribute { name: String<'a>, value: Operand },
414
+}
415
+
416
#[derive(Clone, Debug)]
417
pub struct Operand {
418
pub ix: InstrIx,