Skip to main content

xidl_parser/hir/
spec.rs

1use super::{
2    Definition, InterfaceDcl, ModuleDcl, ParserProperties, Specification, TypeDcl,
3    expand_annotations, interface_codegen, parse_xidlc_pragma,
4};
5use crate::jsonrpc_hir;
6use crate::rest_hir::{self, HirProjectionKind, ProjectedHir};
7use crate::semantic;
8use serde_json::Value;
9use std::path::Path;
10
11impl From<crate::typed_ast::Specification> for Specification {
12    fn from(value: crate::typed_ast::Specification) -> Self {
13        spec_from_typed_ast(value, true)
14    }
15}
16
17impl Specification {
18    pub fn from_typed_ast_with_properties(
19        value: crate::typed_ast::Specification,
20        properties: ParserProperties,
21    ) -> Self {
22        spec_from_typed_ast(value, expand_interface(&properties))
23    }
24
25    pub fn from_typed_ast_with_properties_and_path(
26        value: crate::typed_ast::Specification,
27        properties: ParserProperties,
28        _path: impl AsRef<Path>,
29    ) -> crate::error::ParserResult<Self> {
30        Self::lower(value, expand_interface(&properties))
31    }
32
33    pub fn from_typed_ast_with_path(
34        value: crate::typed_ast::Specification,
35        _path: impl AsRef<Path>,
36    ) -> crate::error::ParserResult<Self> {
37        Self::lower(value, true)
38    }
39
40    pub fn project_typed_ast_with_properties_and_path(
41        value: crate::typed_ast::Specification,
42        properties: ParserProperties,
43        _path: impl AsRef<Path>,
44    ) -> crate::error::ParserResult<ProjectedHir> {
45        let spec = Self::lower(value, expand_interface(&properties))?;
46        match hir_projection_kind(&properties) {
47            HirProjectionKind::Rpc => Ok(ProjectedHir::Rpc(spec)),
48            HirProjectionKind::Http => rest_hir::project(&spec).map(ProjectedHir::Http),
49            HirProjectionKind::JsonRpc => jsonrpc_hir::project(&spec).map(ProjectedHir::JsonRpc),
50        }
51    }
52
53    fn lower(
54        value: crate::typed_ast::Specification,
55        expand_interfaces: bool,
56    ) -> crate::error::ParserResult<Self> {
57        let mut definitions = Vec::new();
58        collect_defs(value.0, &mut definitions);
59        let mut spec = Self(definitions);
60        // All source targets share HTTP declaration validation; builtins belong
61        // to its temporary type environment, not the generated user models.
62        rest_hir::HttpUnion::validate_source(&spec.0)?;
63        // Validate lexical source scopes before adding RPC wrapper declarations
64        // at their generated scopes.
65        if expand_interfaces {
66            Self::expand_interfaces(&mut spec.0, &mut Vec::new())?;
67        }
68        semantic::analyze(&mut spec);
69        Ok(spec)
70    }
71
72    fn expand_interfaces(
73        definitions: &mut Vec<Definition>,
74        modules: &mut Vec<String>,
75    ) -> crate::error::ParserResult<()> {
76        for mut definition in std::mem::take(definitions) {
77            match &mut definition {
78                Definition::ModuleDcl(module) => {
79                    modules.push(module.ident.clone());
80                    Self::expand_interfaces(&mut module.definition, modules)?;
81                    modules.pop();
82                }
83                Definition::InterfaceDcl(interface) => {
84                    definitions.extend(interface_codegen::expand_interface(interface, modules)?);
85                }
86                _ => {}
87            }
88            definitions.push(definition);
89        }
90        Ok(())
91    }
92}
93
94pub(crate) fn spec_from_typed_ast(
95    value: crate::typed_ast::Specification,
96    expand_interfaces: bool,
97) -> Specification {
98    Specification::lower(value, expand_interfaces).expect("HIR conversion should not fail")
99}
100
101pub(super) fn collect_defs(defs: Vec<crate::typed_ast::Definition>, out: &mut Vec<Definition>) {
102    for def in defs {
103        match def {
104            crate::typed_ast::Definition::ModuleDcl(module) => {
105                let ident = module.ident.0;
106                let annotations = expand_annotations(module.annotations);
107                let mut inner = Vec::new();
108                collect_defs(module.definition, &mut inner);
109                out.push(Definition::ModuleDcl(ModuleDcl {
110                    annotations,
111                    ident,
112                    definition: inner,
113                }));
114            }
115            crate::typed_ast::Definition::PreprocCall(call) => {
116                if let Some(pragma) = parse_xidlc_pragma(&call) {
117                    out.push(Definition::Pragma(pragma));
118                }
119            }
120            crate::typed_ast::Definition::TypeDcl(value) => {
121                out.push(Definition::TypeDcl(TypeDcl::from(value)))
122            }
123            crate::typed_ast::Definition::ConstDcl(value) => {
124                out.push(Definition::ConstDcl(value.into()))
125            }
126            crate::typed_ast::Definition::ExceptDcl(value) => {
127                out.push(Definition::ExceptDcl(value.into()))
128            }
129            crate::typed_ast::Definition::InterfaceDcl(value) => {
130                out.push(Definition::InterfaceDcl(InterfaceDcl::from(value)));
131            }
132            crate::typed_ast::Definition::PreprocInclude(_) => {
133                // Includes are now handled at the tree-sitter stage.
134                // If we see a PreprocInclude here, it means it was not expanded.
135            }
136            crate::typed_ast::Definition::TemplateModuleDcl(_)
137            | crate::typed_ast::Definition::TemplateModuleInst(_)
138            | crate::typed_ast::Definition::PreprocDefine(_) => {}
139        }
140    }
141}
142
143fn expand_interface(properties: &ParserProperties) -> bool {
144    if let Some(expand) = properties.get("expand_interface").and_then(Value::as_bool) {
145        return expand;
146    }
147
148    matches!(hir_projection_kind(properties), HirProjectionKind::Rpc)
149}
150
151fn hir_projection_kind(properties: &ParserProperties) -> HirProjectionKind {
152    match properties.get("hir_kind").and_then(Value::as_str) {
153        Some(value) if value.eq_ignore_ascii_case("http") => HirProjectionKind::Http,
154        Some(value)
155            if value.eq_ignore_ascii_case("jsonrpc") || value.eq_ignore_ascii_case("json-rpc") =>
156        {
157            HirProjectionKind::JsonRpc
158        }
159        _ => HirProjectionKind::Rpc,
160    }
161}