vize_l3 0.595.0

L3 - the Davinci reactivity IR (codename Impeto): flat scheduled ops with explicit state and effect edges
Documentation
use alloc::vec::Vec;
use vize_l0::cstr;

use crate::op::OpKind;
use crate::operand::{Operand, OperandRole};

use super::lookup::{Tables, contains, op, region};
use super::{Violation, ViolationCode};

pub(super) fn check(program: &Tables<'_, '_>, out: &mut Vec<Violation>) {
    for operand in &program.operands {
        if let Some(message) = invalid(program, operand) {
            out.push(Violation {
                code: ViolationCode::Operand,
                span: operand.value.span,
                message: cstr!(
                    "operand {} for {}: {message}",
                    operand.role.as_str(),
                    operand.op
                ),
            });
        }
    }
}

fn invalid(program: &Tables<'_, '_>, operand: &Operand<'_>) -> Option<&'static str> {
    let Some(owner) = op(program, operand.op) else {
        return Some("owner does not resolve");
    };
    if !operand.value.is_well_formed() {
        return Some("malformed value");
    }
    if let Some(handler) = operand.value.native_handler
        && (owner.kind != OpKind::SetEvent
            || operand.role != OperandRole::Value
            || handler.node().index() != owner.id.index())
    {
        return Some("native handler must be the value of its actual attached event node");
    }
    if !contains(owner.span, operand.value.span) {
        return Some("value span escapes owner");
    }
    let attribute = matches!(
        operand.role,
        OperandRole::Attribute | OperandRole::ModelAttribute
    );
    if operand.name.is_some() != attribute {
        return Some("attribute name must occur exactly on attribute roles");
    }
    if !operand.role.accepts_target(operand.target.is_some()) {
        return Some("target presence does not match operand role");
    }
    if let Some(target_id) = operand.target {
        let Some(target) = op(program, target_id) else {
            return Some("target does not resolve");
        };
        if target_id == owner.id
            || target.region != owner.region
            || !contains(target.span, owner.span)
            || !matches!(
                target.kind,
                OpKind::InsertNode | OpKind::CreateComponent | OpKind::SlotOutlet
            )
        {
            return Some("target is not a containing materialized operation in the same region");
        }
    }
    if operand.role == OperandRole::Condition {
        let Some(branch) = operand.region.and_then(|id| region(program, id)) else {
            return Some("condition branch does not resolve");
        };
        if owner.kind != OpKind::If
            || branch.owner != Some(owner.id)
            || branch.parent != Some(owner.region)
            || operand.target.is_some()
        {
            return Some("condition branch must be owned by its if operation");
        }
    } else if operand.region.is_some() {
        return Some("only condition operands may reference a branch");
    }
    None
}