tamer: asg::graph: Index Root->Pkg with canonical names

The previous commit introduced canonical names, and this uses them to index.

The next step will be to utilize those names to look up packages on
definition rather than creating a new package node, so that references to
yet-to-be-defined (or yet-to-be-imported) packages can be resolved on the
graph.

DEV-13162
main
Mike Gerwitz 2023-05-02 16:07:25 -04:00
parent 92c9c9ba2f
commit 7cfe6a6f8d
4 changed files with 131 additions and 10 deletions

View File

@ -442,13 +442,75 @@ fn pkg_canonical_name() {
let asg = sut.finalize().unwrap().into_context();
let pkg = asg
.root(S1)
let oi_root = asg.root(S1);
let oi_pkg = oi_root
.edges_filtered::<Pkg>(&asg)
.next()
.expect("cannot find package from root");
assert_eq!(Some(name), pkg.resolve(&asg).canonical_name());
assert_eq!(Some(name), oi_pkg.resolve(&asg).canonical_name());
// We should be able to find the same package by its index.
let oi_pkg_indexed = asg.lookup(oi_root, name);
assert_eq!(
Some(oi_pkg),
oi_pkg_indexed,
"package was not indexed at Root"
);
}
// This isn't supposed to happen in practice,
// especially with normal usage of TAME where names are generated from
// filenames.
#[test]
fn pkg_cannot_redeclare() {
let name = SPair("foo/bar".into(), S2);
let name2 = SPair("foo/bar".into(), S5);
let namefix = SPair("foo/fix".into(), S6);
#[rustfmt::skip]
let toks = vec![
PkgStart(S1),
BindIdent(name),
PkgEnd(S3),
PkgStart(S4),
// Attempt to define a package of the same name.
BindIdent(name2),
// RECOVERY: Use a proper name.
BindIdent(namefix),
PkgEnd(S7),
];
let mut sut = Sut::parse(toks.into_iter());
assert_eq!(
#[rustfmt::skip]
vec![
Ok(Incomplete), // PkgStart
Ok(Incomplete), // BindIdent
Ok(Incomplete), // PkgEnd
Ok(Incomplete), // PkgStart
Err(ParseError::StateError(
AsgError::PkgRedeclare(name, name2)
)),
// RECOVERY: Ignore the attempted name
Ok(Incomplete), // BindIdent
Ok(Incomplete), // PkgEnd
],
sut.by_ref().collect::<Vec<_>>(),
);
let asg = sut.finalize().unwrap().into_context();
// The second package should be available under the recovery name.
let oi_root = asg.root(S1);
let oi_pkg = asg
.lookup::<Pkg>(oi_root, namefix)
.expect("failed to locate package by its recovery name");
assert_eq!(S4.merge(S7).unwrap(), oi_pkg.resolve(&asg).span());
}
#[test]
@ -463,9 +525,11 @@ fn pkg_cannot_rename() {
// Attempt to provide a name a second time.
BindIdent(name2),
// RECOVERY: Just ignore it.
PkgEnd(S3),
PkgEnd(S4),
];
let mut sut = Sut::parse(toks.into_iter());
assert_eq!(
vec![
Ok(Incomplete), // PkgStart
@ -474,8 +538,17 @@ fn pkg_cannot_rename() {
// RECOVERY: Ignore the attempted rename
Ok(Incomplete), // PkgEnd
],
Sut::parse(toks.into_iter()).collect::<Vec<_>>(),
sut.by_ref().collect::<Vec<_>>(),
);
let asg = sut.finalize().unwrap().into_context();
// The original name should have been kept.
let oi_root = asg.root(S1);
let oi_pkg = asg
.lookup::<Pkg>(oi_root, name)
.expect("failed to locate package by its original name");
assert_eq!(S1.merge(S4).unwrap(), oi_pkg.resolve(&asg).span());
}
#[test]

View File

@ -61,6 +61,12 @@ pub enum AsgError {
/// whereas _declaring_ an identifier provides metadata about it.
IdentRedefine(SPair, Span),
/// A package of this same name has already been defined.
///
/// The [`SPair`]s represent the original and redefinition names
/// respectively.
PkgRedeclare(SPair, SPair),
/// Attempted to rename a package from the first [`SPair`] to the
/// second.
///
@ -155,6 +161,11 @@ impl Display for AsgError {
IdentRedefine(spair, _) => {
write!(f, "cannot redefine {}", TtQuote::wrap(spair))
}
PkgRedeclare(orig, _) => write!(
f,
"attempted to redeclare or redefine package {}",
TtQuote::wrap(orig),
),
PkgRename(from, to) => write!(
f,
"attempted to rename package {} to {}",
@ -236,6 +247,11 @@ impl Diagnostic for AsgError {
.help(" defined and its definition cannot be changed."),
],
PkgRedeclare(orig, redef) => vec![
orig.note("package originally declared here"),
redef.error("attempting to redeclare or redefine package here"),
],
PkgRename(from, to) => vec![
from.note("package was originally named here"),
to.error("attempted to rename package here"),

View File

@ -190,6 +190,27 @@ impl Asg {
self.graph.node_count()
}
pub(super) fn try_index<
O: ObjectRelatable,
OS: ObjectIndexRelTo<O>,
S: Into<SymbolId>,
>(
&mut self,
imm_env: OS,
name: S,
oi: ObjectIndex<O>,
) -> Result<(), ObjectIndex<O>> {
let sym = name.into();
let prev = self
.index
.insert((O::rel_ty(), sym, imm_env.widen()), oi.widen());
match prev {
None => Ok(()),
Some(oi) => Err(oi.must_narrow_into::<O>()),
}
}
/// Index the provided symbol `name` as representing the
/// [`ObjectIndex`] in the immediate environment `imm_env`.
///
@ -216,14 +237,12 @@ impl Asg {
oi: ObjectIndex<O>,
) {
let sym = name.into();
let prev = self
.index
.insert((O::rel_ty(), sym, imm_env.widen()), oi.widen());
let prev = self.try_index(imm_env, sym, oi);
// We should never overwrite indexes
#[allow(unused_variables)] // used only for debug
#[allow(unused_imports)]
if let Some(prev_oi) = prev {
if let Err(prev_oi) = prev {
use crate::fmt::{DisplayWrapper, TtQuote};
crate::debug_diagnostic_panic!(
vec![

View File

@ -202,12 +202,25 @@ impl ObjectIndex<Pkg> {
/// Attempt to assign a canonical name to this package.
///
/// This assignment will fail if the package already has a name.
/// This assignment will fail if the package either already has a name
/// or if a package of the same name has already been declared.
pub fn assign_canonical_name(
self,
asg: &mut Asg,
name: SPair,
) -> Result<Self, AsgError> {
let oi_root = asg.root(name);
asg.try_index(oi_root, name, self).map_err(|oi_prev| {
let prev = oi_prev.resolve(asg);
// unwrap note: a canonical name must exist for this error to
// have been thrown,
// but this will at least not blow up if something really
// odd happens.
AsgError::PkgRedeclare(prev.canonical_name().unwrap_or(name), name)
})?;
self.try_map_obj(asg, |pkg| pkg.assign_canonical_name(name))
}