Skip to main content

codehelion_core/semantic/
cross_language.rs

1use super::{
2    BTreeMap, DirectPropagation, FallibleKind, Language, OPTIONAL_VALIDATION_RULE, OperationKind,
3    OperationNode, RESULT_VALIDATION_RULE, SOG_SCHEMA_VERSION, SemanticCandidateConfig,
4    SemanticCandidatePair, SemanticOperationGraph, SemanticRule, SemanticRuleMatcher,
5    SemanticRulePattern, SemanticRuleScope, compatible_fallible_kinds, compatible_type_tags,
6    direct_construct_matches, only_api_name,
7};
8
9/// One explicit correspondence between Rust and C++ standard-library APIs.
10///
11/// The strings are supplemental API evidence emitted by compiler helpers, not
12/// source spellings and never stable call identifiers. A correspondence is
13/// intentionally absent for C and for every API that is not listed here.
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15pub struct CrossLanguageApiCorrespondence {
16    /// Stable identifier shown when a cross-language rule uses this entry.
17    pub id: &'static str,
18    /// The closed SOG operation the paired APIs establish.
19    pub operation: OperationKind,
20    /// Compiler-confirmed Rust API names covered by this entry.
21    pub rust_api_names: &'static [&'static str],
22    /// Compiler-confirmed C++ API names covered by this entry.
23    pub cpp_api_names: &'static [&'static str],
24}
25
26const CROSS_LANGUAGE_API_CORRESPONDENCES: &[CrossLanguageApiCorrespondence] = &[
27    CrossLanguageApiCorrespondence {
28        id: "sequence-source-v1",
29        operation: OperationKind::Source,
30        rust_api_names: &["rust::IntoIterator::into_iter", "rust::slice::iter"],
31        cpp_api_names: &["std::begin"],
32    },
33    CrossLanguageApiCorrespondence {
34        id: "sequence-filter-v1",
35        operation: OperationKind::Filter,
36        rust_api_names: &["rust::Iterator::filter"],
37        cpp_api_names: &["std::copy_if"],
38    },
39    CrossLanguageApiCorrespondence {
40        id: "sequence-map-v1",
41        operation: OperationKind::Map,
42        rust_api_names: &["rust::Iterator::map"],
43        cpp_api_names: &["std::transform"],
44    },
45    CrossLanguageApiCorrespondence {
46        id: "sequence-reduce-v1",
47        operation: OperationKind::Reduce,
48        rust_api_names: &["rust::Iterator::fold"],
49        cpp_api_names: &["std::accumulate"],
50    },
51    CrossLanguageApiCorrespondence {
52        id: "sequence-collect-v1",
53        operation: OperationKind::Collect,
54        rust_api_names: &["rust::Iterator::collect", "rust::Vec::push"],
55        cpp_api_names: &["std::push_back"],
56    },
57];
58
59/// Return the complete closed Rust-to-C++ API correspondence table.
60#[must_use]
61pub const fn cross_language_api_correspondences() -> &'static [CrossLanguageApiCorrespondence] {
62    CROSS_LANGUAGE_API_CORRESPONDENCES
63}
64
65/// Find the correspondence entry for one compiler-confirmed standard API.
66///
67/// The lookup is exact and language-aware.  It does not interpret a call
68/// target, infer an API from a suffix, or map a project-owned method name.
69#[must_use]
70pub fn cross_language_api_correspondence(
71    language: Language,
72    api_name: &str,
73) -> Option<&'static CrossLanguageApiCorrespondence> {
74    CROSS_LANGUAGE_API_CORRESPONDENCES
75        .iter()
76        .find(|entry| match language {
77            Language::Rust => entry.rust_api_names.contains(&api_name),
78            Language::Cpp => entry.cpp_api_names.contains(&api_name),
79            Language::C => false,
80        })
81}
82
83pub(super) const CROSS_LANGUAGE_SEQUENCE_PIPELINE_RULE: SemanticRule = SemanticRule {
84    id: "cross-language-sequence-pipeline-v1",
85    version: 1,
86    confidence: 0.55,
87    scope: SemanticRuleScope::RustCpp,
88    pattern: SemanticRulePattern {
89        minimum_operations: 2,
90        permitted_kinds: &[
91            OperationKind::Source,
92            OperationKind::Filter,
93            OperationKind::Map,
94            OperationKind::Reduce,
95            OperationKind::Collect,
96        ],
97    },
98    matcher: SemanticRuleMatcher::EquivalentSequence,
99};
100
101pub(super) const CROSS_LANGUAGE_OPTIONAL_VALIDATION_RULE: SemanticRule = SemanticRule {
102    id: "cross-language-optional-validation-v1",
103    version: 1,
104    confidence: 0.55,
105    scope: SemanticRuleScope::RustCpp,
106    pattern: SemanticRulePattern {
107        minimum_operations: 1,
108        permitted_kinds: &[OperationKind::Validate],
109    },
110    matcher: SemanticRuleMatcher::DirectConstruct {
111        kind: OperationKind::Validate,
112        fallible_kind: FallibleKind::Option,
113        direct_propagation: None,
114    },
115};
116
117pub(super) const CROSS_LANGUAGE_RESULT_VALIDATION_RULE: SemanticRule = SemanticRule {
118    id: "cross-language-result-validation-v1",
119    version: 1,
120    confidence: 0.55,
121    scope: SemanticRuleScope::RustCpp,
122    pattern: SemanticRulePattern {
123        minimum_operations: 1,
124        permitted_kinds: &[OperationKind::Validate],
125    },
126    matcher: SemanticRuleMatcher::DirectConstruct {
127        kind: OperationKind::Validate,
128        fallible_kind: FallibleKind::Result,
129        direct_propagation: None,
130    },
131};
132
133pub(super) const CROSS_LANGUAGE_RESULT_DIRECT_PROPAGATION_RULE: SemanticRule = SemanticRule {
134    id: "cross-language-result-direct-propagation-v1",
135    version: 1,
136    confidence: 0.55,
137    scope: SemanticRuleScope::RustCpp,
138    pattern: SemanticRulePattern {
139        minimum_operations: 1,
140        permitted_kinds: &[OperationKind::PropagateError],
141    },
142    matcher: SemanticRuleMatcher::DirectConstruct {
143        kind: OperationKind::PropagateError,
144        fallible_kind: FallibleKind::Result,
145        direct_propagation: Some(DirectPropagation::ResultAdapter),
146    },
147};
148
149/// Closed compiler-construct correspondence retained beside the optional
150/// validation rule. It is intentionally distinct from the API table: a
151/// presence check is established by a resolved standard fallible type and a
152/// compiler-parsed branch, not by recovering an arbitrary method spelling.
153pub(super) const OPTIONAL_VALIDATION_CORRESPONDENCE_ID: &str = "optional-presence-validation-v1";
154
155/// Closed compiler-construct correspondence for Rust `Result::is_ok()` and
156/// C++ `expected::has_value()`/`operator bool`. Both helpers resolve the
157/// standard family before this rule can compare the branch.
158pub(super) const RESULT_VALIDATION_CORRESPONDENCE_ID: &str = "result-expected-validation-v1";
159
160/// Closed compiler-construct correspondence for a Rust `Result` adapter and a
161/// C++ `expected` identity return. It remains distinct from the API table:
162/// neither side depends on an API-call sequence.
163pub(super) const RESULT_DIRECT_PROPAGATION_CORRESPONDENCE_ID: &str =
164    "result-expected-direct-propagation-v1";
165
166/// One graph admitted to a caller-selected Rust-to-C++ comparison domain.
167///
168/// `comparison_partition` is deliberately distinct from the graph's complete
169/// `BuildVariant` fingerprint. The latter continues to identify how the graph
170/// was produced; the former proves that a caller explicitly chose its two
171/// origin variants for this comparison.
172#[derive(Debug, Clone, PartialEq, Eq)]
173pub struct CrossLanguageCandidateInput {
174    /// Opaque identity of the explicit comparison domain.
175    pub comparison_partition: [u8; 16],
176    /// Graph retaining its language and original `BuildVariant` identity.
177    pub graph: SemanticOperationGraph,
178}
179
180/// Accounting for opt-in Rust-to-C++ candidate extraction.
181#[derive(Debug, Clone, Default, PartialEq, Eq)]
182pub struct CrossLanguageCandidateStats {
183    /// Graphs presented by the explicit comparison caller.
184    pub graphs: usize,
185    /// Graphs outside the current schema, operation rule, language pair, or
186    /// closed API correspondence table.
187    pub ineligible_graphs: usize,
188    /// Distinct explicit-partition and operation-sequence buckets formed.
189    pub buckets: usize,
190    /// Buckets omitted in full for exceeding the configured member ceiling.
191    pub oversized_buckets: usize,
192    /// Pairs in eligible buckets before the run-wide ceiling is applied.
193    pub pairs_available: usize,
194    /// Pairs omitted in full because accepting their bucket exceeds the ceiling.
195    pub pairs_budget_dropped: usize,
196    /// Candidate pairs returned to the cross-language verifier.
197    pub pairs_emitted: usize,
198}
199
200/// Candidate pairs and accounting for an opt-in Rust-to-C++ comparison.
201#[derive(Debug, Clone, PartialEq, Eq)]
202pub struct CrossLanguageCandidateExtraction {
203    /// Candidate positions in the corresponding input slice.
204    pub pairs: Vec<SemanticCandidatePair>,
205    /// What the extractor considered and deliberately omitted.
206    pub stats: CrossLanguageCandidateStats,
207}
208
209/// One verified Rust-to-C++ rule application with its closed correspondence
210/// evidence.
211#[derive(Debug, Clone, PartialEq)]
212pub struct CrossLanguageRuleMatch {
213    /// Rule that justified the correspondence.
214    pub rule: SemanticRule,
215    /// Registered API or compiler-construct correspondence identifiers used by
216    /// the matched operations.
217    pub correspondence_ids: Vec<&'static str>,
218}
219
220/// Extract bounded candidates for an explicit Rust-to-C++ comparison.
221///
222/// Ordinary semantic findings must use
223/// [`extract_registered_candidates`](crate::semantic::extract_registered_candidates).
224/// This function considers only graphs that carry the same caller-provided
225/// comparison partition, have a Rust/C++ language pairing, and consist solely
226/// of closed API correspondences or compiler-confirmed direct-loop
227/// constructs. It never compares C, joins normal `BuildVariants`, or falls
228/// back to matching API-name suffixes or arbitrary source syntax.
229#[must_use]
230pub fn extract_cross_language_candidates(
231    inputs: &[CrossLanguageCandidateInput],
232    config: SemanticCandidateConfig,
233) -> CrossLanguageCandidateExtraction {
234    #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
235    struct CandidateKey {
236        comparison_partition: [u8; 16],
237        operations: Vec<OperationKind>,
238    }
239
240    #[derive(Default)]
241    struct Bucket {
242        rust: Vec<usize>,
243        cpp: Vec<usize>,
244    }
245
246    let mut stats = CrossLanguageCandidateStats {
247        graphs: inputs.len(),
248        ..CrossLanguageCandidateStats::default()
249    };
250    let mut index: BTreeMap<CandidateKey, Bucket> = BTreeMap::new();
251    for (index_in_input, input) in inputs.iter().enumerate() {
252        let graph = &input.graph;
253        let pipeline = CROSS_LANGUAGE_SEQUENCE_PIPELINE_RULE.pattern.accepts(graph)
254            && graph.nodes.iter().all(|node| {
255                node.attributes.api_names.len() == 1
256                    && node.attributes.api_names.iter().next().is_some_and(|api| {
257                        cross_language_api_correspondence(graph.language, api)
258                            .is_some_and(|entry| entry.operation == node.kind)
259                    })
260            });
261        let direct_loop_pipeline = is_cross_language_direct_loop(graph);
262        let optional_validation = is_optional_validation(graph);
263        let result_validation = is_result_validation(graph);
264        let result_direct_propagation = is_result_direct_propagation(graph);
265        if graph.schema_version != SOG_SCHEMA_VERSION
266            || !matches!(graph.language, Language::Rust | Language::Cpp)
267            || !(pipeline
268                || direct_loop_pipeline
269                || optional_validation
270                || result_validation
271                || result_direct_propagation)
272        {
273            stats.ineligible_graphs += 1;
274            continue;
275        }
276        let bucket = index
277            .entry(CandidateKey {
278                comparison_partition: input.comparison_partition,
279                operations: graph.nodes.iter().map(|node| node.kind).collect(),
280            })
281            .or_default();
282        match graph.language {
283            Language::Rust => bucket.rust.push(index_in_input),
284            Language::Cpp => bucket.cpp.push(index_in_input),
285            Language::C => unreachable!("C graphs are excluded before indexing"),
286        }
287    }
288    stats.buckets = index.len();
289
290    let mut pairs = Vec::new();
291    for bucket in index.into_values() {
292        let members = bucket.rust.len().saturating_add(bucket.cpp.len());
293        if members > config.max_bucket_members {
294            stats.oversized_buckets += 1;
295            continue;
296        }
297        let available = bucket.rust.len().saturating_mul(bucket.cpp.len());
298        stats.pairs_available = stats.pairs_available.saturating_add(available);
299        if pairs.len().saturating_add(available) > config.max_candidate_pairs {
300            stats.pairs_budget_dropped = stats.pairs_budget_dropped.saturating_add(available);
301            continue;
302        }
303        for rust in &bucket.rust {
304            for cpp in &bucket.cpp {
305                pairs.push(SemanticCandidatePair {
306                    left: (*rust).min(*cpp),
307                    right: (*rust).max(*cpp),
308                });
309            }
310        }
311    }
312    pairs.sort_unstable();
313    stats.pairs_emitted = pairs.len();
314    CrossLanguageCandidateExtraction { pairs, stats }
315}
316
317/// Verify explicit Rust-to-C++ candidates using every registered API mapping.
318#[must_use]
319pub fn verify_cross_language_candidates(
320    inputs: &[CrossLanguageCandidateInput],
321    candidates: &[SemanticCandidatePair],
322) -> Vec<(SemanticCandidatePair, CrossLanguageRuleMatch)> {
323    candidates
324        .iter()
325        .filter_map(|&candidate| {
326            let (Some(left), Some(right)) =
327                (inputs.get(candidate.left), inputs.get(candidate.right))
328            else {
329                return None;
330            };
331            (left.comparison_partition == right.comparison_partition)
332                .then(|| {
333                    match_cross_language_pipeline(&left.graph, &right.graph).or_else(|| {
334                        match_cross_language_optional_validation(&left.graph, &right.graph)
335                            .or_else(|| {
336                                match_cross_language_result_validation(&left.graph, &right.graph)
337                            })
338                            .or_else(|| {
339                                match_cross_language_result_direct_propagation(
340                                    &left.graph,
341                                    &right.graph,
342                                )
343                            })
344                    })
345                })
346                .flatten()
347                .map(|rule_match| (candidate, rule_match))
348        })
349        .collect()
350}
351
352/// Match one explicitly selected Rust-to-C++ sequence pipeline.
353///
354/// Unlike [`match_registered_pipeline`](crate::semantic::match_registered_pipeline), this is not part of ordinary
355/// BuildVariant-local detection. Its caller must first select an explicit
356/// comparison domain and use [`extract_cross_language_candidates`]. Aligned
357/// operations must either name the same closed API correspondence entry or be
358/// the deliberately small compiler-confirmed direct-loop construct pair.
359#[must_use]
360pub fn match_cross_language_pipeline(
361    left: &SemanticOperationGraph,
362    right: &SemanticOperationGraph,
363) -> Option<CrossLanguageRuleMatch> {
364    let rust_and_cpp = matches!(
365        (left.language, right.language),
366        (Language::Rust, Language::Cpp) | (Language::Cpp, Language::Rust)
367    );
368    if CROSS_LANGUAGE_SEQUENCE_PIPELINE_RULE.scope != SemanticRuleScope::RustCpp
369        || left.schema_version != SOG_SCHEMA_VERSION
370        || right.schema_version != SOG_SCHEMA_VERSION
371        || !rust_and_cpp
372        || left.build_variant_fingerprint == right.build_variant_fingerprint
373        || left.nodes.len() != right.nodes.len()
374        || !CROSS_LANGUAGE_SEQUENCE_PIPELINE_RULE.pattern.accepts(left)
375        || !CROSS_LANGUAGE_SEQUENCE_PIPELINE_RULE.pattern.accepts(right)
376    {
377        return None;
378    }
379    if is_cross_language_direct_loop(left)
380        && is_cross_language_direct_loop(right)
381        && left
382            .nodes
383            .iter()
384            .zip(&right.nodes)
385            .all(|(left_node, right_node)| left_node.kind == right_node.kind)
386    {
387        return Some(CrossLanguageRuleMatch {
388            rule: CROSS_LANGUAGE_SEQUENCE_PIPELINE_RULE,
389            correspondence_ids: vec![DIRECT_LOOP_SEQUENCE_CORRESPONDENCE_ID],
390        });
391    }
392
393    let mut correspondence_ids = Vec::with_capacity(left.nodes.len());
394    for (left_node, right_node) in left.nodes.iter().zip(&right.nodes) {
395        if left_node.kind != right_node.kind
396            || !compatible_type_tags(
397                left_node.attributes.type_tag,
398                right_node.attributes.type_tag,
399            )
400            || !compatible_fallible_kinds(
401                left_node.attributes.fallible_kind,
402                right_node.attributes.fallible_kind,
403            )
404        {
405            return None;
406        }
407        let left_api = only_api_name(left_node)?;
408        let right_api = only_api_name(right_node)?;
409        let left_entry = cross_language_api_correspondence(left.language, left_api)?;
410        let right_entry = cross_language_api_correspondence(right.language, right_api)?;
411        let correspondence_matches = left_entry.id == right_entry.id
412            && correspondence_covers(left_entry, left_node)
413            && correspondence_covers(right_entry, right_node);
414        if !correspondence_matches {
415            return None;
416        }
417        correspondence_ids.push(left_entry.id);
418    }
419    Some(CrossLanguageRuleMatch {
420        rule: CROSS_LANGUAGE_SEQUENCE_PIPELINE_RULE,
421        correspondence_ids,
422    })
423}
424
425/// Closed correspondence for compiler-confirmed Rust and C++ direct loop
426/// forms. It is intentionally separate from the API table: both helpers have
427/// proved a standard sequence plus an unchanged loop binding, while neither
428/// side is represented by an arbitrary recovered call spelling.
429pub(super) const DIRECT_LOOP_SEQUENCE_CORRESPONDENCE_ID: &str = "direct-loop-sequence-v1";
430
431/// Whether `graph` is one direct range/`for` loop form that both compiler
432/// helpers recognize. A graph with an API name is deliberately excluded: that
433/// stays on the API correspondence path, and a transformed call cannot borrow
434/// the loop rule merely because it appears beside a construct.
435fn is_cross_language_direct_loop(graph: &SemanticOperationGraph) -> bool {
436    matches!(
437        graph.nodes.as_slice(),
438        [
439            OperationNode {
440                kind: OperationKind::Source,
441                attributes: source,
442            },
443            OperationNode {
444                kind: OperationKind::Collect | OperationKind::Reduce,
445                attributes: operation,
446            },
447        ] if source.api_names.is_empty()
448            && operation.api_names.is_empty()
449            && source.fallible_kind.is_none()
450            && operation.fallible_kind.is_none()
451            && source.direct_propagation.is_none()
452            && operation.direct_propagation.is_none()
453            && source.resource_kind.is_none()
454            && operation.resource_kind.is_none()
455    )
456}
457
458/// Match an explicit Rust `Option` validation with its C++ `optional` counterpart.
459///
460/// The compiler helpers establish the standard fallible family; this rule does
461/// not infer it from source-level branch spelling.
462#[must_use]
463pub fn match_cross_language_optional_validation(
464    left: &SemanticOperationGraph,
465    right: &SemanticOperationGraph,
466) -> Option<CrossLanguageRuleMatch> {
467    let rust_and_cpp = matches!(
468        (left.language, right.language),
469        (Language::Rust, Language::Cpp) | (Language::Cpp, Language::Rust)
470    );
471    (CROSS_LANGUAGE_OPTIONAL_VALIDATION_RULE.scope == SemanticRuleScope::RustCpp
472        && left.schema_version == SOG_SCHEMA_VERSION
473        && right.schema_version == SOG_SCHEMA_VERSION
474        && rust_and_cpp
475        && left.build_variant_fingerprint != right.build_variant_fingerprint
476        && is_optional_validation(left)
477        && is_optional_validation(right))
478    .then_some(CrossLanguageRuleMatch {
479        rule: CROSS_LANGUAGE_OPTIONAL_VALIDATION_RULE,
480        correspondence_ids: vec![OPTIONAL_VALIDATION_CORRESPONDENCE_ID],
481    })
482}
483
484/// Match a Rust `Result` presence branch with the C++ `expected` counterpart.
485///
486/// Both helpers confirm the standard family. This stays a branch-level rule:
487/// it does not infer propagation or handling from either branch body.
488#[must_use]
489pub fn match_cross_language_result_validation(
490    left: &SemanticOperationGraph,
491    right: &SemanticOperationGraph,
492) -> Option<CrossLanguageRuleMatch> {
493    let rust_and_cpp = matches!(
494        (left.language, right.language),
495        (Language::Rust, Language::Cpp) | (Language::Cpp, Language::Rust)
496    );
497    (CROSS_LANGUAGE_RESULT_VALIDATION_RULE.scope == SemanticRuleScope::RustCpp
498        && left.schema_version == SOG_SCHEMA_VERSION
499        && right.schema_version == SOG_SCHEMA_VERSION
500        && rust_and_cpp
501        && left.build_variant_fingerprint != right.build_variant_fingerprint
502        && is_result_validation(left)
503        && is_result_validation(right))
504    .then_some(CrossLanguageRuleMatch {
505        rule: CROSS_LANGUAGE_RESULT_VALIDATION_RULE,
506        correspondence_ids: vec![RESULT_VALIDATION_CORRESPONDENCE_ID],
507    })
508}
509
510/// Match a Rust direct `Result` adapter with a C++ direct `expected` identity
511/// return. Both helpers must establish the same language-neutral error/value
512/// category and the exact registered propagation form.
513#[must_use]
514pub fn match_cross_language_result_direct_propagation(
515    left: &SemanticOperationGraph,
516    right: &SemanticOperationGraph,
517) -> Option<CrossLanguageRuleMatch> {
518    let rust_and_cpp = matches!(
519        (left.language, right.language),
520        (Language::Rust, Language::Cpp) | (Language::Cpp, Language::Rust)
521    );
522    (CROSS_LANGUAGE_RESULT_DIRECT_PROPAGATION_RULE.scope == SemanticRuleScope::RustCpp
523        && left.schema_version == SOG_SCHEMA_VERSION
524        && right.schema_version == SOG_SCHEMA_VERSION
525        && rust_and_cpp
526        && left.build_variant_fingerprint != right.build_variant_fingerprint
527        && is_result_direct_propagation(left)
528        && is_result_direct_propagation(right))
529    .then_some(CrossLanguageRuleMatch {
530        rule: CROSS_LANGUAGE_RESULT_DIRECT_PROPAGATION_RULE,
531        correspondence_ids: vec![RESULT_DIRECT_PROPAGATION_CORRESPONDENCE_ID],
532    })
533}
534
535const fn correspondence_covers(
536    correspondence: &CrossLanguageApiCorrespondence,
537    node: &OperationNode,
538) -> bool {
539    matches!(
540        (correspondence.operation, node.kind),
541        (OperationKind::Source, OperationKind::Source)
542            | (OperationKind::Filter, OperationKind::Filter)
543            | (OperationKind::Map, OperationKind::Map)
544            | (OperationKind::Reduce, OperationKind::Reduce)
545            | (OperationKind::Collect, OperationKind::Collect)
546    )
547}
548
549fn is_optional_validation(graph: &SemanticOperationGraph) -> bool {
550    OPTIONAL_VALIDATION_RULE.pattern.accepts(graph)
551        && matches!(
552            OPTIONAL_VALIDATION_RULE.matcher,
553            SemanticRuleMatcher::DirectConstruct {
554                kind,
555                fallible_kind,
556                direct_propagation,
557            } if direct_construct_matches(graph, kind, fallible_kind, direct_propagation)
558        )
559}
560
561fn is_result_validation(graph: &SemanticOperationGraph) -> bool {
562    RESULT_VALIDATION_RULE.pattern.accepts(graph)
563        && matches!(
564            RESULT_VALIDATION_RULE.matcher,
565            SemanticRuleMatcher::DirectConstruct {
566                kind,
567                fallible_kind,
568                direct_propagation,
569            } if direct_construct_matches(graph, kind, fallible_kind, direct_propagation)
570        )
571}
572
573fn is_result_direct_propagation(graph: &SemanticOperationGraph) -> bool {
574    CROSS_LANGUAGE_RESULT_DIRECT_PROPAGATION_RULE
575        .pattern
576        .accepts(graph)
577        && matches!(
578            CROSS_LANGUAGE_RESULT_DIRECT_PROPAGATION_RULE.matcher,
579            SemanticRuleMatcher::DirectConstruct {
580                kind,
581                fallible_kind,
582                direct_propagation,
583            } if direct_construct_matches(graph, kind, fallible_kind, direct_propagation)
584        )
585}