1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
//! Resolves where a hardcoded synthetic-handler leaf field (e.g. `chunks`) actually lives when
//! the call's own result type is an envelope wrapping the type that declares it.
//!
//! [`FieldResolver::result_field_oracle_knows`] already answers "does the call's own root type
//! declare this path" by walking the IR struct graph from the anchored root. That is exactly
//! right for a synthetic handler whose root genuinely lacks the field — refusing is correct,
//! since `{result_var}.chunks` would not compile. It is too narrow for a crate whose result type
//! is an envelope: `result_fields` already names the envelope projection (e.g. `results`), and
//! the IR can confirm whether THAT path, not the bare leaf, reaches a declaring type. Neither
//! `result_field_oracle_knows` nor `root_declares_path` tries any prefix but the bare name, so an
//! envelope shape reads identically to a genuinely unreachable field: both answer `Some(false)`.
//! [`FieldResolver::anchor_leaf`] tries every `result_fields` entry as a candidate prefix before
//! agreeing with that refusal.
use std::collections::HashSet;
use super::types::FieldResolver;
/// Where a synthetic handler's hardcoded leaf field name should be accessed from, once the
/// call's own root type has had a chance to answer directly.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum LeafAnchor {
/// The root declares the leaf itself, or no IR root was ever anchored (the permissive
/// default every oracle in this module already applies to an unresolved root) — access it
/// straight off the result variable.
Direct,
/// The root does not declare the leaf, but this `result_fields`-declared path — confirmed by
/// the IR to reach a type that does — should be accessed first. Already carries an index hop
/// (`[0]`) when the IR shows the prefix field is a `Vec<T>`.
Prefixed(String),
}
impl FieldResolver {
/// Resolve where `leaf` lives relative to the call's own result type.
///
/// `None` means every candidate was positively refused: `leaf` is unreachable both directly
/// on the root and through every `result_fields` prefix, so the caller must refuse to render
/// an accessor for it — there is no compiling path. This is purely additive over
/// [`FieldResolver::result_field_oracle_knows`]: it can only turn that oracle's `Some(false)`
/// into a rescue by finding a `result_fields` prefix that reaches `leaf`, never turn an
/// existing `Some(true)` or `None` answer into a refusal. ~keep
pub fn anchor_leaf(&self, leaf: &str) -> Option<LeafAnchor> {
match self.result_field_oracle_knows(leaf) {
None | Some(true) => Some(LeafAnchor::Direct),
Some(false) => self.anchor_leaf_via_result_fields(leaf, &self.result_fields),
}
}
fn anchor_leaf_via_result_fields(&self, leaf: &str, result_fields: &HashSet<String>) -> Option<LeafAnchor> {
result_fields.iter().find_map(|prefix| {
let prefix_path = if self.is_collection_root(prefix) {
format!("{prefix}[0]")
} else {
prefix.clone()
};
let candidate = format!("{prefix_path}.{leaf}");
(self.result_field_oracle_knows(&candidate) == Some(true)).then_some(LeafAnchor::Prefixed(prefix_path))
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::core::ir::{FieldDef, TypeDef, TypeRef};
use std::collections::HashMap;
/// `Envelope { results: Vec<Document> }`, `Document { chunks: Vec<Chunk>, metadata: Metadata }`,
/// `Metadata { output_format: String }` — the shape a consumer's envelope-wrapped result takes.
fn envelope_document_metadata_type_defs() -> Vec<TypeDef> {
vec![
TypeDef {
name: "Envelope".to_string(),
fields: vec![FieldDef {
name: "results".to_string(),
ty: TypeRef::Vec(Box::new(TypeRef::Named("Document".to_string()))),
..FieldDef::default()
}],
..TypeDef::default()
},
TypeDef {
name: "Document".to_string(),
fields: vec![
FieldDef {
name: "chunks".to_string(),
ty: TypeRef::Vec(Box::new(TypeRef::Named("Chunk".to_string()))),
..FieldDef::default()
},
FieldDef {
name: "metadata".to_string(),
ty: TypeRef::Named("Metadata".to_string()),
..FieldDef::default()
},
],
..TypeDef::default()
},
TypeDef {
name: "Metadata".to_string(),
fields: vec![FieldDef {
name: "output_format".to_string(),
ty: TypeRef::String,
..FieldDef::default()
}],
..TypeDef::default()
},
]
}
fn envelope_resolver(result_fields: &[&str]) -> FieldResolver {
let type_defs = envelope_document_metadata_type_defs();
let result_field_map = FieldResolver::ir_result_field_facts(&type_defs, "rust");
let collection_map = FieldResolver::ir_collection_fields(&type_defs);
let (reachable, excluded, optional) = FieldResolver::ir_field_sets(&type_defs);
let result_fields: HashSet<String> = result_fields.iter().map(|s| s.to_string()).collect();
FieldResolver::new(
&HashMap::new(),
&HashSet::new(),
&result_fields,
&HashSet::new(),
&HashSet::new(),
)
.with_ir_result_fields(result_field_map, Some("Envelope".to_string()))
.with_ir_collection_map(collection_map, Some("Envelope".to_string()))
.with_ir_fields(reachable, excluded, optional)
}
/// The confirmed defect: `chunks` is unreachable on `Envelope` directly, but IS reachable
/// through the `results` prefix `result_fields` names — the anchor must find it, with the
/// index hop `results` genuinely needs as a `Vec<Document>`.
#[test]
fn envelope_prefix_reaches_a_leaf_declared_on_the_nested_type() {
let resolver = envelope_resolver(&["results"]);
assert_eq!(
resolver.anchor_leaf("chunks"),
Some(LeafAnchor::Prefixed("results[0]".to_string()))
);
}
/// The deeper, nested-and-indexed shape: a two-hop path (`results[0].metadata`) must resolve
/// exactly like the one-hop `chunks` case — an anchoring fix that only handles a bare
/// projected root without an index hop, or only a single hop, is too narrow.
#[test]
fn envelope_prefix_reaches_a_leaf_nested_two_hops_deep() {
let resolver = envelope_resolver(&["results"]);
assert_eq!(
resolver.anchor_leaf("metadata.output_format"),
Some(LeafAnchor::Prefixed("results[0]".to_string()))
);
}
/// The control: a call whose root genuinely declares the leaf directly must still resolve to
/// `Direct` — the fix must not turn into "always prefix."
#[test]
fn root_declaring_the_leaf_directly_stays_direct() {
let resolver = envelope_resolver(&["results"]);
assert_eq!(resolver.anchor_leaf("results"), Some(LeafAnchor::Direct));
}
/// Sanctioned refusal: when `chunks` is genuinely unreachable — no `result_fields` prefix
/// leads to it either — the anchor must still refuse. A permissive anchor that always finds
/// SOME prefix would silently emit a non-compiling accessor instead of the honest skip.
#[test]
fn genuinely_unreachable_leaf_is_still_refused() {
let resolver = envelope_resolver(&["results"]);
assert_eq!(resolver.anchor_leaf("not_a_real_field"), None);
}
/// A `result_fields` entry that itself does not lead anywhere useful must not be mistaken
/// for the right prefix — only a prefix whose nested type positively declares the leaf wins.
#[test]
fn a_result_fields_entry_that_does_not_reach_the_leaf_is_not_used() {
let resolver = envelope_resolver(&["results", "unrelated"]);
assert_eq!(
resolver.anchor_leaf("chunks"),
Some(LeafAnchor::Prefixed("results[0]".to_string()))
);
}
/// No anchored root type at all (the state of every call site before this fix, and every
/// config-only fixture with no IR wired in) must keep the pre-existing permissive default:
/// `Direct`, never a refusal.
#[test]
fn no_anchored_root_type_keeps_the_permissive_default() {
let type_defs = envelope_document_metadata_type_defs();
let result_field_map = FieldResolver::ir_result_field_facts(&type_defs, "rust");
let (reachable, excluded, optional) = FieldResolver::ir_field_sets(&type_defs);
let resolver = FieldResolver::new(
&HashMap::new(),
&HashSet::new(),
&HashSet::new(),
&HashSet::new(),
&HashSet::new(),
)
.with_ir_result_fields(result_field_map, None)
.with_ir_fields(reachable, excluded, optional);
assert_eq!(resolver.anchor_leaf("chunks"), Some(LeafAnchor::Direct));
}
}