vize_l2 0.441.0

L2 - the Davinci semantic IR (codename Disegno): the neutral op family every input dialect lowers into
Documentation
use oxc_parser::Parser;
use oxc_span::SourceType;
use vize_l0::{Allocator, SourceRoot};
use vize_l2::file::{DeclarationKind, FileIssueKind, ScopeId, ScriptUnit};
use vize_l2::lang::js::{
    CallEvent, DeclaredEvent, FileObserver, FileProducer, ProgramInput, ProgramScope,
    StatementEvent,
};
use vize_l2::resolution::ResolutionErrorKind;

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum Point {
    Unit,
    Statement,
    Function,
    Parameter,
    Call,
}
struct Observer {
    point: Point,
    failure: fn(),
    scope: Option<ScopeId>,
}
impl Observer {
    fn observe(&mut self, point: Point, scope: ScopeId) {
        if point == self.point {
            self.scope = Some(scope);
            (self.failure)();
        }
    }
}
impl<'a> FileObserver<'a> for Observer {
    type Checkpoint = ();
    fn unit(&mut self, unit: &ScriptUnit) {
        self.observe(Point::Unit, unit.scope);
    }
    fn statement(&mut self, event: StatementEvent<'_, 'a>) {
        self.observe(Point::Statement, event.scope);
    }
    fn declared(&mut self, event: DeclaredEvent<'_, 'a>) {
        match event.declaration.kind {
            DeclarationKind::Function => self.observe(Point::Function, event.declaration.scope),
            DeclarationKind::Parameter => self.observe(Point::Parameter, event.declaration.scope),
            _ => {}
        }
    }
    fn checkpoint(&self) {}
    fn call(&mut self, event: CallEvent<'_, 'a>) -> Result<(), ResolutionErrorKind> {
        self.observe(Point::Call, event.scope);
        Ok(())
    }
    fn rollback(&mut self, _: ()) {}
}

#[test]
fn caught_observer_unwind_keeps_original_program_and_refuses_every_interrupted_unit_site() {
    let arena = Allocator::default();
    for (source, profile) in [
        (
            "é<script>/* original */ function show(value) { return show(value); } const after = 1;</script>",
            SourceType::mjs(),
        ),
        (
            "é<script>/* original */ function show(value: number) { return show(value); } const after = 1;</script>",
            SourceType::ts().with_module(true),
        ),
        (
            "é<script>/* original */ function show(value: number): number { return show(value); } const after = 1;</script>",
            SourceType::ts().with_module(true),
        ),
        (
            "é<script>/* original */ function show(value?: number): number { return show(value); } const after = 1;</script>",
            SourceType::ts().with_module(true),
        ),
        (
            "é<script>/* original */ export function show(value: number): number { return show(value); } const after = 1;</script>",
            SourceType::ts().with_module(true),
        ),
    ] {
        let start = source.find("/*").unwrap();
        let end = source.find("</script>").unwrap();
        let block = SourceRoot::new(source)
            .unwrap()
            .block(source.get(start..end).unwrap(), start as u32)
            .unwrap();
        let parsed = Parser::new(&arena, block.source(), profile).parse_observed();
        let program = parsed.admitted().unwrap().program();
        for point in [
            Point::Unit,
            Point::Statement,
            Point::Function,
            Point::Parameter,
            Point::Call,
        ] {
            let mut producer = FileProducer::new(&arena, source).unwrap();
            let mut observer = Observer {
                point,
                failure: || std::panic::resume_unwind(Box::new("actual observer interruption")),
                scope: None,
            };
            let interrupted = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
                producer.program_observed(
                    ProgramInput::checked(parsed.admitted().unwrap(), block, 5).unwrap(),
                    ProgramScope::Module,
                    &mut observer,
                )
            }));
            assert!(interrupted.is_err(), "{point:?}");
            assert_eq!(producer.interrupted_programs().count(), 1);
            let file = producer.finish().unwrap();
            assert!(!file.is_complete(), "{point:?}");
            assert_eq!(file.units().len(), 1);
            assert_eq!(file.units()[0].id.index(), 5);
            assert!(file.issues().is_empty());
            let issue = file.units()[0].interruption().unwrap();
            assert_eq!(issue.kind, FileIssueKind::InterruptedProgram);
            assert_eq!(issue.unit, file.units()[0].id);
            assert_eq!(issue.span, block.span());
            assert_eq!(file.interrupted_programs().count(), 1);
            assert!(
                file.scopes()
                    .iter()
                    .any(|scope| Some(scope.id) == observer.scope)
            );
            assert!(core::ptr::eq(file.artifact().source(), source));
            assert!(core::ptr::eq(parsed.admitted().unwrap().program(), program));
            assert_eq!(program.body.len(), 2);
            assert_eq!(program.comments.len(), 1);
            assert!(parsed.diagnostics().is_empty());
            assert!(
                file.bindings()
                    .all(|binding| binding.declaration().unwrap().name != "after")
            );
        }
        let mut producer = FileProducer::new(&arena, source).unwrap();
        producer
            .program(
                ProgramInput::checked(parsed.admitted().unwrap(), block, 5).unwrap(),
                ProgramScope::Module,
            )
            .unwrap();
        let complete = producer.finish().unwrap();
        assert!(complete.is_complete());
        assert!(complete.issues().is_empty());
        assert_eq!(complete.interrupted_programs().count(), 0);
        assert_eq!(complete.bindings().count(), 3);
        assert_eq!(complete.references().len(), 2);
    }
}