in checker/src/path.rs [833:1006]
fn canonicalize(&self, environment: &Environment) -> Rc<Path> {
if let Some(val) = environment.value_at(self) {
// If self binds to value &p then self and path &p are equivalent paths.
// Since many paths can bind to &p and p is canonical (by construction of &p).
// we replace self with &p.
// This reasoning is extended to any expression that has type ExpressionType::ThinPointer.
// With one exception: we guard against the case where self may bind to old(self).
// This can happen when self is rooted in a parameter and has been accessed before
// being assigned to. After any assignment, self and old(self) are not aliases so
// they need to stay distinct, not least because otherwise the first assignment
// to self will actually update old(self).
let mut not_dummy = true;
if let Expression::InitialParameterValue { path, .. } = &val.expression {
if self.eq(path) {
not_dummy = false;
}
}
// If self binds to value &p then self and path &p are equivalent paths.
// Since self is derived from p, we use path &p as the canonical form.
// If we used self instead, then what would we do if we encounter another
// path that also binds to value &p?
if not_dummy && val.expression.infer_type() == ExpressionType::ThinPointer {
if matches!(&val.expression, Expression::Offset { .. }) {
return Path::get_as_path(val.clone());
}
return Path::new_computed(val.clone());
}
// If the environment contains a value for (key) self, self must be canonical.
// At any rate, it makes no sense to turn it into another path that likely
// does not lead to the known value.
return self.clone();
}
// If self is a qualified path, then recursive canonicalization of the qualifier may
// cause substitutions (as above) and that could result in a non canonical qualified path
// if *p becomes *&q. We thus need some additional logic to canonicalize the overall
// path once the qualifier has been canonicalized.
if let PathEnum::QualifiedPath {
qualifier,
selector,
..
} = &self.value
{
let canonical_qualifier = qualifier.canonicalize(environment);
// If the canonical qualifier binds to a special value, we may need to deconstruct that
// value before constructing the qualified path.
let qualifier_as_value = if let PathEnum::Computed { value }
| PathEnum::Offset { value } = &canonical_qualifier.value
{
Some(value)
} else {
environment.value_at(&canonical_qualifier)
};
if let Some(value) = qualifier_as_value {
match &value.expression {
Expression::InitialParameterValue { path, .. } => {
let ps = Path::new_qualified(path.clone(), selector.clone());
return if let Some(v) = environment.value_at(&ps) {
if let Expression::InitialParameterValue { path: p, .. } = &v.expression
{
if ps.eq(p) {
// The p.s is the key for old(p.s), so ps by itself is unambiguous.
return ps;
}
}
if matches!(path.value, PathEnum::Parameter { .. }) {
// old(p).s => p.s if there has been no assignment to p, which is
// the case because qualifier_as_value is a dummy value.
Path::new_qualified(path.clone(), selector.clone())
} else {
Path::new_qualified(
Path::new_computed(value.clone()),
selector.clone(),
)
}
} else {
// Since there is no entry for ps in the environment, looking up
// its value will create it (in a context where the type of p.s
// is known).
ps
};
}
Expression::Offset { left, right } if right.is_zero() => {
if let Expression::Reference(p) = &left.expression {
// *offset(&p, 0) becomes p
if **selector == PathSelector::Deref {
return p.clone();
}
}
// offset(p, 0) becomes p in a qualifier
let p = Path::get_as_path(value.clone());
return Path::new_qualified(p, selector.clone());
}
// *&p is equivalent to p and (&p).q is equivalent to p.q, etc.
Expression::Reference(p) => {
// *&p just becomes p
// (except when the value at p is structured and the result is assigned to a local,
// but such paths are never canonicalized).
if **selector == PathSelector::Deref {
return p.clone();
}
// since self is a qualified path we have to drop the reference operator
// since selectors implicitly dereference pointers.
return Path::new_qualified(p.clone(), selector.clone());
}
Expression::Variable { path, .. } => {
return Path::new_qualified(path.clone(), selector.clone());
}
_ => {
if **selector == PathSelector::Deref {
return Path::new_qualified(
Path::get_as_path(value.clone()),
selector.clone(),
);
}
}
}
}
// An impossible downcast is equivalent to BOTTOM
if let PathSelector::Downcast(_, variant) = selector.as_ref() {
let discriminator = Path::new_discriminant(canonical_qualifier.clone());
if let Some(val) = environment.value_at(&discriminator) {
if let Expression::CompileTimeConstant(ConstantDomain::U128(ordinal)) =
&val.expression
{
if (*variant as u128) != *ordinal {
// The downcast is impossible in this calling context
return Path::new_computed(Rc::new(abstract_value::BOTTOM));
}
}
}
}
if let PathSelector::Slice(count) = selector.as_ref() {
if let Expression::CompileTimeConstant(ConstantDomain::U128(count)) =
&count.expression
{
if let PathEnum::QualifiedPath {
qualifier,
selector,
..
} = &qualifier.value
{
if **selector == PathSelector::Deref {
if let PathEnum::Offset { value } = &qualifier.value {
if let Expression::Offset { left, right } = &value.expression {
if let Expression::CompileTimeConstant(ConstantDomain::I128(
from,
)) = &right.expression
{
let base_path = Path::new_deref(
Path::get_as_path(left.clone()),
ExpressionType::NonPrimitive,
);
let to = *from + (*count as i128);
let selector = Rc::new(PathSelector::ConstantSlice {
from: *from as u64,
to: to as u64,
from_end: false,
});
return Path::new_qualified(base_path, selector);
}
}
}
}
}
}
}
Path::new_qualified(canonical_qualifier, selector.clone())
} else {
self.clone()
}
}