vj 0.2.0

vj: a SystemVerilog (IEEE 1800-2023) simulator built on slang and LLVM.
//! Source-level checks on constraint declarations that slang does not make.
//!
//! IEEE 1800-2023 18.5.1: "it shall be an error if more than one external
//! constraint block is provided for any given prototype". slang binds the
//! first external block it finds and never looks at the rest, so a second one
//! would be silently ignored; this pass rejects it.

use std::collections::HashSet;
use std::ops::Range;

use sv_lang::nodes::ConstraintDeclarationSyntax;
use sv_lang::{AstNode, SyntaxTree};

/// Rejects a prototype that more than one `constraint C::name { ... }` block
/// in the same scope claims. The error text names the prototype.
pub(crate) fn check_single_external_blocks(trees: &[SyntaxTree]) -> Result<(), String> {
    let mut seen: HashSet<(usize, Option<Range<usize>>, String)> = HashSet::new();
    for (index, tree) in trees.iter().enumerate() {
        for decl in tree.root().find_all::<ConstraintDeclarationSyntax>() {
            // Only the out-of-block form `C::name` is an external block.
            let name: String = decl
                .name()
                .syntax()
                .text()
                .chars()
                .filter(|c| !c.is_whitespace())
                .collect();
            if !name.contains("::") {
                continue;
            }
            let scope = decl.syntax().parent().map(|p| p.byte_range());
            if !seen.insert((index, scope, name.clone())) {
                return Err(format!(
                    "more than one external constraint block for the prototype `{name}` \
                     (IEEE 1800-2023 18.5.1)"
                ));
            }
        }
    }
    Ok(())
}