Skip to main content

presolve_compiler/
form_definition_lowering.rs

1//! Decorator-free V2 `defineForm({...})` field recognition.
2//!
3//! The parser selects direct instance-field initializer calls without assigning
4//! framework meaning. Canonical component proof establishes ownership and the
5//! TypeScript authority proves that the selected callee is Presolve's exported
6//! `defineForm` intrinsic.
7
8use std::collections::{BTreeMap, BTreeSet};
9
10use presolve_parser::{ParsedFile, SourceSpan};
11
12use crate::{
13    normalize_authored_semantics_v1, AuthoredSemanticCandidateKindV1,
14    AuthoredSemanticNormalizationErrorV1, AuthoredSourceRangeV1,
15    CanonicalAuthoredDeclarationKindV1, CanonicalAuthoredSemanticModelV1, CanonicalIntrinsicKindV1,
16    ResolvedAuthoredSemanticCandidateV1, ResolvedIntrinsicIdentityV1,
17};
18
19/// A direct instance-field initializer call on an authority-proven component.
20#[derive(Debug, Clone, PartialEq, Eq)]
21pub struct FormDefinitionSiteV1 {
22    pub subject: String,
23    pub declaration_source: AuthoredSourceRangeV1,
24    pub callee_source: AuthoredSourceRangeV1,
25}
26
27/// TypeScript proof that a selected field initializer is `defineForm`.
28#[derive(Debug, Clone, PartialEq, Eq)]
29pub struct ResolvedFormDefinitionV1 {
30    pub callee_source: AuthoredSourceRangeV1,
31    pub form_identity: ResolvedIntrinsicIdentityV1,
32}
33
34/// A validated canonical V2 Form-recognition product.
35#[derive(Debug, Clone, PartialEq, Eq)]
36pub struct FormDefinitionLoweringV1 {
37    pub sites: Vec<FormDefinitionSiteV1>,
38    pub model: CanonicalAuthoredSemanticModelV1,
39}
40
41#[derive(Debug, Clone, PartialEq, Eq)]
42pub enum FormDefinitionLoweringErrorV1 {
43    ComponentSourcePathMismatch,
44    UnknownComponentDeclaration { start: usize, end: usize },
45    DuplicateResolution { start: usize, end: usize },
46    UnknownFormResolution { start: usize, end: usize },
47    InvalidAuthoredSemantics(AuthoredSemanticNormalizationErrorV1),
48}
49
50impl std::fmt::Display for FormDefinitionLoweringErrorV1 {
51    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
52        match self {
53            Self::ComponentSourcePathMismatch => write!(
54                formatter,
55                "component and Form authored-semantic products must describe the same source file"
56            ),
57            Self::UnknownComponentDeclaration { start, end } => write!(
58                formatter,
59                "canonical component declaration has no source class at {start}..{end}"
60            ),
61            Self::DuplicateResolution { start, end } => {
62                write!(
63                    formatter,
64                    "duplicate Form definition resolution at {start}..{end}"
65                )
66            }
67            Self::UnknownFormResolution { start, end } => write!(
68                formatter,
69                "Form definition resolution has no source field callee at {start}..{end}"
70            ),
71            Self::InvalidAuthoredSemantics(error) => error.fmt(formatter),
72        }
73    }
74}
75
76impl std::error::Error for FormDefinitionLoweringErrorV1 {}
77
78/// Select direct instance-field calls owned by canonical V2 components.
79pub fn form_definition_sites_v1(
80    parsed: &ParsedFile,
81    component_model: &CanonicalAuthoredSemanticModelV1,
82) -> Result<Vec<FormDefinitionSiteV1>, FormDefinitionLoweringErrorV1> {
83    if component_model.source_path != parsed.path {
84        return Err(FormDefinitionLoweringErrorV1::ComponentSourcePathMismatch);
85    }
86    let components = component_model
87        .declarations
88        .iter()
89        .filter(|declaration| declaration.kind == CanonicalAuthoredDeclarationKindV1::Component)
90        .map(|declaration| (declaration.subject.clone(), range_key(declaration.source)))
91        .collect::<BTreeSet<_>>();
92    for (subject, declaration_key) in &components {
93        if !parsed
94            .classes
95            .iter()
96            .any(|class| class.name == *subject && range_key(range(class.span)) == *declaration_key)
97        {
98            return Err(FormDefinitionLoweringErrorV1::UnknownComponentDeclaration {
99                start: declaration_key.0,
100                end: declaration_key.1,
101            });
102        }
103    }
104
105    let mut sites = parsed
106        .classes
107        .iter()
108        .filter(|class| components.contains(&(class.name.clone(), range_key(range(class.span)))))
109        .flat_map(|class| {
110            class.properties.iter().filter_map(move |property| {
111                let call = property.initializer_call.as_ref()?;
112                (!property.is_static).then_some(FormDefinitionSiteV1 {
113                    subject: format!("{}.{}", class.name, property.name),
114                    declaration_source: range(property.span),
115                    callee_source: range(call.callee_span),
116                })
117            })
118        })
119        .collect::<Vec<_>>();
120    sites.sort_by_key(|site| {
121        (
122            site.callee_source.start,
123            site.callee_source.end,
124            site.subject.clone(),
125        )
126    });
127    Ok(sites)
128}
129
130/// Lower only authority-proven V2 `defineForm` calls.
131pub fn lower_form_definitions_v1(
132    parsed: &ParsedFile,
133    component_model: &CanonicalAuthoredSemanticModelV1,
134    resolutions: impl IntoIterator<Item = ResolvedFormDefinitionV1>,
135) -> Result<FormDefinitionLoweringV1, FormDefinitionLoweringErrorV1> {
136    let sites = form_definition_sites_v1(parsed, component_model)?;
137    let known_sites = sites
138        .iter()
139        .map(|site| range_key(site.callee_source))
140        .collect::<BTreeSet<_>>();
141    let mut resolution_by_site = BTreeMap::new();
142    for resolution in resolutions {
143        let key = range_key(resolution.callee_source);
144        if !known_sites.contains(&key) {
145            return Err(FormDefinitionLoweringErrorV1::UnknownFormResolution {
146                start: key.0,
147                end: key.1,
148            });
149        }
150        if resolution_by_site.insert(key, resolution).is_some() {
151            return Err(FormDefinitionLoweringErrorV1::DuplicateResolution {
152                start: key.0,
153                end: key.1,
154            });
155        }
156    }
157    let candidates = sites.iter().filter_map(|site| {
158        let resolution = resolution_by_site.get(&range_key(site.callee_source))?;
159        Some(ResolvedAuthoredSemanticCandidateV1 {
160            subject: site.subject.clone(),
161            source: site.declaration_source,
162            kind: AuthoredSemanticCandidateKindV1::ResolvedIntrinsic {
163                intrinsic_kind: CanonicalIntrinsicKindV1::Form,
164                intrinsic_identity: resolution.form_identity.clone(),
165            },
166        })
167    });
168    let model = normalize_authored_semantics_v1(parsed, candidates)
169        .map_err(FormDefinitionLoweringErrorV1::InvalidAuthoredSemantics)?;
170    Ok(FormDefinitionLoweringV1 { sites, model })
171}
172
173fn range(span: SourceSpan) -> AuthoredSourceRangeV1 {
174    AuthoredSourceRangeV1 {
175        start: span.start,
176        end: span.end,
177        line: span.line,
178        column: span.column,
179    }
180}
181
182fn range_key(range: AuthoredSourceRangeV1) -> (usize, usize) {
183    (range.start, range.end)
184}
185
186#[cfg(test)]
187mod tests {
188    use presolve_parser::parse_file;
189
190    use crate::{
191        lower_component_inheritance_v1, CanonicalAuthoredDeclarationKindV1,
192        ResolvedComponentInheritanceV1, ResolvedIntrinsicIdentityV1,
193    };
194
195    use super::{form_definition_sites_v1, lower_form_definitions_v1, ResolvedFormDefinitionV1};
196
197    fn identity(name: &str) -> ResolvedIntrinsicIdentityV1 {
198        ResolvedIntrinsicIdentityV1 {
199            name: name.to_owned(),
200            flags: 32,
201            declaration_modules: vec!["node_modules/presolve/src/index.d.ts".to_owned()],
202        }
203    }
204
205    #[test]
206    fn lowers_only_authority_proven_form_definitions() {
207        let source = r#"
208class Profile extends Base {
209  profile = createForm({ fields: {} });
210  lookalike = helper({ fields: {} });
211}
212"#;
213        let parsed = parse_file("src/Profile.tsx", source);
214        let component_site = crate::component_inheritance_sites_v1(&parsed)
215            .into_iter()
216            .next()
217            .unwrap();
218        let components = lower_component_inheritance_v1(
219            &parsed,
220            [ResolvedComponentInheritanceV1 {
221                heritage_source: component_site.heritage_source,
222                component_identity: identity("Component"),
223            }],
224        )
225        .unwrap()
226        .model;
227        let sites = form_definition_sites_v1(&parsed, &components).unwrap();
228        let lowering = lower_form_definitions_v1(
229            &parsed,
230            &components,
231            [ResolvedFormDefinitionV1 {
232                callee_source: sites[0].callee_source,
233                form_identity: identity("defineForm"),
234            }],
235        )
236        .unwrap();
237        assert_eq!(lowering.model.declarations.len(), 1);
238        assert_eq!(
239            lowering.model.declarations[0].kind,
240            CanonicalAuthoredDeclarationKindV1::Form
241        );
242        assert_eq!(lowering.model.declarations[0].subject, "Profile.profile");
243    }
244}