Skip to main content

opy_rs/hir/
mod.rs

1//! Opy HIR v2 — the OPY semantic model owned by `opy-rs`.
2//!
3//! The wire contract is the `wright/opy-hir` protocol, major version 2
4//! (produced as `2.0.0`), specified in `docs/hir/opy-hir-v2.md`. This module provides the serde
5//! protocol types, envelope and structural validation, and a deterministic
6//! debug dump.
7//!
8//! Ingestion order follows the spec (§8): envelope identity/version first,
9//! then unknown-node-kind rejection, then deserialization, then invariant
10//! validation. Every failure is a structured [`HirError`].
11
12pub mod dump;
13pub mod error;
14pub mod types;
15mod validate;
16
17pub use error::HirError;
18pub use types::{
19    Annotation, AnnotationArg, Declaration, DictEntry, DirectiveRecord, DirectiveValue, Event,
20    Expr, Generator, OptimizationState, Position, PreprocessingSnapshot, PreprocessingState,
21    Program, Protocol, Rule, RuleEntry, Settings, SettingsListElement, SettingsNode, SourceFile,
22    Span, Stmt, SwitchArm, TranslationState, default_var_index,
23};
24
25use serde_json::Value;
26
27/// Parse and validate an Opy HIR v2 payload from a JSON string.
28///
29/// Returns a structured [`HirError`] for malformed JSON, an unsupported
30/// protocol identity or major version, unknown node kinds, or invariant
31/// violations.
32pub fn parse_str(input: &str) -> Result<Program, HirError> {
33    let value: Value = serde_json::from_str(input)?;
34    parse_value(value)
35}
36
37/// Parse and validate an Opy HIR v2 payload from a JSON value.
38pub fn parse_value(value: Value) -> Result<Program, HirError> {
39    validate::check_envelope(&value)?;
40    validate::check_unknown_kinds(&value)?;
41    let program: Program = serde_json::from_value(value)?;
42    program.validate()?;
43    Ok(program)
44}
45
46impl Program {
47    /// Validate structural invariants of this program (spans, identifiers,
48    /// references). Envelope and node-kind checks are performed by
49    /// [`parse_str`]/[`parse_value`].
50    pub fn validate(&self) -> Result<(), HirError> {
51        validate::validate_program(self)
52    }
53
54    /// Render a deterministic debug dump suitable for tests and issue
55    /// reports.
56    pub fn dump(&self) -> String {
57        dump::dump(self)
58    }
59}
60
61#[cfg(test)]
62mod tests {
63    use serde_json::json;
64
65    use super::validate::check_envelope;
66
67    #[test]
68    fn v2_envelope_is_accepted_and_v1_is_rejected_before_body_inspection() {
69        let v2 = json!({
70            "protocol": { "name": "wright/opy-hir", "version": "2.0.0" }
71        });
72        assert!(check_envelope(&v2).is_ok());
73
74        let v1 = json!({
75            "protocol": { "name": "wright/opy-hir", "version": "1.1.0" },
76            "rules": [{ "kind": "malformed-v1-body" }]
77        });
78        let error = check_envelope(&v1).expect_err("v1 payload must not enter the v2 parser");
79        assert_eq!(error.code(), "incompatible-protocol");
80    }
81}