From 1bb25b05b32850bba87b4ba2d575cef2c75075a1 Mon Sep 17 00:00:00 2001 From: Mike Gerwitz Date: Mon, 12 Jun 2023 12:33:22 -0400 Subject: [PATCH] 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 `::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 --- tamer/src/asg/graph/xmli.rs | 8 +++- tamer/src/asg/mod.rs | 2 +- tamer/src/bin/tamec.rs | 92 +++++++++++++++++++------------------ tamer/src/diagnose.rs | 52 +++++++++++++++++++++ tamer/src/lib.rs | 3 +- tamer/src/nir.rs | 2 +- tamer/src/nir/air.rs | 2 - tamer/src/nir/tplshort.rs | 7 ++- tamer/src/pipeline.rs | 12 +++++ tamer/src/xir/autoclose.rs | 8 +++- tamer/src/xir/flat.rs | 7 ++- tamer/src/xir/flat/test.rs | 4 +- 12 files changed, 139 insertions(+), 60 deletions(-) diff --git a/tamer/src/asg/graph/xmli.rs b/tamer/src/asg/graph/xmli.rs index f9a82e82..87ab17d9 100644 --- a/tamer/src/asg/graph/xmli.rs +++ b/tamer/src/asg/graph/xmli.rs @@ -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; +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( diff --git a/tamer/src/asg/mod.rs b/tamer/src/asg/mod.rs index 444c5b46..d7a86df2 100644 --- a/tamer/src/asg/mod.rs +++ b/tamer/src/asg/mod.rs @@ -74,7 +74,7 @@ pub use graph::{ ObjectKind, }, visit, - xmli::AsgTreeToXirf, + xmli::{AsgTreeToXirf, AsgTreeToXirfError}, Asg, AsgResult, IndexType, }; diff --git a/tamer/src/bin/tamec.rs b/tamer/src/bin/tamec.rs index 03e1ba6f..338fc156 100644 --- a/tamer/src/bin/tamec.rs +++ b/tamer/src/bin/tamec.rs @@ -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), + XirfAutoCloseError(ParseError, XirfAutoCloseError>), + XirfToXirError(ParseError, XirfToXirError>), ErrorsDuringLowering(ErrorCount), FinalizeError(FinalizeError), } @@ -353,6 +361,7 @@ pub enum RecoverableError { XirParseError(ParseError), XirfParseError(ParseError), NirParseError(ParseError, XirfToNirError>), + TplShortDesugarError(ParseError), InterpError(ParseError), NirToAirError(ParseError), AirAggregateError(ParseError), @@ -376,15 +385,29 @@ impl From for UnrecoverableError { } } -impl From for UnrecoverableError { - fn from(e: FinalizeError) -> Self { - Self::FinalizeError(e) +impl From> for UnrecoverableError { + fn from(e: ParseError) -> Self { + Self::AsgTreeToXirfError(e) } } -impl From for UnrecoverableError { - fn from(_: Infallible) -> Self { - unreachable!(">::from") +impl From, XirfToXirError>> for UnrecoverableError { + fn from(e: ParseError, XirfToXirError>) -> Self { + Self::XirfToXirError(e) + } +} + +impl From, XirfAutoCloseError>> + for UnrecoverableError +{ + fn from(e: ParseError, XirfAutoCloseError>) -> Self { + Self::XirfAutoCloseError(e) + } +} + +impl From for UnrecoverableError { + fn from(e: FinalizeError) -> Self { + Self::FinalizeError(e) } } @@ -396,14 +419,6 @@ impl From> for UnrecoverableError { } } -impl From> for RecoverableError { - fn from(_: ParseError) -> Self { - unreachable!( - ">>::from" - ) - } -} - impl From> for RecoverableError { fn from(e: ParseError) -> Self { Self::XirParseError(e) @@ -424,6 +439,12 @@ impl From, XirfToNirError>> } } +impl From> for RecoverableError { + fn from(e: ParseError) -> Self { + Self::TplShortDesugarError(e) + } +} + impl From> for RecoverableError { fn from(e: ParseError) -> 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 { 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(), diff --git a/tamer/src/diagnose.rs b/tamer/src/diagnose.rs index db38190b..390df30e 100644 --- a/tamer/src/diagnose.rs +++ b/tamer/src/diagnose.rs @@ -332,3 +332,55 @@ impl> 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 for $name { + fn from(_: std::convert::Infallible) -> Self { + unreachable!() + } + } + + impl From<$name> for std::convert::Infallible { + fn from(_: $name) -> Self { + unreachable!() + } + } + } +} diff --git a/tamer/src/lib.rs b/tamer/src/lib.rs index 1aadabab..471fe037 100644 --- a/tamer/src/lib.rs +++ b/tamer/src/lib.rs @@ -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; diff --git a/tamer/src/nir.rs b/tamer/src/nir.rs index 7d9157b0..6a4ee756 100644 --- a/tamer/src/nir.rs +++ b/tamer/src/nir.rs @@ -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. /// diff --git a/tamer/src/nir/air.rs b/tamer/src/nir/air.rs index ff43a83d..d543bc5d 100644 --- a/tamer/src/nir/air.rs +++ b/tamer/src/nir/air.rs @@ -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); } diff --git a/tamer/src/nir/tplshort.rs b/tamer/src/nir/tplshort.rs index 7032388f..cc1f9072 100644 --- a/tamer/src/nir/tplshort.rs +++ b/tamer/src/nir/tplshort.rs @@ -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( diff --git a/tamer/src/pipeline.rs b/tamer/src/pipeline.rs index 648e9819..75f23528 100644 --- a/tamer/src/pipeline.rs +++ b/tamer/src/pipeline.rs @@ -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}, diff --git a/tamer/src/xir/autoclose.rs b/tamer/src/xir/autoclose.rs index 6870543b..d17c9d98 100644 --- a/tamer/src/xir/autoclose.rs +++ b/tamer/src/xir/autoclose.rs @@ -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; type Object = XirfToken; - type Error = Infallible; + type Error = XirfAutoCloseError; type Context = AutoCloseStack; fn parse_token( diff --git a/tamer/src/xir/flat.rs b/tamer/src/xir/flat.rs index 9365eee1..57484fcd 100644 --- a/tamer/src/xir/flat.rs +++ b/tamer/src/xir/flat.rs @@ -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 Display for XirfToXir { } } +diagnostic_infallible! { + pub enum XirfToXirError {} +} + impl ParseState for XirfToXir { type Token = XirfToken; type Object = XirToken; - type Error = Infallible; + type Error = XirfToXirError; fn parse_token( self, diff --git a/tamer/src/xir/flat/test.rs b/tamer/src/xir/flat/test.rs index 23179f46..c7b70c46 100644 --- a/tamer/src/xir/flat/test.rs +++ b/tamer/src/xir/flat/test.rs @@ -619,10 +619,10 @@ fn xirf_to_xir() { ); // The lowering pipeline above requires compatible errors. - impl From, Infallible>> + impl From, XirfToXirError>> for ParseError { - fn from(_value: ParseError, Infallible>) -> Self { + fn from(_value: ParseError, XirfToXirError>) -> Self { unreachable!() } }