use super::types::{ConcatPlaceholder, ResObj, ResolverValue, SubstPlaceholder};
use super::utils::segments_to_key;
use crate::lexer::Segment;
pub(crate) fn contains_self_ref(v: &ResolverValue, full_key: &str) -> bool {
match v {
ResolverValue::Subst(sp) => !sp.known_absent && subst_full_key(sp) == full_key,
ResolverValue::Concat(c) => c.nodes.iter().any(|n| contains_self_ref(n, full_key)),
ResolverValue::UnresolvedArray(elems) => {
elems.iter().any(|e| contains_self_ref(e, full_key))
}
ResolverValue::Obj(o) => o.fields.values().any(|f| contains_self_ref(f, full_key)),
_ => false,
}
}
pub(crate) fn fold_self_ref(
v: &ResolverValue,
full_key: &str,
replacement: &ResolverValue,
) -> ResolverValue {
match v {
ResolverValue::Subst(sp) if subst_full_key(sp) == full_key => replacement.clone(),
ResolverValue::Concat(c) => ResolverValue::Concat(ConcatPlaceholder {
nodes: c
.nodes
.iter()
.map(|n| fold_self_ref(n, full_key, replacement))
.collect(),
separator_flags: c.separator_flags.clone(),
line: c.line,
col: c.col,
}),
ResolverValue::UnresolvedArray(elems) => ResolverValue::UnresolvedArray(
elems
.iter()
.map(|e| fold_self_ref(e, full_key, replacement))
.collect(),
),
ResolverValue::Obj(o) => {
let mut new_fields = indexmap::IndexMap::new();
for (k, val) in &o.fields {
new_fields.insert(k.clone(), fold_self_ref(val, full_key, replacement));
}
ResolverValue::Obj(ResObj {
fields: new_fields,
prior_values: o.prior_values.clone(),
reset_keys: o.reset_keys.clone(),
})
}
_ => v.clone(),
}
}
pub(crate) fn fold_known_absent_self_ref(
v: &ResolverValue,
full_key: &str,
replacement: &ResolverValue,
) -> ResolverValue {
match v {
ResolverValue::Subst(sp) if sp.known_absent && subst_full_key(sp) == full_key => {
replacement.clone()
}
ResolverValue::Concat(c) => ResolverValue::Concat(ConcatPlaceholder {
nodes: c
.nodes
.iter()
.map(|n| fold_known_absent_self_ref(n, full_key, replacement))
.collect(),
separator_flags: c.separator_flags.clone(),
line: c.line,
col: c.col,
}),
ResolverValue::UnresolvedArray(elems) => ResolverValue::UnresolvedArray(
elems
.iter()
.map(|e| fold_known_absent_self_ref(e, full_key, replacement))
.collect(),
),
ResolverValue::Obj(o) => {
let mut new_fields = indexmap::IndexMap::new();
for (k, val) in &o.fields {
new_fields.insert(
k.clone(),
fold_known_absent_self_ref(val, full_key, replacement),
);
}
ResolverValue::Obj(ResObj {
fields: new_fields,
prior_values: o.prior_values.clone(),
reset_keys: o.reset_keys.clone(),
})
}
_ => v.clone(),
}
}
pub(crate) fn fold_or_skip_prior(
prior: &ResolverValue,
full_key: &str,
old: Option<&ResolverValue>,
) -> Option<ResolverValue> {
if !contains_self_ref(prior, full_key) {
return Some(prior.clone());
}
if let Some(o) = old {
return Some(fold_self_ref(prior, full_key, o));
}
fold_optional_self_ref_absent(prior, full_key)
}
fn fold_optional_self_ref_absent(v: &ResolverValue, full_key: &str) -> Option<ResolverValue> {
match v {
ResolverValue::Subst(sp) if subst_full_key(sp) == full_key => {
if !sp.optional {
return None;
}
let mut absent = sp.clone();
absent.known_absent = true;
Some(ResolverValue::Subst(absent))
}
ResolverValue::Concat(c) => {
let mut nodes = Vec::with_capacity(c.nodes.len());
for n in &c.nodes {
nodes.push(fold_optional_self_ref_absent(n, full_key)?);
}
Some(ResolverValue::Concat(ConcatPlaceholder {
nodes,
separator_flags: c.separator_flags.clone(),
line: c.line,
col: c.col,
}))
}
ResolverValue::UnresolvedArray(elems) => {
let mut folded = Vec::with_capacity(elems.len());
for e in elems {
folded.push(fold_optional_self_ref_absent(e, full_key)?);
}
Some(ResolverValue::UnresolvedArray(folded))
}
ResolverValue::Obj(o) => {
let mut new_fields = indexmap::IndexMap::new();
for (k, val) in &o.fields {
new_fields.insert(k.clone(), fold_optional_self_ref_absent(val, full_key)?);
}
Some(ResolverValue::Obj(ResObj {
fields: new_fields,
prior_values: o.prior_values.clone(),
reset_keys: o.reset_keys.clone(),
}))
}
_ => Some(v.clone()),
}
}
pub(crate) fn subst_full_key(sp: &SubstPlaceholder) -> String {
segments_to_key(&sp.segments)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::lexer::Segment;
use crate::value::{HoconValue, ScalarValue};
use indexmap::IndexMap;
fn seg(text: &str) -> Segment {
Segment {
text: text.to_string(),
line: 1,
col: 1,
}
}
fn make_subst(key: &str, optional: bool) -> ResolverValue {
ResolverValue::Subst(SubstPlaceholder {
segments: vec![seg(key)],
optional,
known_absent: false,
list_suffix: false,
line: 1,
col: 1,
prefix_len: 0,
})
}
fn subst_known_absent(v: &ResolverValue) -> bool {
match v {
ResolverValue::Subst(sp) => sp.known_absent,
_ => panic!("expected Subst, got {:?}", v),
}
}
#[test]
fn unresolved_array_optional_self_ref_folded() {
let array = ResolverValue::UnresolvedArray(vec![make_subst("a", true)]);
let result = fold_or_skip_prior(&array, "a", None);
assert!(
result.is_some(),
"expected Some for optional self-ref array"
);
match result.unwrap() {
ResolverValue::UnresolvedArray(elems) => {
assert_eq!(elems.len(), 1);
assert!(
subst_known_absent(&elems[0]),
"array item should be known_absent after fold"
);
}
other => panic!("expected UnresolvedArray, got {:?}", other),
}
}
#[test]
fn unresolved_array_required_self_ref_returns_none() {
let array = ResolverValue::UnresolvedArray(vec![make_subst("a", false)]);
let result = fold_or_skip_prior(&array, "a", None);
assert!(
result.is_none(),
"expected None for required self-ref in array"
);
}
#[test]
fn unresolved_array_no_self_ref_passes_through() {
let array = ResolverValue::UnresolvedArray(vec![make_subst("b", true)]);
let result = fold_or_skip_prior(&array, "a", None);
assert!(result.is_some());
match result.unwrap() {
ResolverValue::UnresolvedArray(elems) => {
assert_eq!(elems.len(), 1);
assert!(!subst_known_absent(&elems[0]));
}
other => panic!("expected UnresolvedArray, got {:?}", other),
}
}
#[test]
fn unresolved_array_mixed_items_only_self_ref_folded() {
let array = ResolverValue::UnresolvedArray(vec![
make_subst("a", true), make_subst("b", true), ]);
let result = fold_or_skip_prior(&array, "a", None);
assert!(result.is_some());
match result.unwrap() {
ResolverValue::UnresolvedArray(elems) => {
assert_eq!(elems.len(), 2);
assert!(
subst_known_absent(&elems[0]),
"first item should be known_absent"
);
assert!(
!subst_known_absent(&elems[1]),
"second item should not be known_absent"
);
}
other => panic!("expected UnresolvedArray, got {:?}", other),
}
}
#[test]
fn obj_optional_self_ref_field_folded() {
let mut fields = IndexMap::new();
fields.insert("history".to_string(), make_subst("a", true));
let obj = ResolverValue::Obj(ResObj {
fields,
prior_values: IndexMap::new(),
reset_keys: std::collections::HashSet::new(),
});
let result = fold_or_skip_prior(&obj, "a", None);
assert!(
result.is_some(),
"expected Some for optional self-ref in obj field"
);
match result.unwrap() {
ResolverValue::Obj(o) => {
let field = o.fields.get("history").expect("history field missing");
assert!(
subst_known_absent(field),
"obj field should be known_absent after fold"
);
}
other => panic!("expected Obj, got {:?}", other),
}
}
#[test]
fn obj_required_self_ref_field_returns_none() {
let mut fields = IndexMap::new();
fields.insert("history".to_string(), make_subst("a", false));
let obj = ResolverValue::Obj(ResObj {
fields,
prior_values: IndexMap::new(),
reset_keys: std::collections::HashSet::new(),
});
let result = fold_or_skip_prior(&obj, "a", None);
assert!(
result.is_none(),
"expected None for required self-ref in obj field"
);
}
#[test]
fn obj_multiple_fields_only_self_ref_folded() {
let mut fields = IndexMap::new();
fields.insert("history".to_string(), make_subst("a", true));
fields.insert("other".to_string(), make_subst("b", true));
let obj = ResolverValue::Obj(ResObj {
fields,
prior_values: IndexMap::new(),
reset_keys: std::collections::HashSet::new(),
});
let result = fold_or_skip_prior(&obj, "a", None);
assert!(result.is_some());
match result.unwrap() {
ResolverValue::Obj(o) => {
let history = o.fields.get("history").expect("history missing");
let other = o.fields.get("other").expect("other missing");
assert!(
subst_known_absent(history),
"history should be known_absent"
);
assert!(
!subst_known_absent(other),
"other should not be known_absent"
);
}
other => panic!("expected Obj, got {:?}", other),
}
}
#[test]
fn fallback_resolved_scalar_passthrough() {
let scalar =
ResolverValue::Resolved(HoconValue::Scalar(ScalarValue::string("hello".to_string())));
let result = fold_optional_self_ref_absent(&scalar, "a");
assert!(result.is_some());
match result.unwrap() {
ResolverValue::Resolved(HoconValue::Scalar(sv)) => {
assert_eq!(sv.raw, "hello");
}
other => panic!("expected Resolved(Scalar), got {:?}", other),
}
}
#[test]
fn fallback_resolved_number_passthrough() {
let num =
ResolverValue::Resolved(HoconValue::Scalar(ScalarValue::number("42".to_string())));
let result = fold_optional_self_ref_absent(&num, "a");
assert!(result.is_some());
match result.unwrap() {
ResolverValue::Resolved(HoconValue::Scalar(sv)) => {
assert_eq!(sv.raw, "42");
}
other => panic!("expected Resolved(Scalar), got {:?}", other),
}
}
#[test]
fn unresolved_array_resolved_item_hits_fallback() {
let array = ResolverValue::UnresolvedArray(vec![
ResolverValue::Resolved(HoconValue::Scalar(ScalarValue::string("x".to_string()))),
make_subst("a", true),
]);
let result = fold_or_skip_prior(&array, "a", None);
assert!(result.is_some());
match result.unwrap() {
ResolverValue::UnresolvedArray(elems) => {
assert_eq!(elems.len(), 2);
match &elems[0] {
ResolverValue::Resolved(HoconValue::Scalar(sv)) => {
assert_eq!(sv.raw, "x");
}
other => panic!("expected Resolved(Scalar) at [0], got {:?}", other),
}
assert!(subst_known_absent(&elems[1]));
}
other => panic!("expected UnresolvedArray, got {:?}", other),
}
}
}
pub(crate) fn fold_nested_self_refs(v: &ResolverValue, path_prefix: &[String]) -> ResolverValue {
if let ResolverValue::Obj(o) = v {
let mut new_fields = indexmap::IndexMap::new();
for (k, field_val) in &o.fields {
let mut child_path = path_prefix.to_vec();
child_path.push(k.clone());
let full_key =
super::utils::string_segments_to_key(child_path.iter().map(String::as_str));
let folded_field = fold_nested_self_refs(field_val, &child_path);
let final_val = if contains_self_ref(&folded_field, &full_key) {
if let Some(leaf_prior) = o.prior_values.get(k) {
let leaf_prior_folded = fold_nested_self_refs(leaf_prior, &child_path);
fold_self_ref(&folded_field, &full_key, &leaf_prior_folded)
} else {
folded_field
}
} else {
folded_field
};
new_fields.insert(k.clone(), final_val);
}
ResolverValue::Obj(ResObj {
fields: new_fields,
prior_values: o.prior_values.clone(),
reset_keys: o.reset_keys.clone(),
})
} else {
v.clone()
}
}
pub(crate) fn contains_subst_by_path(v: &ResolverValue, target: &[Segment]) -> bool {
match v {
ResolverValue::Subst(sp) => {
!sp.known_absent && super::utils::segments_text_equal(&sp.segments, target)
}
ResolverValue::Concat(c) => c.nodes.iter().any(|n| contains_subst_by_path(n, target)),
ResolverValue::UnresolvedArray(elems) => {
elems.iter().any(|e| contains_subst_by_path(e, target))
}
ResolverValue::Obj(o) => o.fields.values().any(|f| contains_subst_by_path(f, target)),
_ => false,
}
}