vize_l2 0.481.0

L2 - the Davinci semantic IR (codename Disegno): the neutral op family every input dialect lowers into
Documentation
//! One statement/declaration traversal; incomplete subtrees are never guessed.

use super::ProgramInput;
use super::observer::{DeclaredEvent, FileObserver, StatementEvent};
use crate::file::build::{DeclarationSite, Facts};
use crate::file::{
    DeclarationKind, FileIssueKind, InitializerKind, Namespace, ScopeId, ScriptUnitId,
};
use core::num::NonZeroU32;
use oxc_ast::ast::{
    BindingIdentifier, BindingPattern, Declaration, Expression, Statement, VariableDeclaration,
    VariableDeclarationKind,
};
use oxc_span::GetSpan;
use vize_l0::Span;

mod annotations;
mod exports;
mod functions;
mod imports;
mod references;

pub(super) fn program<'a, O: FileObserver<'a>>(
    input: &ProgramInput<'_, 'a>,
    facts: &mut Facts<'a>,
    unit: ScriptUnitId,
    scope: ScopeId,
    observer: &mut O,
) {
    let first_reference = facts.references.len();
    let first_export = facts.exports.len();
    let mut walk = Walk {
        input,
        facts,
        unit,
        scope,
        observer,
        context: Context::Unit,
        root_statement: None,
    };
    for (index, statement) in input.references.program().body.iter().enumerate() {
        walk.root_statement = u32::try_from(index)
            .ok()
            .and_then(|index| index.checked_add(1))
            .and_then(NonZeroU32::new);
        if walk.root_statement.is_none() {
            walk.facts
                .issue(unit, input.block.span(), FileIssueKind::BindingLimit);
            break;
        }
        walk.statement(statement);
    }
    walk.facts.resolve_references(first_reference, first_export);
}

struct Walk<'f, 'p, 'a, O> {
    input: &'f ProgramInput<'p, 'a>,
    facts: &'f mut Facts<'a>,
    unit: ScriptUnitId,
    scope: ScopeId,
    observer: &'f mut O,
    context: Context,
    root_statement: Option<NonZeroU32>,
}

#[derive(Clone, Copy, PartialEq, Eq)]
enum Context {
    Unit,
    Function,
}

