//! Typed live reference domains; parameter IDs and opaque historical captures are separate. use super::*; use crate::{Element, Id, Project, SharedBody}; use std::collections::BTreeSet; pub fn expression_properties(expression: &Expr) -> BTreeSet { let mut found = BTreeSet::new(); walk(expression, &mut |node| { if let Expr::Property(id) = node { found.insert(*id); } }); found } pub fn expression_definitions(expression: &Expr) -> BTreeSet { let mut found = BTreeSet::new(); walk(expression, &mut |node| match node { Expr::Quantity { unit, .. } => { found.insert(unit.local()); } Expr::Call { definition, .. } => { found.insert(definition.local()); } Expr::AsKind { kind, .. } => { found.insert(kind.local()); } _ => {} }); found } pub(crate) fn walk<'a>(expression: &'a Expr, visit: &mut impl FnMut(&'a Expr)) { let mut pending = vec![expression]; while let Some(node) = pending.pop() { visit(node); match node { Expr::List(items) => pending.extend(items.iter().rev()), Expr::Unary { operand, .. } => pending.push(operand), Expr::Binary { left, right, .. } => { pending.push(right); pending.push(left); } Expr::If { condition, when_true, when_false, } => { pending.push(when_false); pending.push(when_true); pending.push(condition); } Expr::Call { arguments, .. } => pending.extend(arguments.values().rev()), Expr::Sum { values, initial } => { pending.push(values); pending.push(initial); } Expr::Power { base, .. } => pending.push(base), Expr::AsKind { value, .. } => pending.push(value), _ => {} } } } fn walk_mut(expression: &mut Expr, visit: &mut impl FnMut(&mut Expr)) { let mut pending = vec![expression]; while let Some(node) = pending.pop() { visit(node); match node { Expr::List(items) => pending.extend(items.iter_mut()), Expr::Unary { operand, .. } => pending.push(operand), Expr::Binary { left, right, .. } => { pending.push(left); pending.push(right); } Expr::If { condition, when_true, when_false, } => { pending.push(condition); pending.push(when_true); pending.push(when_false); } Expr::Call { arguments, .. } => pending.extend(arguments.values_mut()), Expr::Sum { values, initial } => { pending.push(values); pending.push(initial); } Expr::Power { base, .. } => pending.push(base), Expr::AsKind { value, .. } => pending.push(value), _ => {} } } } pub fn element_model_references(element: &Element) -> BTreeSet { let mut result = BTreeSet::new(); let mut binding = |value: &Binding| { if let Binding::Expression(expression) = value { result.extend(expression_properties(expression)); } }; match element.shared() { Some(SharedBody::EngineeringProperty { base, .. }) => binding(base), Some(SharedBody::AnalysisCase { overrides, .. }) => { for value in overrides.values() { binding(value); } } Some(SharedBody::AnalysisCheck { predicate, .. }) => binding(predicate), _ => {} } match element.shared() { Some(SharedBody::EngineeringProperty { subject, .. }) => { result.insert(subject.id()); } Some(SharedBody::AnalysisCase { subject, overrides, .. }) => { result.extend(subject.map(AnalysisSubject::id)); result.extend(overrides.keys()); } Some(SharedBody::AnalysisCheck { subject, documented_constraint, .. }) => { result.extend(subject.map(AnalysisSubject::id)); result.extend(documented_constraint); } _ => {} } result } fn remap_ref(reference: &mut DefinitionRef, old: Id, new: Id) { let DefinitionRef::Local(id) = reference; if *id == old { *id = new; } } fn remap_type(value: &mut AnalysisType, old: Id, new: Id) { let (AnalysisType::Scalar(scalar) | AnalysisType::List(scalar)) = value; if let ScalarType::Quantity(reference) = scalar { remap_ref(reference, old, new); } } fn remap_binding(value: &mut Binding, old: Id, new: Id) { if let Binding::Expression(expression) = value { walk_mut(expression, &mut |node| match node { Expr::Property(id) if *id == old => *id = new, Expr::Quantity { unit, .. } => remap_ref(unit, old, new), Expr::Call { definition, .. } => remap_ref(definition, old, new), Expr::AsKind { kind, .. } => remap_ref(kind, old, new), _ => {} // Parameter nodes and argument keys are scoped, never global. }); } } fn remap_subject(subject: &mut AnalysisSubject, old: Id, new: Id) { let (AnalysisSubject::Element(id) | AnalysisSubject::Relation(id)) = subject; if *id == old { *id = new; } } /// Check before any mutation so a map-key collision never silently discards a binding. pub(crate) fn check_remap(project: &Project, old: Id, new: Id) -> Result<(), AnalysisError> { if old != new { for element in project.elements.values() { if let Some(SharedBody::AnalysisCase { overrides, .. }) = element.shared() && overrides.contains_key(&old) && overrides.contains_key(&new) { return Err(AnalysisError::new( element.id, "overrides", "analysis_reference_collision", "Reidentification would merge two distinct override keys.", )); } } } Ok(()) } pub(crate) fn remap_project(project: &mut Project, old: Id, new: Id) { for element in project.elements.values_mut() { match element.shared_mut() { Some(SharedBody::EngineeringProperty { subject, value_type, display_unit, base, }) => { remap_subject(subject, old, new); remap_type(value_type, old, new); if let Some(reference) = display_unit { remap_ref(reference, old, new); } remap_binding(base, old, new); } Some(SharedBody::AnalysisCase { subject, overrides, .. }) => { if let Some(subject) = subject { remap_subject(subject, old, new); } if old != new && let Some(value) = overrides.remove(&old) { overrides.insert(new, value); } for value in overrides.values_mut() { remap_binding(value, old, new); } } Some(SharedBody::AnalysisCheck { subject, documented_constraint, predicate, }) => { if let Some(subject) = subject { remap_subject(subject, old, new); } if *documented_constraint == Some(old) { *documented_constraint = Some(new); } remap_binding(predicate, old, new); } _ => {} } } for definition in project.definitions.values_mut() { match &mut definition.body { DefinitionBody::QuantityKind(QuantityKindDefinition { algebra: QuantityAlgebra::Affine { difference }, .. }) => remap_ref(difference, old, new), DefinitionBody::Unit(unit) => remap_ref(&mut unit.kind, old, new), DefinitionBody::Calculation(calculation) => { for parameter in &mut calculation.parameters { remap_type(&mut parameter.value_type, old, new); } remap_type(&mut calculation.result, old, new); remap_binding(&mut calculation.body, old, new); } _ => {} } } }