diff --git a/tamer/src/asg/air.rs b/tamer/src/asg/air.rs index 9a9cc127..848c5faf 100644 --- a/tamer/src/asg/air.rs +++ b/tamer/src/asg/air.rs @@ -44,7 +44,7 @@ use crate::{ diagnostic_todo, parse::{prelude::*, StateStack}, span::{Span, UNKNOWN_SPAN}, - sym::{st::raw::WS_EMPTY, SymbolId}, + sym::SymbolId, }; use std::fmt::{Debug, Display}; @@ -175,7 +175,7 @@ impl ParseState for AirAggregate { // TODO: Remove; transitionary (no package name required) (UnnamedPkg(span), tok) => { - match ctx.begin_pkg(span, SPair(WS_EMPTY, span)) { + match ctx.begin_pkg(span, SPair("/TODO".into(), span)) { Ok(oi_pkg) => Transition(Toplevel(oi_pkg)) .incomplete() .with_lookahead(tok), @@ -187,10 +187,7 @@ impl ParseState for AirAggregate { // 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); + let name = oi_pkg.resolve(ctx.asg_mut()).canonical_name(); Transition(Toplevel(oi_pkg)) .err(AsgError::PkgRename(name, rename)) @@ -220,10 +217,10 @@ impl ParseState for AirAggregate { } // Package import - (Toplevel(oi_pkg), AirBind(RefIdent(pathspec))) => { - oi_pkg.import(ctx.asg_mut(), pathspec); - Transition(Toplevel(oi_pkg)).incomplete() - } + (Toplevel(oi_pkg), AirBind(RefIdent(pathspec))) => oi_pkg + .import(ctx.asg_mut(), pathspec) + .map(|_| ()) + .transition(Toplevel(oi_pkg)), // Note: We unfortunately can't match on `AirExpr | AirBind | ...` // and delegate in the same block diff --git a/tamer/src/asg/air/test.rs b/tamer/src/asg/air/test.rs index a883d979..697bab26 100644 --- a/tamer/src/asg/air/test.rs +++ b/tamer/src/asg/air/test.rs @@ -411,7 +411,7 @@ fn nested_open_pkg() { #[rustfmt::skip] let toks = vec![ PkgStart(S1), - BindIdent(SPair("foo".into(), S2)), + BindIdent(SPair("/foo".into(), S2)), // Cannot nest package PkgStart(S3), @@ -434,7 +434,7 @@ fn nested_open_pkg() { #[test] fn pkg_canonical_name() { - let name = SPair("foo/bar".into(), S2); + let name = SPair("/foo/bar".into(), S2); #[rustfmt::skip] let toks = vec![ @@ -454,7 +454,7 @@ fn pkg_canonical_name() { .next() .expect("cannot find package from root"); - assert_eq!(Some(name), oi_pkg.resolve(&asg).canonical_name()); + assert_eq!(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); @@ -470,9 +470,9 @@ fn pkg_canonical_name() { // 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); + 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![ @@ -521,8 +521,8 @@ fn pkg_cannot_redeclare() { #[test] fn pkg_cannot_rename() { - let name = SPair("foo/bar".into(), S2); - let name2 = SPair("bad/rename".into(), S3); + let name = SPair("/foo/bar".into(), S2); + let name2 = SPair("/bad/rename".into(), S3); #[rustfmt::skip] let toks = vec![ @@ -558,13 +558,15 @@ fn pkg_cannot_rename() { } #[test] -fn pkg_import() { - let pathspec = SPair("foo/bar".into(), S2); +fn pkg_import_canonicalized_against_current_pkg() { + let pkg_name = SPair("/foo/bar".into(), S2); + let pkg_rel = SPair("baz/quux".into(), S3); #[rustfmt::skip] let toks = vec![ PkgStart(S1), - RefIdent(pathspec), + BindIdent(pkg_name), + RefIdent(pkg_rel), PkgEnd(S3), ]; @@ -583,7 +585,8 @@ fn pkg_import() { .expect("cannot find imported package") .resolve(&asg); - assert_eq!(Some(pathspec), import.import_path()); + // TODO + assert_eq!(SPair("/foo/baz/quux".into(), S3), import.canonical_name()); } // Documentation can be mixed in with objects in a literate style. diff --git a/tamer/src/asg/error.rs b/tamer/src/asg/error.rs index 57dd1d04..e0a79a7a 100644 --- a/tamer/src/asg/error.rs +++ b/tamer/src/asg/error.rs @@ -31,7 +31,9 @@ use crate::{ span::Span, }; -use super::{visit::Cycle, TransitionError}; +use super::{ + graph::object::pkg::CanonicalNameError, visit::Cycle, TransitionError, +}; /// An error from an ASG operation. /// @@ -61,6 +63,9 @@ pub enum AsgError { /// whereas _declaring_ an identifier provides metadata about it. IdentRedefine(SPair, Span), + /// Error while processing the canonical name for a package. + PkgCanonicalName(CanonicalNameError), + /// A package of this same name has already been defined. /// /// The [`SPair`]s represent the original and redefinition names @@ -157,10 +162,11 @@ impl Display for AsgError { use AsgError::*; match self { - IdentTransition(err) => Display::fmt(&err, f), + IdentTransition(e) => Display::fmt(&e, f), IdentRedefine(spair, _) => { write!(f, "cannot redefine {}", TtQuote::wrap(spair)) } + PkgCanonicalName(e) => Display::fmt(&e, f), PkgRedeclare(orig, _) => write!( f, "attempted to redeclare or redefine package {}", @@ -213,8 +219,14 @@ impl Display for AsgError { impl Error for AsgError {} impl From for AsgError { - fn from(err: TransitionError) -> Self { - Self::IdentTransition(err) + fn from(e: TransitionError) -> Self { + Self::IdentTransition(e) + } +} + +impl From for AsgError { + fn from(e: CanonicalNameError) -> Self { + Self::PkgCanonicalName(e) } } @@ -247,6 +259,8 @@ impl Diagnostic for AsgError { .help(" defined and its definition cannot be changed."), ], + PkgCanonicalName(e) => e.describe(), + PkgRedeclare(orig, redef) => vec![ orig.note("package originally declared here"), redef.error("attempting to redeclare or redefine package here"), diff --git a/tamer/src/asg/graph/object/pkg.rs b/tamer/src/asg/graph/object/pkg.rs index d1c63c64..6288b23c 100644 --- a/tamer/src/asg/graph/object/pkg.rs +++ b/tamer/src/asg/graph/object/pkg.rs @@ -19,7 +19,7 @@ //! Package object on the ASG. -use super::{prelude::*, Doc, Ident, NameableMissingObject, Tpl}; +use super::{prelude::*, Doc, Ident, Tpl}; use crate::{ f::Functor, fmt::{DisplayWrapper, TtQuote}, @@ -33,21 +33,34 @@ use super::ObjectKind; mod name; +pub use name::{CanonicalName, CanonicalNameError}; + #[derive(Debug, PartialEq, Eq)] -pub struct Pkg(Span, PathSpec); +pub struct Pkg(Span, CanonicalName); impl Pkg { /// 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>(start: S, name: SPair) -> Self { - Self(start.into(), PathSpec::Canonical(name)) + pub(super) fn new_canonical>( + start: S, + name: SPair, + ) -> Result { + Ok(Self(start.into(), CanonicalName::from_canonical(name)?)) } - /// Represent a package imported according to the provided - /// [`PathSpec`]. - pub fn new_imported(pathspec: SPair) -> Self { - Self(pathspec.span(), PathSpec::TodoImport(pathspec)) + /// Import a package with a namespec canonicalized against a reference + /// parent package. + /// + /// The parent package should be the package containing the namespec. + pub fn new_imported( + Pkg(_, parent_name): &Pkg, + namespec: SPair, + ) -> Result { + Ok(Self( + namespec.span(), + CanonicalName::resolve_namespec_rel(parent_name, namespec)?, + )) } pub fn span(&self) -> Span { @@ -59,31 +72,18 @@ impl Pkg { /// The canonical name for this package assigned by /// [`Self::new_canonical`], /// if any. - pub fn canonical_name(&self) -> Option { + pub fn canonical_name(&self) -> SPair { match self { - Self(_, pathspec) => pathspec.canonical_name(), - } - } - - /// The import path to this package as provided by - /// [`Self::new_imported`], - /// if any. - pub fn import_path(&self) -> Option { - match self { - Self(_, pathspec) => pathspec.import_path(), + Self(_, name) => (*name).into(), } } } impl Display for Pkg { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(f, "package") - } -} - -impl NameableMissingObject for Pkg { - fn missing(pathspec: SPair) -> Self { - Self::new_imported(pathspec) + match self { + Self(_, name) => write!(f, "package {}", TtQuote::wrap(name)), + } } } @@ -95,62 +95,6 @@ impl Functor for Pkg { } } -#[derive(Debug, PartialEq, Eq)] -enum PathSpec { - /// Canonical package name. - /// - /// This is the name of the package relative to the project root. - /// This is like the module name after `crate::` in Rust, - /// but with `/` package separators in place of `::`. - Canonical(SPair), - - /// Import path relative to the current package - /// (which is likely the compilation unit). - /// - /// TODO: This will be replaced with [`Self::Canonical`] once that is - /// working and relative paths can be resolved against the active - /// package. - TodoImport(SPair), -} - -impl PathSpec { - fn canonical_name(&self) -> Option { - use PathSpec::*; - - match self { - Canonical(spair) => Some(*spair), - TodoImport(_) => None, - } - } - - fn import_path(&self) -> Option { - use PathSpec::*; - - match self { - // TODO: Let's wait to see if we actually need this, - // since we'll need to allocate and intern a `/`-prefixed - // symbol. - Canonical(_) => None, - TodoImport(spair) => Some(*spair), - } - } -} - -impl Display for PathSpec { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - use PathSpec::*; - - match self { - Canonical(spair) => write!(f, "package {}", TtQuote::wrap(spair)), - TodoImport(spair) => write!( - f, - "package import {} relative to compilation unit", - TtQuote::wrap(spair) - ), - } - } -} - object_rel! { /// Packages serve as a root for all identifiers defined therein, /// and so an edge to [`Ident`] will never be a cross edge. @@ -182,9 +126,15 @@ impl ObjectIndex { /// /// This simply adds the import to the graph; /// package loading must be performed by another subsystem. - pub fn import(self, asg: &mut Asg, pathspec: SPair) -> Self { - let oi_import = asg.create(Pkg::new_imported(pathspec)); - self.add_edge_to(asg, oi_import, Some(pathspec.span())) + pub fn import( + self, + asg: &mut Asg, + namespec: SPair, + ) -> Result { + let parent = self.resolve(asg); + let oi_import = asg.create(Pkg::new_imported(parent, namespec)?); + + Ok(self.add_edge_to(asg, oi_import, Some(namespec.span()))) } /// Arbitrary text serving as documentation in a literate style. diff --git a/tamer/src/asg/graph/object/pkg/name.rs b/tamer/src/asg/graph/object/pkg/name.rs index 0fe42ca8..4c75f8ef 100644 --- a/tamer/src/asg/graph/object/pkg/name.rs +++ b/tamer/src/asg/graph/object/pkg/name.rs @@ -325,6 +325,14 @@ fn validate_components( .map(|_| name) } +impl Display for CanonicalName { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + Self(name) => Display::fmt(name, f), + } + } +} + impl From for SPair { fn from(value: CanonicalName) -> Self { match value { diff --git a/tamer/src/asg/graph/object/root.rs b/tamer/src/asg/graph/object/root.rs index 47b94da1..7372dc8a 100644 --- a/tamer/src/asg/graph/object/root.rs +++ b/tamer/src/asg/graph/object/root.rs @@ -109,7 +109,7 @@ impl ObjectIndex { start: Span, name: SPair, ) -> Result, AsgError> { - let oi_pkg = asg.create(Pkg::new_canonical(start, name)); + 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); @@ -118,7 +118,7 @@ impl ObjectIndex { // 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) + AsgError::PkgRedeclare(prev.canonical_name(), name) })?; Ok(oi_pkg.root(asg)) diff --git a/tamer/src/asg/graph/visit/topo/test.rs b/tamer/src/asg/graph/visit/topo/test.rs index 6850b7e9..9259cc56 100644 --- a/tamer/src/asg/graph/visit/topo/test.rs +++ b/tamer/src/asg/graph/visit/topo/test.rs @@ -301,8 +301,8 @@ fn omits_unreachable() { // roots since that is the entry point for this API. #[test] fn sorts_objects_given_multiple_roots() { - let pkg_a_name = SPair("pkg/a".into(), S2); - let pkg_b_name = SPair("pkg/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); diff --git a/tamer/src/asg/graph/xmli.rs b/tamer/src/asg/graph/xmli.rs index d6fee977..f9a82e82 100644 --- a/tamer/src/asg/graph/xmli.rs +++ b/tamer/src/asg/graph/xmli.rs @@ -235,8 +235,10 @@ impl<'a> TreeContext<'a> { } /// Emit a package import statement. + /// + /// The import will have its path canonicalized. fn emit_import(&mut self, pkg: &Pkg, depth: Depth) -> Option { - let ps = pkg.import_path()?; + let ps = pkg.canonical_name(); self.push(Xirf::attr(QN_PACKAGE, ps.symbol(), (ps.span(), ps.span()))); Some(Xirf::open( diff --git a/tamer/tests/xmli/package/expected.xml b/tamer/tests/xmli/package/expected.xml index a645254c..c28a5129 100644 --- a/tamer/tests/xmli/package/expected.xml +++ b/tamer/tests/xmli/package/expected.xml @@ -5,8 +5,8 @@ - - - + + + diff --git a/tamer/tests/xmli/package/src.xml b/tamer/tests/xmli/package/src.xml index 2a5c0b0c..52835ef0 100644 --- a/tamer/tests/xmli/package/src.xml +++ b/tamer/tests/xmli/package/src.xml @@ -5,7 +5,9 @@ Packages aren't actually imported yet, but they do need to be represented on the graph for `xmli` derivation. - + Import paths will be canonicalized. + +