tamer: asg: Integrate package CanonicalName
This change requires every package to have a canonical name, and performs namespec canonicalization on imports. Since all package names are canonicalized, this opens the door to being able to index package names at import, allowing the object to be shared on the graph and properly reference a package after it has been resolved. Note that the system tests' canonicalization is relative to the hard-coded `/TODO` presently; that will change in the near future once `tamec` generates names from the provided path. DEV-13162main
parent
a9d0f43684
commit
48bcb0cdab
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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<TransitionError> for AsgError {
|
||||
fn from(err: TransitionError) -> Self {
|
||||
Self::IdentTransition(err)
|
||||
fn from(e: TransitionError) -> Self {
|
||||
Self::IdentTransition(e)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<CanonicalNameError> 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"),
|
||||
|
|
|
@ -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<S: Into<Span>>(start: S, name: SPair) -> Self {
|
||||
Self(start.into(), PathSpec::Canonical(name))
|
||||
pub(super) fn new_canonical<S: Into<Span>>(
|
||||
start: S,
|
||||
name: SPair,
|
||||
) -> Result<Self, AsgError> {
|
||||
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<Self, AsgError> {
|
||||
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<SPair> {
|
||||
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<SPair> {
|
||||
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<Span> 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<SPair> {
|
||||
use PathSpec::*;
|
||||
|
||||
match self {
|
||||
Canonical(spair) => Some(*spair),
|
||||
TodoImport(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn import_path(&self) -> Option<SPair> {
|
||||
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<Pkg> {
|
|||
///
|
||||
/// 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<Self, AsgError> {
|
||||
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.
|
||||
|
|
|
@ -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<CanonicalName> for SPair {
|
||||
fn from(value: CanonicalName) -> Self {
|
||||
match value {
|
||||
|
|
|
@ -109,7 +109,7 @@ impl ObjectIndex<Root> {
|
|||
start: Span,
|
||||
name: SPair,
|
||||
) -> Result<ObjectIndex<Pkg>, 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<Root> {
|
|||
// 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))
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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<Xirf> {
|
||||
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(
|
||||
|
|
|
@ -5,8 +5,8 @@
|
|||
|
||||
|
||||
|
||||
<import package="first" />
|
||||
<import package="second" />
|
||||
<import package="third" />
|
||||
<import package="/first/pkg" />
|
||||
<import package="/second" />
|
||||
<import package="/third" />
|
||||
</package>
|
||||
|
||||
|
|
|
@ -5,7 +5,9 @@
|
|||
|
||||
Packages aren't actually imported yet,
|
||||
but they do need to be represented on the graph for `xmli` derivation.
|
||||
<import package="first" />
|
||||
Import paths will be canonicalized.
|
||||
|
||||
<import package="first/pkg" />
|
||||
<import package="second" />
|
||||
<import package="third" />
|
||||
</package>
|
||||
|
|
Loading…
Reference in New Issue