use crate::op::{Attribute, BindingOp, ComponentOp, ElementOp};
use vize_l0::Span;
use vize_l0::pass::{Remark, RemarkArg, RemarkSink};
use super::consts::{PropsBlocker, props_blocker};
use super::lattice::RegionSummary;
use super::{StaticFacts, StaticLevel};
pub const STATIC_SUBTREE: &str = "static-subtree";
pub const STATIC_PROPS: &str = "static-props";
struct Args<'a> {
slots: [RemarkArg<'a>; 4],
len: usize,
}
impl<'a> Args<'a> {
fn new(first: RemarkArg<'a>) -> Self {
Self {
slots: [first; 4],
len: 1,
}
}
fn push(&mut self, arg: RemarkArg<'a>) {
if let Some(slot) = self.slots.get_mut(self.len) {
*slot = arg;
self.len += 1;
}
}
fn blocker(&mut self, blocker: &'a str, op: Option<&'a str>, rule: Option<&'a str>) {
self.push(RemarkArg::str("blocker", blocker));
if let Some(op) = op {
self.push(RemarkArg::str("op", op));
}
if let Some(rule) = rule {
self.push(RemarkArg::str("rule", rule));
}
}
fn props(&mut self, blocker: PropsBlocker) {
match blocker {
PropsBlocker::RefAttribute => self.blocker("ref-attribute", None, None),
PropsBlocker::Binding { op, rule } => self.blocker("binding", Some(op), rule),
}
}
fn as_slice(&self) -> &[RemarkArg<'a>] {
self.slots.get(..self.len).unwrap_or(&self.slots)
}
}
pub(super) fn element<R: RemarkSink>(
remarks: &mut R,
element: &ElementOp<'_>,
fact: StaticFacts,
children: &RegionSummary,
) {
let owner = RemarkArg::str("tag", element.tag);
let mut args = Args::new(owner);
if fact.level == StaticLevel::FullyStatic {
remarks.emit(&Remark::applied(
STATIC_SUBTREE,
element.span,
args.as_slice(),
));
return;
}
if element.tag == "svg" && !element.bindings.is_empty() {
args.blocker("svg-directive", None, None);
} else if let Some(blocker) = props_blocker(&element.attributes, &element.bindings) {
args.props(blocker);
} else if let Some(child) = children.first_dynamic.or(children.first_dynamic_text) {
args.blocker("child", Some(child), None);
}
debug_assert!(args.len > 1, "a non-static subtree always has a blocker");
remarks.emit(&Remark::missed(
STATIC_SUBTREE,
element.span,
args.as_slice(),
));
props(
remarks,
owner,
element.span,
(&element.attributes, &element.bindings),
fact,
);
}
pub(super) fn component<R: RemarkSink>(
remarks: &mut R,
component: &ComponentOp<'_>,
fact: StaticFacts,
) {
props(
remarks,
RemarkArg::str("component", component.name),
component.span,
(&component.attributes, &component.bindings),
fact,
);
}
fn props<R: RemarkSink>(
remarks: &mut R,
owner: RemarkArg<'_>,
span: Span,
(attributes, bindings): (&[Attribute<'_>], &[BindingOp<'_>]),
fact: StaticFacts,
) {
if attributes.is_empty() && bindings.is_empty() {
return;
}
let mut args = Args::new(owner);
let blocker = props_blocker(attributes, bindings);
debug_assert_eq!(blocker.is_none(), fact.props_hoistable);
match blocker {
None => remarks.emit(&Remark::applied(STATIC_PROPS, span, args.as_slice())),
Some(blocker) => {
args.props(blocker);
remarks.emit(&Remark::missed(STATIC_PROPS, span, args.as_slice()));
}
}
}