impl<'a, O: FileObserver<'a>> Walk<'_, '_, 'a, O> {
    fn source_site(
        &mut self,
        literal: &oxc_ast::ast::StringLiteral<'a>,
    ) -> Option<crate::file::SourceSite> {
        self.span(literal.span)?;
        if self.context != Context::Unit {
            self.unsupported(literal.span);
            return None;
        }
        Some(crate::file::SourceSite {
            statement: self.root_statement?,
        })
    }

    fn span(&mut self, span: oxc_span::Span) -> Option<Span> {
        let Some(authored) = self
            .input
            .references
            .authored_span(Span::new(span.start, span.end))
        else {
            self.facts.issue(
                self.unit,
                self.input.block.span(),
                FileIssueKind::InvalidSpan,
            );
            return None;
        };
        Some(authored)
    }

    fn unsupported(&mut self, span: oxc_span::Span) {
        if let Some(span) = self.span(span) {
            self.facts
                .issue(self.unit, span, FileIssueKind::UnsupportedSyntax);
        }
    }

    fn statement(&mut self, statement: &Statement<'a>) {
        if self.context != Context::Unit
            || !matches!(
                statement,
                Statement::ExportDefaultDeclaration(_) | Statement::EmptyStatement(_)
            )
        {
            super::ordinary::reject(self.facts, self.unit);
        }
        if self.context != Context::Unit
            || !matches!(
                statement,
                Statement::VariableDeclaration(_) | Statement::EmptyStatement(_)
            )
        {
            super::setup::reject(self.facts, self.unit);
        }
        let Some(span) = self.span(statement.span()) else {
            return;
        };
        self.observer.statement(StatementEvent {
            unit: self.unit,
            scope: self.scope,
            span,
            statement,
        });
        if matches!(
            statement,
            Statement::ExportNamedDeclaration(_)
                | Statement::ExportDefaultDeclaration(_)
                | Statement::ExportAllDeclaration(_)
        ) && let Some(unit) = self
            .facts
            .units
            .iter_mut()
            .find(|unit| unit.id == self.unit)
        {
            unit.origin.has_export = true;
        }
        if self.context == Context::Function
            && !matches!(
                statement,
                Statement::VariableDeclaration(_)
                    | Statement::EmptyStatement(_)
                    | Statement::ExpressionStatement(_)
                    | Statement::ReturnStatement(_)
            )
        {
            self.unsupported(statement.span());
            return;
        }
        match statement {
            Statement::VariableDeclaration(value) => self.variable(value, false),
            Statement::ImportDeclaration(value) => self.import(value),
            Statement::ExportNamedDeclaration(value) => self.export_named(value),
            Statement::ExportDefaultDeclaration(value) => self.export_default(value),
            Statement::ExportAllDeclaration(value) => self.export_all(value),
            Statement::FunctionDeclaration(value) => self.function(value, false),
            Statement::ClassDeclaration(value) => {
                if let Some(id) = &value.id {
                    self.binding(id, DeclarationKind::Class, InitializerKind::Unknown, None);
                }
                self.unsupported(value.span);
            }
            Statement::EmptyStatement(_) => {}
            Statement::ExpressionStatement(value) => self.expression(&value.expression),
            Statement::ReturnStatement(value) if self.context == Context::Function => {
                if let Some(expression) = &value.argument {
                    self.expression(expression);
                }
            }
            other => self.unsupported(other.span()),
        }
    }

    fn declaration(&mut self, declaration: &Declaration<'a>, exported: bool) {
        match declaration {
            Declaration::VariableDeclaration(value) => self.variable(value, exported),
            Declaration::FunctionDeclaration(value) => self.function(value, exported),
            other => self.unsupported(other.span()),
        }
    }

    fn variable(&mut self, value: &VariableDeclaration<'a>, exported: bool) {
        if self.context != Context::Unit
            || exported
            || value.declare
            || !matches!(
                value.kind,
                VariableDeclarationKind::Const
                    | VariableDeclarationKind::Let
                    | VariableDeclarationKind::Var
            )
        {
            super::setup::reject(self.facts, self.unit);
        }
        let kind = match value.kind {
            VariableDeclarationKind::Const => DeclarationKind::Const,
            VariableDeclarationKind::Let => DeclarationKind::Let,
            VariableDeclarationKind::Var => DeclarationKind::Var,
            _ => {
                self.unsupported(value.span);
                return;
            }
        };
        if value.declare {
            self.unsupported(value.span);
            return;
        }
        for declaration in &value.declarations {
            let BindingPattern::BindingIdentifier(id) = &declaration.id else {
                super::setup::reject(self.facts, self.unit);
                self.unsupported(declaration.span);
                continue;
            };
            let initializer = match &declaration.init {
                Some(value) => initializer_kind(value),
                _ => InitializerKind::Unknown,
            };
            let type_supported = self.type_annotation(
                declaration,
                self.context == Context::Unit && !exported,
                initializer.is_primitive(),
            );
            if !initializer.is_primitive()
                || !type_supported
                || declaration.definite
                || matches!(
                    id.name.as_str(),
                    "eval"
                        | "arguments"
                        | "implements"
                        | "interface"
                        | "let"
                        | "package"
                        | "private"
                        | "protected"
                        | "public"
                        | "static"
                        | "yield"
                )
            {
                super::setup::reject(self.facts, self.unit);
            }
            let binding = self.binding(id, kind, initializer, declaration.init.as_ref());
            if exported && let Some(span) = self.span(id.span) {
                self.push_export(id.name.as_str(), binding, None, Namespace::Value, span);
            }
            if !type_supported {
                self.unsupported(declaration.span);
            }
            if let Some(expression) = &declaration.init {
                self.expression(expression);
            }
        }
    }

    fn binding(
        &mut self,
        id: &BindingIdentifier<'_>,
        kind: DeclarationKind,
        initializer: InitializerKind,
        expression: Option<&Expression<'a>>,
    ) -> Option<crate::resolution::BindingId> {
        let span = self.span(id.span)?;
        let binding = self.facts.declare(DeclarationSite {
            unit: self.unit,
            scope: self.scope,
            name: id.name.as_str(),
            span,
            namespace: Namespace::Value,
            kind,
            initializer,
            import_source: None,
            imported_name: None,
            direct_program: self.context == Context::Unit,
        })?;
        let initializer = match expression {
            Some(expression) => self.span(expression.span()).map(|span| (expression, span)),
            None => None,
        };
        if let Some(declaration) = self.facts.declarations.get(binding.index() as usize) {
            self.observer.declared(DeclaredEvent {
                declaration,
                initializer,
            });
        }
        Some(binding)
    }
}

fn initializer_kind(expression: &Expression<'_>) -> InitializerKind {
    match expression {
        Expression::StringLiteral(value) if value.lone_surrogates => {
            InitializerKind::PrimitiveStringWithLoneSurrogates
        }
        Expression::StringLiteral(value)
            if value
                .value
                .as_bytes()
                .iter()
                .any(|byte| matches!(*byte, b'\0' | b'\r')) =>
        {
            InitializerKind::PrimitiveStringWithNulOrCr
        }
        Expression::BooleanLiteral(_)
        | Expression::NullLiteral(_)
        | Expression::NumericLiteral(_)
        | Expression::BigIntLiteral(_)
        | Expression::StringLiteral(_) => InitializerKind::PrimitiveLiteral,
        _ => InitializerKind::Unknown,
    }
}