tamer: asg::air::ir::AirPkg::PkgImport: New token

This allows us to drop `AirIdent::IdentRef`, which in turn allows dropping
`AirIdent` entirely from `AirPkgAggregate`.

This is also a more appropriate abstraction; having to track all the ways in
which `IdentRef` was used can be confusing.  This means that `AirIdent` is
true to its name---used only for identifiers.  The new token type makes it
very clear where package imports are recognized, and it's also easier to
search for.

DEV-13162
main
Mike Gerwitz 2023-05-30 16:10:28 -04:00
parent 1f2315436c
commit 896fb3a0e5
8 changed files with 83 additions and 57 deletions

View File

@ -202,8 +202,10 @@ impl ParseState for AirAggregate {
tok @ AirPkg(..),
) => ctx.ret_or_transfer(st, tok, AirPkgAggregate::new()),
(Pkg(pkg), AirPkg(etok)) => ctx.proxy(pkg, etok),
(Pkg(pkg), AirBind(etok)) => ctx.proxy(pkg, etok),
(Pkg(pkg), AirDoc(etok)) => ctx.proxy(pkg, etok),
(st @ Pkg(..), tok @ AirBind(_)) => {
ctx.try_ret_with_lookahead(st, tok)
}
// Expression
(st @ (Pkg(_) | PkgTpl(_)), tok @ AirExpr(..)) => {

View File

@ -482,6 +482,19 @@ sum_ir! {
display: |f| write!(f, "open package"),
},
/// Import a package identified by the provided namespec.
///
/// This is similar to [`AirBind::RefIdent`],
/// except that this is used for package references whereas
/// the latter is used for identifiers.
/// Having a token to uniquely represent imports allows package
/// parsers to omit support for [`AirBind`] entirely rather
/// than supporting that subset only to use the one token.
PkgImport(namespec: SPair) => {
span: namespec,
display: |f| write!(f, "import package {}", TtQuote::wrap(namespec)),
},
/// Complete processing of the current package.
PkgEnd(span: Span) => {
span: span,
@ -827,13 +840,9 @@ sum_ir! {
}
}
/// Package definitions.
///
/// It is assumed that tokens that may appear as the body of a package,
/// with the exception of [`AirIdent`],
/// will preempt the package parser,
/// and so are not included here.
pub sum enum AirBindablePkg = AirPkg | AirBind | AirDoc;
/// Package definitions interspersed with documentation in a
/// literate style.
pub sum enum AirLiteratePkg = AirPkg | AirDoc;
/// Expressions that are able to be bound to identifiers.
///
@ -848,6 +857,17 @@ sum_ir! {
pub sum enum AirBindableMeta = AirMeta | AirBind;
}
impl AirBind {
/// Name of the identifier described by this token.
pub fn name(&self) -> SPair {
use AirBind::*;
match self {
BindIdent(name) | RefIdent(name) => *name,
}
}
}
impl AirIdent {
/// Name of the identifier described by this token.
pub fn name(&self) -> SPair {

View File

@ -23,7 +23,7 @@
use super::{
super::{graph::object::Pkg, AsgError, ObjectIndex},
ir::AirBindablePkg,
ir::AirLiteratePkg,
AirAggregate, AirAggregateCtx,
};
use crate::{diagnose::Annotate, diagnostic_todo, parse::prelude::*};
@ -59,7 +59,7 @@ impl Display for AirPkgAggregate {
}
impl ParseState for AirPkgAggregate {
type Token = AirBindablePkg;
type Token = AirLiteratePkg;
type Object = ();
type Error = AsgError;
type Context = AirAggregateCtx;
@ -70,8 +70,8 @@ impl ParseState for AirPkgAggregate {
tok: Self::Token,
ctx: &mut Self::Context,
) -> crate::parse::TransitionResult<Self::Super> {
use super::ir::{AirBind::*, AirDoc::*, AirPkg::*};
use AirBindablePkg::*;
use super::ir::{AirDoc::*, AirPkg::*};
use AirLiteratePkg::*;
use AirPkgAggregate::*;
match (self, tok) {
@ -94,11 +94,6 @@ impl ParseState for AirPkgAggregate {
}
}
(Toplevel(oi_pkg), AirBind(BindIdent(name))) => {
Transition(Toplevel(oi_pkg))
.err(AsgError::InvalidBindContext(name))
}
(Toplevel(oi_pkg), AirPkg(PkgEnd(span))) => {
oi_pkg.close(ctx.asg_mut(), span);
ctx.pkg_clear();
@ -123,19 +118,21 @@ impl ParseState for AirPkgAggregate {
}
// Package import
(Toplevel(oi_pkg), AirBind(RefIdent(pathspec))) => oi_pkg
.import(ctx.asg_mut(), pathspec)
(Toplevel(oi_pkg), AirPkg(PkgImport(namespec))) => oi_pkg
.import(ctx.asg_mut(), namespec)
.map(|_| ())
.transition(Toplevel(oi_pkg)),
(Ready, AirPkg(PkgImport(namespec))) => {
Transition(Ready).err(AsgError::InvalidPkgImport(namespec))
}
(Ready, AirPkg(PkgEnd(span))) => {
Transition(Ready).err(AsgError::InvalidPkgEndContext(span))
}
// Token may refer to a parent context.
(st @ Ready, tok @ (AirBind(..) | AirDoc(..))) => {
Transition(st).dead(tok)
}
(st @ Ready, tok @ AirDoc(..)) => Transition(st).dead(tok),
}
}

View File

@ -525,36 +525,6 @@ fn pkg_cannot_redeclare() {
assert_eq!(S6.merge(S8).unwrap(), oi_pkg.resolve(ctx.asg_ref()).span());
}
#[test]
fn pkg_cannot_rename() {
let pkg_name = SPair("/foo/bar".into(), S1);
let name = SPair("baz".into(), S2);
#[rustfmt::skip]
let toks = vec![
PkgStart(S1, pkg_name),
BindIdent(name),
PkgEnd(S3),
];
let sut = Sut::parse(toks.into_iter());
assert_eq!(
#[rustfmt::skip]
vec![
Ok(Incomplete), // PkgStart
Err(ParseError::StateError(
AsgError::InvalidBindContext(name),
)),
// RECOVERY
Ok(Incomplete), // PkgEnd
],
sut.collect::<Vec<_>>(),
);
}
#[test]
fn pkg_import_canonicalized_against_current_pkg() {
let pkg_name = SPair("/foo/bar".into(), S2);
@ -563,7 +533,7 @@ fn pkg_import_canonicalized_against_current_pkg() {
#[rustfmt::skip]
let toks = vec![
PkgStart(S1, pkg_name),
RefIdent(pkg_rel),
PkgImport(pkg_rel),
PkgEnd(S3),
];
@ -599,7 +569,7 @@ fn pkg_doc() {
// Some object to place in-between the two
// documentation blocks.
RefIdent(id_import),
PkgImport(id_import),
DocText(doc_b),
];

View File

@ -87,6 +87,9 @@ pub enum AsgError {
/// Attempted to open an expression in an invalid context.
PkgExpected(Span),
/// Requested package import in a non-package context.
InvalidPkgImport(SPair),
/// An expresion is not reachable by any other expression or
/// identifier.
///
@ -184,6 +187,11 @@ impl Display for AsgError {
write!(f, "invalid context for package close",)
}
PkgExpected(_) => write!(f, "expected package definition"),
InvalidPkgImport(namespec) => write!(
f,
"unexpected package import {}",
TtQuote::wrap(namespec)
),
DanglingExpr(_) => write!(
f,
"dangling expression (anonymous expression has no parent)"
@ -300,6 +308,15 @@ impl Diagnostic for AsgError {
vec![span.error("a package definition was expected here")]
}
InvalidPkgImport(namespec) => {
vec![
namespec.error("this package cannot be imported here"),
namespec.help(
"imports must appear in the context of a package",
),
]
}
DanglingExpr(span) => vec![
span.error(
"this expression is unreachable and its value \

View File

@ -145,6 +145,9 @@ pub enum Nir {
/// metavariable definition.
Text(SPair),
/// Import a package identified by the provided namespec.
Import(SPair),
/// "No-op" (no operation) that does nothing.
///
/// Since this is taking user input and effectively discarding it,
@ -178,7 +181,7 @@ impl Nir {
Open(_, _) | Close(_, _) => None,
BindIdent(spair) | RefSubject(spair) | Ref(spair) | Desc(spair)
| Text(spair) => Some(spair.symbol()),
| Text(spair) | Import(spair) => Some(spair.symbol()),
Noop(_) => None,
}
@ -214,6 +217,7 @@ impl Functor<SymbolId> for Nir {
Ref(spair) => Ref(spair.map(f)),
Desc(spair) => Desc(spair.map(f)),
Text(spair) => Text(spair.map(f)),
Import(spair) => Import(spair.map(f)),
Noop(_) => self,
}
@ -336,7 +340,7 @@ impl Token for Nir {
Close(_, span) => *span,
BindIdent(spair) | RefSubject(spair) | Ref(spair) | Desc(spair)
| Text(spair) => spair.span(),
| Text(spair) | Import(spair) => spair.span(),
// A no-op is discarding user input,
// so we still want to know where that is so that we can
@ -374,6 +378,12 @@ impl Display for Nir {
// output.
Text(_) => write!(f, "text"),
Import(namespec) => write!(
f,
"import package with namespec {}",
TtQuote::wrap(namespec)
),
Noop(_) => write!(f, "no-op"),
}
}

View File

@ -262,6 +262,16 @@ impl ParseState for NirToAir {
Transition(Ready).ok(Air::DocIndepClause(clause))
}
(Ready, Import(namespec)) => {
Transition(Ready).ok(Air::PkgImport(namespec))
}
// Shouldn't happen in practice because nir::parse will not
// produce this.
// This assumption is only valid so long as that's the only
// thing producing NIR.
(st @ Meta(..), tok @ Import(_)) => Transition(st).dead(tok),
(_, tok @ (Todo(..) | TodoAttr(..))) => {
crate::diagnostic_todo!(
vec![tok.internal_error(

View File

@ -261,7 +261,7 @@ ele_parse! {
/// for composing larger systems out of smaller components.
ImportStmt := QN_IMPORT(_, ospan) {
@ {
QN_PACKAGE => Ref,
QN_PACKAGE => Import,
QN_EXPORT => TodoAttr,
} => Noop(ospan.into()),
// ^ we only care about the `Ref`