tamer: Newtypes for all Infallible ParseState errors

More information will be presented in the commit that follows to generalize
these, but this sets the stage.

The recently-introduced pipeline macro takes care of most of the job of a
declarative pipeline, but it's still leaky, since it requires that the
_caller_ create error sum types.  This not only exposes implementation
details and so undermines the goal of making pipelines easy to declare and
compose, but it's also one of the last major components of boilerplate for
the lowering pipeline.

My previous attempts at generating error sum types automatically for
pipelines ran into a problem because of overlapping `impl`s for the various
`<S as ParseState>::Error` types; this resolves that issue via
newtypes.  I had considered other approaches, including explicitly
generating code to `map_err` as part of the lowering pipeline, but in the
end this is the easier way to reason about things that also keeps manual
`Lower` pipelines on the same level of expressiveness as the pipeline macro;
I want to restrict its unique capabilities as much as possible to
elimination of boilerplate and nothing more.

DEV-13162
main
Mike Gerwitz 2023-06-12 12:33:22 -04:00
parent 672cc54c14
commit 1bb25b05b3
12 changed files with 139 additions and 60 deletions

View File

@ -54,7 +54,7 @@ use crate::{
},
};
use arrayvec::ArrayVec;
use std::{convert::Infallible, fmt::Display, marker::PhantomData};
use std::{fmt::Display, marker::PhantomData};
#[derive(Debug, PartialEq, Eq)]
pub enum AsgTreeToXirf<'a> {
@ -75,10 +75,14 @@ impl<'a> Display for AsgTreeToXirf<'a> {
type Xirf = XirfToken<Text>;
diagnostic_infallible! {
pub enum AsgTreeToXirfError {}
}
impl<'a> ParseState for AsgTreeToXirf<'a> {
type Token = TreeWalkRel;
type Object = Xirf;
type Error = Infallible;
type Error = AsgTreeToXirfError;
type Context = TreeContext<'a>;
fn parse_token(

View File

@ -74,7 +74,7 @@ pub use graph::{
ObjectKind,
},
visit,
xmli::AsgTreeToXirf,
xmli::{AsgTreeToXirf, AsgTreeToXirfError},
Asg, AsgResult, IndexType,
};

View File

@ -39,16 +39,21 @@ use std::{
path::Path,
};
use tamer::{
asg::{air::Air, AsgError, DefaultAsg},
asg::{
air::Air, visit::TreeWalkRel, AsgError, AsgTreeToXirfError, DefaultAsg,
},
diagnose::{
AnnotatedSpan, Diagnostic, FsSpanResolver, Reporter, VisualReporter,
},
nir::{InterpError, Nir, NirToAirError, XirfToNirError},
nir::{
InterpError, Nir, NirToAirError, TplShortDesugarError, XirfToNirError,
},
parse::{lowerable, FinalizeError, ParseError, Token, UnknownToken},
pipeline::parse_package_xml,
xir::{
self,
flat::{RefinedText, XirToXirfError, XirfToken},
autoclose::XirfAutoCloseError,
flat::{RefinedText, Text, XirToXirfError, XirfToXirError, XirfToken},
reader::XmlXirReader,
DefaultEscaper, Token as XirToken,
},
@ -317,6 +322,9 @@ pub enum UnrecoverableError {
Io(io::Error),
Fmt(fmt::Error),
XirWriterError(xir::writer::Error),
AsgTreeToXirfError(ParseError<TreeWalkRel, AsgTreeToXirfError>),
XirfAutoCloseError(ParseError<XirfToken<Text>, XirfAutoCloseError>),
XirfToXirError(ParseError<XirfToken<Text>, XirfToXirError>),
ErrorsDuringLowering(ErrorCount),
FinalizeError(FinalizeError),
}
@ -353,6 +361,7 @@ pub enum RecoverableError {
XirParseError(ParseError<UnknownToken, xir::Error>),
XirfParseError(ParseError<XirToken, XirToXirfError>),
NirParseError(ParseError<XirfToken<RefinedText>, XirfToNirError>),
TplShortDesugarError(ParseError<Nir, TplShortDesugarError>),
InterpError(ParseError<Nir, InterpError>),
NirToAirError(ParseError<Nir, NirToAirError>),
AirAggregateError(ParseError<Air, AsgError>),
@ -376,15 +385,29 @@ impl From<xir::writer::Error> for UnrecoverableError {
}
}
impl From<FinalizeError> for UnrecoverableError {
fn from(e: FinalizeError) -> Self {
Self::FinalizeError(e)
impl From<ParseError<TreeWalkRel, AsgTreeToXirfError>> for UnrecoverableError {
fn from(e: ParseError<TreeWalkRel, AsgTreeToXirfError>) -> Self {
Self::AsgTreeToXirfError(e)
}
}
impl From<Infallible> for UnrecoverableError {
fn from(_: Infallible) -> Self {
unreachable!("<UnrecoverableError as From<Infallible>>::from")
impl From<ParseError<XirfToken<Text>, XirfToXirError>> for UnrecoverableError {
fn from(e: ParseError<XirfToken<Text>, XirfToXirError>) -> Self {
Self::XirfToXirError(e)
}
}
impl From<ParseError<XirfToken<Text>, XirfAutoCloseError>>
for UnrecoverableError
{
fn from(e: ParseError<XirfToken<Text>, XirfAutoCloseError>) -> Self {
Self::XirfAutoCloseError(e)
}
}
impl From<FinalizeError> for UnrecoverableError {
fn from(e: FinalizeError) -> Self {
Self::FinalizeError(e)
}
}
@ -396,14 +419,6 @@ impl<T: Token> From<ParseError<T, Infallible>> for UnrecoverableError {
}
}
impl<T: Token> From<ParseError<T, Infallible>> for RecoverableError {
fn from(_: ParseError<T, Infallible>) -> Self {
unreachable!(
"<RecoverableError as From<ParseError<T, Infallible>>>::from"
)
}
}
impl From<ParseError<UnknownToken, xir::Error>> for RecoverableError {
fn from(e: ParseError<UnknownToken, xir::Error>) -> Self {
Self::XirParseError(e)
@ -424,6 +439,12 @@ impl From<ParseError<XirfToken<RefinedText>, XirfToNirError>>
}
}
impl From<ParseError<Nir, TplShortDesugarError>> for RecoverableError {
fn from(e: ParseError<Nir, TplShortDesugarError>) -> Self {
Self::TplShortDesugarError(e)
}
}
impl From<ParseError<Nir, InterpError>> for RecoverableError {
fn from(e: ParseError<Nir, InterpError>) -> Self {
Self::InterpError(e)
@ -450,6 +471,9 @@ impl Display for UnrecoverableError {
Io(e) => Display::fmt(e, f),
Fmt(e) => Display::fmt(e, f),
XirWriterError(e) => Display::fmt(e, f),
AsgTreeToXirfError(e) => Display::fmt(e, f),
XirfToXirError(e) => Display::fmt(e, f),
XirfAutoCloseError(e) => Display::fmt(e, f),
FinalizeError(e) => Display::fmt(e, f),
// TODO: Use formatter for dynamic "error(s)"
@ -468,6 +492,7 @@ impl Display for RecoverableError {
XirParseError(e) => Display::fmt(e, f),
XirfParseError(e) => Display::fmt(e, f),
NirParseError(e) => Display::fmt(e, f),
TplShortDesugarError(e) => Display::fmt(e, f),
InterpError(e) => Display::fmt(e, f),
NirToAirError(e) => Display::fmt(e, f),
AirAggregateError(e) => Display::fmt(e, f),
@ -475,40 +500,16 @@ impl Display for RecoverableError {
}
}
impl Error for UnrecoverableError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
use UnrecoverableError::*;
match self {
Io(e) => Some(e),
Fmt(e) => Some(e),
XirWriterError(e) => Some(e),
ErrorsDuringLowering(_) => None,
FinalizeError(e) => Some(e),
}
}
}
impl Error for RecoverableError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
use RecoverableError::*;
match self {
XirParseError(e) => Some(e),
XirfParseError(e) => Some(e),
NirParseError(e) => Some(e),
InterpError(e) => Some(e),
NirToAirError(e) => Some(e),
AirAggregateError(e) => Some(e),
}
}
}
impl Error for UnrecoverableError {}
impl Diagnostic for UnrecoverableError {
fn describe(&self) -> Vec<AnnotatedSpan> {
use UnrecoverableError::*;
match self {
AsgTreeToXirfError(e) => e.describe(),
XirfToXirError(e) => e.describe(),
XirfAutoCloseError(e) => e.describe(),
FinalizeError(e) => e.describe(),
// Fall back to `Display`
@ -527,6 +528,7 @@ impl Diagnostic for RecoverableError {
XirParseError(e) => e.describe(),
XirfParseError(e) => e.describe(),
NirParseError(e) => e.describe(),
TplShortDesugarError(e) => e.describe(),
InterpError(e) => e.describe(),
NirToAirError(e) => e.describe(),
AirAggregateError(e) => e.describe(),

View File

@ -332,3 +332,55 @@ impl<S: Into<Span>> Annotate for S {
AnnotatedSpan(self.into(), level, label)
}
}
/// Generate a variant-less error enum akin to [`Infallible`].
///
/// This is used to create [`Infallible`]-like newtypes where unique error
/// types are beneficial.
/// For example,
/// this can be used so that [`From`] implementations can be exclusively
/// used to widen errors
/// (or lack thereof)
/// into error sum variants,
/// and is especially useful when code generation is involved to avoid
/// generation of overlapping [`From`] `impl`s.
///
/// The generated enum is convertable [`Into`] and [`From`] [`Infallible`].
macro_rules! diagnostic_infallible {
($vis:vis enum $name:ident {}) => {
/// A unique [`Infallible`](std::convert::Infallible) type.
#[derive(Debug, PartialEq)]
$vis enum $name {}
impl std::fmt::Display for $name {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, stringify!($name))
}
}
impl $crate::diagnose::Diagnostic for $name {
fn describe(&self) -> Vec<$crate::diagnose::AnnotatedSpan> {
// This is a unit struct and should not be able to be
// instantiated!
unreachable!(
concat!(
stringify!($name),
" should be unreachable!"
)
)
}
}
impl From<std::convert::Infallible> for $name {
fn from(_: std::convert::Infallible) -> Self {
unreachable!()
}
}
impl From<$name> for std::convert::Infallible {
fn from(_: $name) -> Self {
unreachable!()
}
}
}
}

View File

@ -181,12 +181,13 @@ pub mod global;
#[macro_use]
extern crate static_assertions;
#[macro_use]
pub mod diagnose;
#[macro_use]
pub mod xir;
pub mod asg;
pub mod convert;
pub mod diagnose;
pub mod f;
pub mod fmt;
pub mod fs;

View File

@ -77,7 +77,7 @@ pub use interp::{InterpError, InterpState as InterpolateNir};
pub use parse::{
NirParseState as XirfToNir, NirParseStateError_ as XirfToNirError,
};
pub use tplshort::TplShortDesugar;
pub use tplshort::{TplShortDesugar, TplShortDesugarError};
/// IR that is "near" the source code.
///

View File

@ -108,8 +108,6 @@ impl ParseState for NirToAir {
use NirToAir::*;
use NirToAirError::*;
use crate::diagnostic_panic;
if let Some(obj) = stack.pop() {
return Transition(Ready).ok(obj).with_lookahead(tok);
}

View File

@ -88,7 +88,6 @@ use crate::{
SymbolId,
},
};
use std::convert::Infallible;
use Nir::*;
use NirEntity::*;
@ -130,10 +129,14 @@ impl Display for TplShortDesugar {
}
}
diagnostic_infallible! {
pub enum TplShortDesugarError {}
}
impl ParseState for TplShortDesugar {
type Token = Nir;
type Object = Nir;
type Error = Infallible;
type Error = TplShortDesugarError;
type Context = Stack;
fn parse_token(

View File

@ -39,6 +39,18 @@
//! The module is responsible for pipeline composition.
//! For information on the lowering pipeline as an abstraction,
//! see [`Lower`].
//!
//! Error Widening
//! ==============
//! Each [`ParseState`] in the pipeline is expected to have its own unique
//! error type,
//! utilizing newtypes if necessary;
//! this ensures that errors are able to be uniquely paired with each
//! [`ParseState`] that produced it without having to perform an
//! explicit mapping at the call site.
//! To facilitate that automatic mapping/aggregation,
//! this uniqueness property also allows for generation of [`From`]
//! implementations that will not overlap.
use crate::{
asg::{air::AirAggregate, AsgTreeToXirf},

View File

@ -106,7 +106,7 @@ use crate::{
span::{Span, UNKNOWN_SPAN},
xir::EleSpan,
};
use std::{convert::Infallible, fmt::Display};
use std::fmt::Display;
use XirfAutoClose::*;
@ -138,10 +138,14 @@ impl Display for XirfAutoClose {
}
}
diagnostic_infallible! {
pub enum XirfAutoCloseError {}
}
impl ParseState for XirfAutoClose {
type Token = XirfToken<Text>;
type Object = XirfToken<Text>;
type Error = Infallible;
type Error = XirfAutoCloseError;
type Context = AutoCloseStack;
fn parse_token(

View File

@ -55,7 +55,6 @@ use crate::{
};
use arrayvec::ArrayVec;
use std::{
convert::Infallible,
error::Error,
fmt::{Debug, Display},
marker::PhantomData,
@ -926,10 +925,14 @@ impl<T: TextType> Display for XirfToXir<T> {
}
}
diagnostic_infallible! {
pub enum XirfToXirError {}
}
impl<T: TextType> ParseState for XirfToXir<T> {
type Token = XirfToken<T>;
type Object = XirToken;
type Error = Infallible;
type Error = XirfToXirError;
fn parse_token(
self,

View File

@ -619,10 +619,10 @@ fn xirf_to_xir() {
);
// The lowering pipeline above requires compatible errors.
impl From<ParseError<XirfToken<Text>, Infallible>>
impl From<ParseError<XirfToken<Text>, XirfToXirError>>
for ParseError<XirToken, XirToXirfError>
{
fn from(_value: ParseError<XirfToken<Text>, Infallible>) -> Self {
fn from(_value: ParseError<XirfToken<Text>, XirfToXirError>) -> Self {
unreachable!()
}
}