Skip to main content

presolve_compiler/
context_declaration_candidate.rs

1use std::collections::BTreeMap;
2
3use crate::{
4    AuthoredContextDeclarationCandidate, ConsumerEntity, ConsumerId, ContextDeclarationCandidateId,
5    ContextDeclarationCandidateKind, ContextDeclarationViolation, ContextEntity, ContextId,
6    ProviderEntity, ProviderId, SemanticId,
7};
8
9/// The only semantic links a valid Context-family declaration candidate may hold.
10#[derive(Debug, Clone, PartialEq, Eq)]
11pub enum ContextSemanticEntityId {
12    Context(ContextId),
13    Provider(ProviderId),
14    Consumer(ConsumerId),
15}
16
17#[derive(Debug, Clone, PartialEq, Eq)]
18pub enum ContextDeclarationStatus {
19    Valid(ContextSemanticEntityId),
20    Invalid(Vec<ContextDeclarationViolation>),
21}
22
23/// Immutable G18 diagnostic authority.  It is assembled from parser-retained
24/// facts and already-lowered compiler products; no source is revisited.
25#[derive(Debug, Clone, PartialEq, Eq)]
26pub struct ContextDeclarationCandidate {
27    pub authored: AuthoredContextDeclarationCandidate,
28    pub status: ContextDeclarationStatus,
29}
30
31#[derive(Debug, Clone, PartialEq, Eq)]
32pub struct ContextDeclarationCandidateRegistry {
33    candidates: Vec<ContextDeclarationCandidate>,
34    by_id: BTreeMap<ContextDeclarationCandidateId, usize>,
35}
36
37impl ContextDeclarationCandidateRegistry {
38    #[must_use]
39    pub fn candidates(&self) -> &[ContextDeclarationCandidate] {
40        &self.candidates
41    }
42
43    #[must_use]
44    pub fn candidate(
45        &self,
46        id: &ContextDeclarationCandidateId,
47    ) -> Option<&ContextDeclarationCandidate> {
48        self.by_id
49            .get(id)
50            .and_then(|index| self.candidates.get(*index))
51    }
52
53    #[must_use]
54    pub fn invalid_candidates(&self) -> Vec<&ContextDeclarationCandidate> {
55        self.candidates
56            .iter()
57            .filter(|candidate| matches!(candidate.status, ContextDeclarationStatus::Invalid(_)))
58            .collect()
59    }
60}
61
62/// Builds the immutable G18 diagnostic authority from retained declaration facts.
63///
64/// # Panics
65///
66/// Panics if an internally inconsistent candidate has no hard violation but
67/// cannot be linked to its required semantic entity.
68#[must_use]
69pub fn collect_context_declaration_candidates(
70    components: &[crate::ComponentNode],
71    contexts: &BTreeMap<ContextId, ContextEntity>,
72    providers: &BTreeMap<ProviderId, ProviderEntity>,
73    consumers: &BTreeMap<ConsumerId, ConsumerEntity>,
74) -> ContextDeclarationCandidateRegistry {
75    let mut candidates = components
76        .iter()
77        .flat_map(|component| component.context_declaration_candidates.iter())
78        .cloned()
79        .map(|authored| {
80            let mut violations = authored.violations.clone();
81            let link = match authored.kind {
82                ContextDeclarationCandidateKind::Context => entity_for_context(&authored, contexts),
83                ContextDeclarationCandidateKind::Provider => {
84                    entity_for_provider(&authored, providers)
85                }
86                ContextDeclarationCandidateKind::Consumer => {
87                    entity_for_consumer(&authored, consumers)
88                }
89            };
90            if violations.is_empty()
91                && link.is_none()
92                && matches!(
93                    authored.kind,
94                    ContextDeclarationCandidateKind::Provider
95                        | ContextDeclarationCandidateKind::Consumer
96                )
97            {
98                violations.push(ContextDeclarationViolation::UnresolvedContextDesignator);
99            }
100            let status = if violations.is_empty() {
101                ContextDeclarationStatus::Valid(
102                    link.unwrap_or_else(|| unreachable!("structurally valid candidate must lower")),
103                )
104            } else {
105                violations.sort_by_key(violation_rank);
106                violations.dedup();
107                ContextDeclarationStatus::Invalid(violations)
108            };
109            ContextDeclarationCandidate { authored, status }
110        })
111        .collect::<Vec<_>>();
112
113    // A duplicate Provider group has no winner. Retain the candidate facts and
114    // make all otherwise-valid source candidates invalid without changing G4.
115    let mut groups = BTreeMap::<(SemanticId, String, String), Vec<usize>>::new();
116    for (index, candidate) in candidates.iter().enumerate() {
117        if candidate.authored.kind == ContextDeclarationCandidateKind::Provider {
118            if let Some(designator) = &candidate.authored.context_designator {
119                groups
120                    .entry((
121                        candidate.authored.owner_component.clone(),
122                        designator.component_symbol.clone(),
123                        designator.context_member.clone(),
124                    ))
125                    .or_default()
126                    .push(index);
127            }
128        }
129    }
130    for group in groups.values().filter(|group| group.len() > 1) {
131        for index in group {
132            let candidate = &mut candidates[*index];
133            match &mut candidate.status {
134                ContextDeclarationStatus::Valid(_) => {
135                    candidate.status = ContextDeclarationStatus::Invalid(vec![
136                        ContextDeclarationViolation::DuplicateProvider,
137                    ]);
138                }
139                ContextDeclarationStatus::Invalid(violations) => {
140                    if violations.as_slice()
141                        == [ContextDeclarationViolation::UnresolvedContextDesignator]
142                    {
143                        violations.clear();
144                    }
145                    violations.push(ContextDeclarationViolation::DuplicateProvider);
146                    violations.sort_by_key(violation_rank);
147                    violations.dedup();
148                }
149            }
150        }
151    }
152    candidates.sort_by(|left, right| left.authored.id.cmp(&right.authored.id));
153    let by_id = candidates
154        .iter()
155        .enumerate()
156        .map(|(index, candidate)| (candidate.authored.id.clone(), index))
157        .collect();
158    ContextDeclarationCandidateRegistry { candidates, by_id }
159}
160
161fn entity_for_context(
162    candidate: &AuthoredContextDeclarationCandidate,
163    contexts: &BTreeMap<ContextId, ContextEntity>,
164) -> Option<ContextSemanticEntityId> {
165    contexts
166        .values()
167        .find(|entity| {
168            entity.owner.entity_id() == Some(&candidate.owner_component)
169                && candidate.field_name.as_deref() == Some(&entity.name)
170        })
171        .map(|entity| ContextSemanticEntityId::Context(entity.id.clone()))
172}
173fn entity_for_provider(
174    candidate: &AuthoredContextDeclarationCandidate,
175    providers: &BTreeMap<ProviderId, ProviderEntity>,
176) -> Option<ContextSemanticEntityId> {
177    providers
178        .values()
179        .find(|entity| {
180            entity.owner.entity_id() == Some(&candidate.owner_component)
181                && candidate.field_name.as_deref() == Some(&entity.name)
182        })
183        .map(|entity| ContextSemanticEntityId::Provider(entity.id.clone()))
184}
185fn entity_for_consumer(
186    candidate: &AuthoredContextDeclarationCandidate,
187    consumers: &BTreeMap<ConsumerId, ConsumerEntity>,
188) -> Option<ContextSemanticEntityId> {
189    consumers
190        .values()
191        .find(|entity| {
192            entity.owner.entity_id() == Some(&candidate.owner_component)
193                && candidate.field_name.as_deref() == Some(&entity.name)
194        })
195        .map(|entity| ContextSemanticEntityId::Consumer(entity.id.clone()))
196}
197fn violation_rank(violation: &ContextDeclarationViolation) -> u8 {
198    match violation {
199        ContextDeclarationViolation::InvalidDeclarationKind { .. }
200        | ContextDeclarationViolation::ConflictingSemanticDecorators => 0,
201        ContextDeclarationViolation::StaticDeclarationUnsupported => 1,
202        ContextDeclarationViolation::DecoratorArity { .. } => 2,
203        ContextDeclarationViolation::ContextDesignatorUnsupported => 3,
204        ContextDeclarationViolation::UnresolvedContextDesignator => 4,
205        ContextDeclarationViolation::MissingDeclaredType => 5,
206        ContextDeclarationViolation::MissingInitializer
207        | ContextDeclarationViolation::ForbiddenInitializer
208        | ContextDeclarationViolation::UnsupportedInitializer
209        | ContextDeclarationViolation::DefiniteAssignmentRequired => 6,
210        ContextDeclarationViolation::DuplicateProvider => 7,
211    }
212}