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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -39,6 +39,18 @@
//! The module is responsible for pipeline composition. //! The module is responsible for pipeline composition.
//! For information on the lowering pipeline as an abstraction, //! For information on the lowering pipeline as an abstraction,
//! see [`Lower`]. //! 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::{ use crate::{
asg::{air::AirAggregate, AsgTreeToXirf}, asg::{air::AirAggregate, AsgTreeToXirf},

View File

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

View File

@ -55,7 +55,6 @@ use crate::{
}; };
use arrayvec::ArrayVec; use arrayvec::ArrayVec;
use std::{ use std::{
convert::Infallible,
error::Error, error::Error,
fmt::{Debug, Display}, fmt::{Debug, Display},
marker::PhantomData, 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> { impl<T: TextType> ParseState for XirfToXir<T> {
type Token = XirfToken<T>; type Token = XirfToken<T>;
type Object = XirToken; type Object = XirToken;
type Error = Infallible; type Error = XirfToXirError;
fn parse_token( fn parse_token(
self, self,

View File

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