tamer: asg::graph: Require name for non-imports

NOTE: This temporarily breaks `tameld`.  It is fixed in a future commit when
names are bound.  This was an oversight when breaking apart changes into
separate commits, because the linker does not yet have system tests like
tamec does.

This is preparing for a full transition to requiring a canonical package
name.  The previous `Unnamed` variant has been removed and `AirAggregate`
will provide a default `WS_EMPTY` name, as `Pkg` had done before.

The intent of this change is to allow for consulting the index before a
new `Pkg` object is created on the graph, but we're not quite ready for that
yet.

Well, that's not entirely true---the linker can be ready for that.  But the
compiler needs to canonicalize import paths relative to the active package
canonical name, which it can't even do yet because tamec isn't generating a
name.

So maybe the linker will be first; it's useful to have that in a separate
commit anyway to emphasize the change.

DEV-13162
main
Mike Gerwitz 2023-05-02 22:12:37 -04:00
parent 799f2c6d96
commit 670c5d3a5d
5 changed files with 122 additions and 91 deletions

View File

@ -44,7 +44,7 @@ use crate::{
diagnostic_todo,
parse::{prelude::*, StateStack},
span::{Span, UNKNOWN_SPAN},
sym::SymbolId,
sym::{st::raw::WS_EMPTY, SymbolId},
};
use std::fmt::{Debug, Display};
@ -67,6 +67,10 @@ pub enum AirAggregate {
#[default]
Empty,
/// Package definition or declaration started,
/// but the name is not yet known.
UnnamedPkg(Span),
/// Expecting a package-level token.
Toplevel(ObjectIndex<Pkg>),
@ -91,6 +95,9 @@ impl Display for AirAggregate {
match self {
Empty => write!(f, "awaiting AIR input for ASG"),
UnnamedPkg(_) => {
write!(f, "expecting canonical package name")
}
Toplevel(_) => {
write!(f, "expecting package header or an expression")
}
@ -143,12 +150,11 @@ impl ParseState for AirAggregate {
(st, AirTodo(Todo(_))) => Transition(st).incomplete(),
(Empty, AirPkg(PkgStart(span))) => {
let oi_pkg = ctx.begin_pkg(span);
Transition(Toplevel(oi_pkg)).incomplete()
Transition(UnnamedPkg(span)).incomplete()
}
(
st @ (Toplevel(_) | PkgExpr(_) | PkgTpl(_)),
st @ (UnnamedPkg(_) | Toplevel(_) | PkgExpr(_) | PkgTpl(_)),
AirPkg(PkgStart(span)),
) => {
// This should always be available in this context.
@ -158,19 +164,44 @@ impl ParseState for AirAggregate {
Transition(st).err(AsgError::NestedPkgStart(span, first_span))
}
// Packages are identified by canonical paths relative to the
// project root.
(UnnamedPkg(span), AirBind(BindIdent(name))) => {
match ctx.begin_pkg(span, name) {
Ok(oi_pkg) => Transition(Toplevel(oi_pkg)).incomplete(),
Err(e) => Transition(UnnamedPkg(span)).err(e),
}
}
// TODO: Remove; transitionary (no package name required)
(UnnamedPkg(span), tok) => {
match ctx.begin_pkg(span, SPair(WS_EMPTY, span)) {
Ok(oi_pkg) => Transition(Toplevel(oi_pkg))
.incomplete()
.with_lookahead(tok),
Err(e) => Transition(UnnamedPkg(span)).err(e),
}
}
(Toplevel(oi_pkg), AirBind(BindIdent(rename))) => {
// TODO: `unwrap_or` is just until canonical name is
// unconditionally available just to avoid the possibility
// of a panic.
let name = oi_pkg
.resolve(ctx.asg_mut())
.canonical_name()
.unwrap_or(rename);
Transition(Toplevel(oi_pkg))
.err(AsgError::PkgRename(name, rename))
}
// No expression was started.
(Toplevel(oi_pkg), AirPkg(PkgEnd(span))) => {
oi_pkg.close(ctx.asg_mut(), span);
Transition(Empty).incomplete()
}
// Packages are identified by canonical paths relative to the
// project root.
(Toplevel(oi_pkg), AirBind(BindIdent(name))) => oi_pkg
.assign_canonical_name(ctx.asg_mut(), name)
.map(|_| ())
.transition(Toplevel(oi_pkg)),
(Toplevel(oi_pkg), tok @ AirDoc(DocIndepClause(..))) => {
diagnostic_todo!(
vec![
@ -301,7 +332,7 @@ impl AirAggregate {
match self {
Empty => true,
Toplevel(_) => self.is_accepting(ctx),
UnnamedPkg(_) | Toplevel(_) => self.is_accepting(ctx),
PkgExpr(st) => st.is_accepting(ctx),
PkgTpl(st) => st.is_accepting(ctx),
}
@ -316,6 +347,7 @@ impl AirAggregate {
fn active_rooting_oi(&self) -> Option<ObjectIndexToTree<Ident>> {
match self {
AirAggregate::Empty => None,
AirAggregate::UnnamedPkg(_) => None,
AirAggregate::Toplevel(pkg_oi) => Some((*pkg_oi).into()),
// Expressions never serve as roots for identifiers;
@ -444,12 +476,18 @@ impl AirAggregateCtx {
}
/// Create a new rooted package and record it as the active package.
fn begin_pkg(&mut self, span: Span) -> ObjectIndex<Pkg> {
fn begin_pkg(
&mut self,
start: Span,
name: SPair,
) -> Result<ObjectIndex<Pkg>, AsgError> {
let Self(asg, _, pkg) = self;
let oi_pkg = asg.create(Pkg::new(span)).root(asg);
let oi_root = asg.root(start);
let oi_pkg = oi_root.create_pkg(asg, start, name)?;
pkg.replace(oi_pkg);
oi_pkg
Ok(oi_pkg)
}
/// The active package if any.
@ -488,7 +526,7 @@ impl AirAggregateCtx {
// unreachable.
// There should be no parent frame and so this will fail to find
// a value.
AirAggregate::Toplevel(_) => None,
AirAggregate::UnnamedPkg(_) | AirAggregate::Toplevel(_) => None,
// Expressions may always contain other expressions,
// and so this method should not be consulted in such a
@ -515,6 +553,7 @@ impl AirAggregateCtx {
stack.iter().rev().find_map(|st| match st {
AirAggregate::Empty => None,
AirAggregate::UnnamedPkg(_) => None,
AirAggregate::Toplevel(pkg_oi) => Some((*pkg_oi).into()),
AirAggregate::PkgExpr(exprst) => {
exprst.active_expr_oi().map(Into::into)

View File

@ -408,18 +408,24 @@ fn close_pkg_without_open() {
#[test]
fn nested_open_pkg() {
#[rustfmt::skip]
let toks = vec![
PkgStart(S1),
PkgStart(S2),
// RECOVERY
PkgEnd(S3),
BindIdent(SPair("foo".into(), S2)),
// Cannot nest package
PkgStart(S3),
// RECOVERY
PkgEnd(S4),
];
assert_eq!(
#[rustfmt::skip]
vec![
Ok(Incomplete), // PkgStart
Err(ParseError::StateError(AsgError::NestedPkgStart(S2, S1))),
// RECOVERY
Ok(Incomplete), // BindIdent
Err(ParseError::StateError(AsgError::NestedPkgStart(S3, S1))),
// RECOVERY
Ok(Incomplete), // PkgEnd
],
Sut::parse(toks.into_iter()).collect::<Vec<_>>(),

View File

@ -35,10 +35,11 @@ use super::ObjectKind;
pub struct Pkg(Span, PathSpec);
impl Pkg {
/// Create a new package intended to serve as the compilation unit,
/// with an empty pathspec.
pub fn new<S: Into<Span>>(span: S) -> Self {
Self(span.into(), PathSpec::Unnamed)
/// Create a new package with a canonicalized name.
///
/// A canonical package name is a path relative to the project root.
pub(super) fn new_canonical<S: Into<Span>>(start: S, name: SPair) -> Self {
Self(start.into(), PathSpec::Canonical(name))
}
/// Represent a package imported according to the provided
@ -53,26 +54,8 @@ impl Pkg {
}
}
/// Attempt to assign a canonical name to this package.
///
/// Only [`PathSpec::Unnamed`] packages may have a named assigned,
/// otherwise an [`AsgError::PkgRename`] [`Err`] will be returned.
pub fn assign_canonical_name(
self,
name: SPair,
) -> Result<Self, (Self, AsgError)> {
match self {
Self(span, PathSpec::Unnamed) => {
Ok(Self(span, PathSpec::Canonical(name)))
}
Self(_, PathSpec::Canonical(orig) | PathSpec::TodoImport(orig)) => {
Err((self, AsgError::PkgRename(orig, name)))
}
}
}
/// The canonical name for this package assigned by
/// [`Self::assign_canonical_name`],
/// [`Self::new_canonical`],
/// if any.
pub fn canonical_name(&self) -> Option<SPair> {
match self {
@ -112,9 +95,6 @@ impl Functor<Span> for Pkg {
#[derive(Debug, PartialEq, Eq)]
enum PathSpec {
/// Unnamed package.
Unnamed,
/// Canonical package name.
///
/// This is the name of the package relative to the project root.
@ -136,7 +116,6 @@ impl PathSpec {
use PathSpec::*;
match self {
Unnamed => None,
Canonical(spair) => Some(*spair),
TodoImport(_) => None,
}
@ -146,7 +125,6 @@ impl PathSpec {
use PathSpec::*;
match self {
Unnamed => None,
// TODO: Let's wait to see if we actually need this,
// since we'll need to allocate and intern a `/`-prefixed
// symbol.
@ -161,9 +139,6 @@ impl Display for PathSpec {
use PathSpec::*;
match self {
Unnamed => {
write!(f, "unnamed package")
}
Canonical(spair) => write!(f, "package {}", TtQuote::wrap(spair)),
TodoImport(spair) => write!(
f,
@ -200,30 +175,6 @@ impl ObjectIndex<Pkg> {
self.map_obj(asg, Pkg::fmap(|open| open.merge(span).unwrap_or(open)))
}
/// Attempt to assign a canonical name to this package.
///
/// 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))
}
/// Indicate that a package should be imported at the provided
/// pathspec.
///

View File

@ -23,6 +23,7 @@ use super::{prelude::*, Ident, Pkg};
use crate::{
asg::{IdentKind, Source},
parse::util::SPair,
span::Span,
};
use std::fmt::Display;
@ -95,4 +96,31 @@ impl ObjectIndex<Root> {
self.lookup_or_missing(asg, name)
.declare(asg, name, kind, src)
}
/// Attempt to declare a package with the given canonical name.
///
/// A canonical package name is a path relative to the project root.
///
/// 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 create_pkg(
self,
asg: &mut Asg,
start: Span,
name: SPair,
) -> Result<ObjectIndex<Pkg>, AsgError> {
let oi_pkg = asg.create(Pkg::new_canonical(start, name));
asg.try_index(self, name, oi_pkg).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)
})?;
Ok(oi_pkg.root(asg))
}
}

View File

@ -301,25 +301,32 @@ fn omits_unreachable() {
// roots since that is the entry point for this API.
#[test]
fn sorts_objects_given_multiple_roots() {
let id_a = SPair("expr_a".into(), S3);
let id_b = SPair("expr_b".into(), S8);
let pkg_a_name = SPair("pkg/a".into(), S2);
let pkg_b_name = SPair("pkg/b".into(), S8);
let id_a = SPair("expr_a".into(), S4);
let id_b = SPair("expr_b".into(), S10);
#[rustfmt::skip]
let toks = vec![
// First root
PkgStart(S1),
ExprStart(ExprOp::Sum, S2),
BindIdent(pkg_a_name),
ExprStart(ExprOp::Sum, S3),
BindIdent(id_a),
ExprEnd(S4),
PkgEnd(S5),
ExprEnd(S5),
PkgEnd(S6),
// Second root,
// independent of the first.
PkgStart(S6),
ExprStart(ExprOp::Sum, S7),
PkgStart(S7),
BindIdent(pkg_b_name),
ExprStart(ExprOp::Sum, S9),
BindIdent(id_b),
ExprEnd(S9),
PkgEnd(S10),
ExprEnd(S11),
PkgEnd(S12),
];
use ObjectTy::*;
@ -329,16 +336,16 @@ fn sorts_objects_given_multiple_roots() {
assert_eq!(
Ok(vec![
// First root.
(Expr, m(S2, S4) ),
(Ident, S3),
(Pkg, m(S1, S5) ),
(Expr, m(S3, S5) ),
(Ident, S4),
(Pkg, m(S1, S6) ),
// Second root,
// but the fact that it is emitted after the first is not
// behavior that should be relied upon.
(Expr, m(S7, S9) ),
(Ident, S8),
(Pkg, m(S6, S10)),
(Expr, m(S9, S11) ),
(Ident, S10),
(Pkg, m(S7, S12)),
]),
topo_report::<object::Pkg, _>(toks).into_iter().collect(),
);