From 7d16af9a06bf77eceadd166351702207da8c537c Mon Sep 17 00:00:00 2001
From: CreepySkeleton <creepy-skeleton@yandex.ru>
Date: Mon, 31 Aug 2020 11:57:55 +0300
Subject: [PATCH] Make use of SpanRange instead of Span

---
 maud_macros/src/ast.rs      | 79 +++++++++++++++++-------------------
 maud_macros/src/generate.rs | 11 ++---
 maud_macros/src/parse.rs    | 81 +++++++++++++++++--------------------
 3 files changed, 82 insertions(+), 89 deletions(-)

diff --git a/maud_macros/src/ast.rs b/maud_macros/src/ast.rs
index 15bcf09..c437a1d 100644
--- a/maud_macros/src/ast.rs
+++ b/maud_macros/src/ast.rs
@@ -1,18 +1,19 @@
-use proc_macro2::{Span, TokenStream, TokenTree};
+use proc_macro2::{TokenStream, TokenTree};
+use proc_macro_error::SpanRange;
 
 #[derive(Debug)]
 pub enum Markup {
     Block(Block),
     Literal {
         content: String,
-        span: Span,
+        span: SpanRange,
     },
     Symbol {
         symbol: TokenStream,
     },
     Splice {
         expr: TokenStream,
-        outer_span: Span,
+        outer_span: SpanRange,
     },
     Element {
         name: TokenStream,
@@ -20,22 +21,22 @@ pub enum Markup {
         body: ElementBody,
     },
     Let {
-        at_span: Span,
+        at_span: SpanRange,
         tokens: TokenStream,
     },
     Special {
         segments: Vec<Special>,
     },
     Match {
-        at_span: Span,
+        at_span: SpanRange,
         head: TokenStream,
         arms: Vec<MatchArm>,
-        arms_span: Span,
+        arms_span: SpanRange,
     },
 }
 
 impl Markup {
-    pub fn span(&self) -> Span {
+    pub fn span(&self) -> SpanRange {
         match *self {
             Markup::Block(ref block) => block.span(),
             Markup::Literal { span, .. } => span,
@@ -43,16 +44,16 @@ impl Markup {
             Markup::Splice { outer_span, .. } => outer_span,
             Markup::Element { ref name, ref body, .. } => {
                 let name_span = span_tokens(name.clone());
-                name_span.join(body.span()).unwrap_or(name_span)
+                name_span.join_range(body.span())
             },
             Markup::Let { at_span, ref tokens } => {
-                at_span.join(span_tokens(tokens.clone())).unwrap_or(at_span)
+                at_span.join_range(span_tokens(tokens.clone()))
             },
             Markup::Special { ref segments } => {
-                join_spans(segments.iter().map(Special::span))
+                join_ranges(segments.iter().map(Special::span))
             },
             Markup::Match { at_span, arms_span, .. } => {
-                at_span.join(arms_span).unwrap_or(at_span)
+                at_span.join_range(arms_span)
             },
         }
     }
@@ -63,12 +64,12 @@ pub type Attrs = Vec<Attr>;
 #[derive(Debug)]
 pub enum Attr {
     Class {
-        dot_span: Span,
+        dot_span: SpanRange,
         name: Markup,
         toggler: Option<Toggler>,
     },
     Id {
-        hash_span: Span,
+        hash_span: SpanRange,
         name: Markup,
     },
     Attribute {
@@ -77,20 +78,20 @@ pub enum Attr {
 }
 
 impl Attr {
-    pub fn span(&self) -> Span {
+    pub fn span(&self) -> SpanRange {
         match *self {
             Attr::Class { dot_span, ref name, ref toggler } => {
                 let name_span = name.span();
-                let dot_name_span = dot_span.join(name_span).unwrap_or(dot_span);
+                let dot_name_span = dot_span.join_range(name_span);
                 if let Some(toggler) = toggler {
-                    dot_name_span.join(toggler.cond_span).unwrap_or(name_span)
+                    dot_name_span.join_range(toggler.cond_span)
                 } else {
                     dot_name_span
                 }
             },
             Attr::Id { hash_span, ref name } => {
                 let name_span = name.span();
-                hash_span.join(name_span).unwrap_or(hash_span)
+                hash_span.join_range(name_span)
             },
             Attr::Attribute { ref attribute } => attribute.span(),
         }
@@ -99,12 +100,12 @@ impl Attr {
 
 #[derive(Debug)]
 pub enum ElementBody {
-    Void { semi_span: Span },
+    Void { semi_span: SpanRange },
     Block { block: Block },
 }
 
 impl ElementBody {
-    pub fn span(&self) -> Span {
+    pub fn span(&self) -> SpanRange {
         match *self {
             ElementBody::Void { semi_span } => semi_span,
             ElementBody::Block { ref block } => block.span(),
@@ -115,26 +116,26 @@ impl ElementBody {
 #[derive(Debug)]
 pub struct Block {
     pub markups: Vec<Markup>,
-    pub outer_span: Span,
+    pub outer_span: SpanRange,
 }
 
 impl Block {
-    pub fn span(&self) -> Span {
+    pub fn span(&self) -> SpanRange {
         self.outer_span
     }
 }
 
 #[derive(Debug)]
 pub struct Special {
-    pub at_span: Span,
+    pub at_span: SpanRange,
     pub head: TokenStream,
     pub body: Block,
 }
 
 impl Special {
-    pub fn span(&self) -> Span {
+    pub fn span(&self) -> SpanRange {
         let body_span = self.body.span();
-        self.at_span.join(body_span).unwrap_or(self.at_span)
+        self.at_span.join_range(body_span)
     }
 }
 
@@ -145,10 +146,10 @@ pub struct Attribute {
 }
 
 impl Attribute {
-    fn span(&self) -> Span {
+    fn span(&self) -> SpanRange {
         let name_span = span_tokens(self.name.clone());
         if let Some(attr_type_span) = self.attr_type.span() {
-            name_span.join(attr_type_span).unwrap_or(name_span)
+            name_span.join_range(attr_type_span)
         } else {
             name_span
         }
@@ -166,7 +167,7 @@ pub enum AttrType {
 }
 
 impl AttrType {
-    fn span(&self) -> Option<Span> {
+    fn span(&self) -> Option<SpanRange> {
         match *self {
             AttrType::Normal { ref value } => Some(value.span()),
             AttrType::Empty { ref toggler } => toggler.as_ref().map(Toggler::span),
@@ -177,11 +178,11 @@ impl AttrType {
 #[derive(Debug)]
 pub struct Toggler {
     pub cond: TokenStream,
-    pub cond_span: Span,
+    pub cond_span: SpanRange,
 }
 
 impl Toggler {
-    fn span(&self) -> Span {
+    fn span(&self) -> SpanRange {
         self.cond_span
     }
 }
@@ -192,20 +193,16 @@ pub struct MatchArm {
     pub body: Block,
 }
 
-pub fn span_tokens<I: IntoIterator<Item=TokenTree>>(tokens: I) -> Span {
-    join_spans(tokens.into_iter().map(|token| token.span()))
+pub fn span_tokens<I: IntoIterator<Item=TokenTree>>(tokens: I) -> SpanRange {
+    join_ranges(tokens.into_iter().map(|s| SpanRange::single_span(s.span())))
 }
 
-pub fn join_spans<I: IntoIterator<Item=Span>>(spans: I) -> Span {
-    let mut iter = spans.into_iter();
-    let mut span = match iter.next() {
+pub fn join_ranges<I: IntoIterator<Item=SpanRange>>(ranges: I) -> SpanRange {
+    let mut iter = ranges.into_iter();
+    let first = match iter.next() {
         Some(span) => span,
-        None => return Span::call_site(),
+        None => return SpanRange::call_site(),
     };
-    for new_span in iter {
-        if let Some(joined) = span.join(new_span) {
-            span = joined;
-        }
-    }
-    span
+    let last = iter.last().unwrap_or(first);
+    first.join_range(last)
 }
diff --git a/maud_macros/src/generate.rs b/maud_macros/src/generate.rs
index 811dc6a..24ad277 100644
--- a/maud_macros/src/generate.rs
+++ b/maud_macros/src/generate.rs
@@ -9,6 +9,7 @@ use proc_macro2::{
     TokenTree,
 };
 use quote::quote;
+use proc_macro_error::SpanRange;
 
 use crate::ast::*;
 
@@ -63,7 +64,7 @@ impl Generator {
                         .map(|arm| self.match_arm(arm))
                         .collect();
                     let mut body = TokenTree::Group(Group::new(Delimiter::Brace, body));
-                    body.set_span(arms_span.into());
+                    body.set_span(arms_span.collapse());
                     let head: TokenStream = head.into();
                     quote!(#head #body)
                 });
@@ -75,7 +76,7 @@ impl Generator {
         let mut build = self.builder();
         self.markups(markups, &mut build);
         let mut block = TokenTree::Group(Group::new(Delimiter::Brace, build.finish()));
-        block.set_span(outer_span.into());
+        block.set_span(outer_span.collapse());
         TokenStream::from(block)
     }
 
@@ -203,7 +204,7 @@ fn desugar_classes_or_ids(
         };
         let head = desugar_toggler(toggler);
         markups.push(Markup::Special {
-            segments: vec![Special { at_span: Span::call_site(), head: head.into(), body }],
+            segments: vec![Special { at_span: SpanRange::call_site(), head: head.into(), body }],
         });
     }
     Some(Attribute {
@@ -211,7 +212,7 @@ fn desugar_classes_or_ids(
         attr_type: AttrType::Normal {
             value: Markup::Block(Block {
                 markups,
-                outer_span: Span::call_site(),
+                outer_span: SpanRange::call_site(),
             }),
         },
     })
@@ -236,7 +237,7 @@ fn desugar_toggler(Toggler { cond, cond_span }: Toggler) -> TokenStream {
     // wrap it in parentheses to avoid parse errors
     if cond.clone().into_iter().any(is_braced_block) {
         let mut wrapped_cond = TokenTree::Group(Group::new(Delimiter::Parenthesis, cond));
-        wrapped_cond.set_span(cond_span.into());
+        wrapped_cond.set_span(cond_span.collapse());
         cond = TokenStream::from(wrapped_cond);
     }
     quote!(if #cond)
diff --git a/maud_macros/src/parse.rs b/maud_macros/src/parse.rs
index f8d1a3d..67849ba 100644
--- a/maud_macros/src/parse.rs
+++ b/maud_macros/src/parse.rs
@@ -7,7 +7,7 @@ use proc_macro2::{
     TokenStream,
     TokenTree,
 };
-use proc_macro_error::{abort, abort_call_site};
+use proc_macro_error::{abort, abort_call_site, SpanRange};
 use std::collections::HashMap;
 use std::mem;
 
@@ -128,13 +128,11 @@ impl Parser {
                             "for" => self.for_expr(at_span, keyword),
                             "match" => self.match_expr(at_span, keyword),
                             "let" => {
-                                let ident_span = ident.span();
-                                let span = at_span.join(ident_span).unwrap_or(ident_span);
+                                let span = SpanRange { first: at_span, last: ident.span() };
                                 abort!(span, "`@let` only works inside a block");
                             },
                             other => {
-                                let ident_span = ident.span();
-                                let span = at_span.join(ident_span).unwrap_or(ident_span);
+                                let span = SpanRange { first: at_span, last: ident.span() };
                                 abort!(span, "unknown keyword `@{}`", other);
                             }
                         }
@@ -146,7 +144,7 @@ impl Parser {
             },
             // Element
             TokenTree::Ident(ident) => {
-                let ident_string = ident.to_string();
+                let _ident_string = ident.to_string();
                 // Is this a keyword that's missing a '@'?
                 // TODO: warning or error?
                 // match ident_string.as_str() {
@@ -171,12 +169,12 @@ impl Parser {
             // Splice
             TokenTree::Group(ref group) if group.delimiter() == Delimiter::Parenthesis => {
                 self.advance();
-                ast::Markup::Splice { expr: group.stream(), outer_span: group.span() }
+                ast::Markup::Splice { expr: group.stream(), outer_span: SpanRange::single_span(group.span()) }
             }
             // Block
             TokenTree::Group(ref group) if group.delimiter() == Delimiter::Brace => {
                 self.advance();
-                ast::Markup::Block(self.block(group.stream(), group.span()))
+                ast::Markup::Block(self.block(group.stream(), SpanRange::single_span(group.span())))
             },
             // ???
             token => {
@@ -193,7 +191,7 @@ impl Parser {
             .unwrap_or_else(|_| abort!(lit, "expected string"));
         ast::Markup::Literal {
             content,
-            span: lit.span(),
+            span: SpanRange::single_span(lit.span()),
         }
     }
 
@@ -210,18 +208,18 @@ impl Parser {
         let body = loop {
             match self.next() {
                 Some(TokenTree::Group(ref block)) if block.delimiter() == Delimiter::Brace => {
-                    break self.block(block.stream(), block.span());
+                    break self.block(block.stream(), SpanRange::single_span(block.span()));
                 },
                 Some(token) => head.push(token),
                 None => {
-                    let head_span = ast::span_tokens(head);
-                    let span = at_span.join(head_span).unwrap_or(head_span);
+                    let mut span = ast::span_tokens(head);
+                    span.first = at_span;
                     abort!(span, "expected body for this `@if`");
                 },
             }
         };
         segments.push(ast::Special {
-            at_span,
+            at_span: SpanRange::single_span(at_span),
             head: head.into_iter().collect(),
             body,
         });
@@ -251,16 +249,15 @@ impl Parser {
                     _ => {
                         match self.next() {
                             Some(TokenTree::Group(ref group)) if group.delimiter() == Delimiter::Brace => {
-                                let body = self.block(group.stream(), group.span());
+                                let body = self.block(group.stream(), SpanRange::single_span(group.span()));
                                 segments.push(ast::Special {
-                                    at_span,
+                                    at_span: SpanRange::single_span(at_span),
                                     head: vec![else_keyword].into_iter().collect(),
                                     body,
                                 });
                             },
                             _ => {
-                                let else_span = else_keyword.span();
-                                let span = at_span.join(else_span).unwrap_or(else_span);
+                                let span = SpanRange { first: at_span, last: else_keyword.span() };
                                 abort!(span, "expected body for this `@else`");
                             },
                         }
@@ -281,17 +278,17 @@ impl Parser {
         let body = loop {
             match self.next() {
                 Some(TokenTree::Group(ref block)) if block.delimiter() == Delimiter::Brace => {
-                    break self.block(block.stream(), block.span());
+                    break self.block(block.stream(), SpanRange::single_span(block.span()));
                 },
                 Some(token) => head.push(token),
                 None => {
-                    let span = at_span.join(keyword_span).unwrap_or(keyword_span);
+                    let span = SpanRange { first: at_span, last: keyword_span };
                     abort!(span, "expected body for this `@while`");
                 },
             }
         };
         ast::Markup::Special {
-            segments: vec![ast::Special { at_span, head: head.into_iter().collect(), body }],
+            segments: vec![ast::Special { at_span: SpanRange::single_span(at_span), head: head.into_iter().collect(), body }],
         }
     }
 
@@ -309,7 +306,7 @@ impl Parser {
                 },
                 Some(token) => head.push(token),
                 None => {
-                    let span = at_span.join(keyword_span).unwrap_or(keyword_span);
+                    let span = SpanRange { first: at_span, last: keyword_span };
                     abort!(span, "missing `in` in `@for` loop");
                 },
             }
@@ -317,17 +314,17 @@ impl Parser {
         let body = loop {
             match self.next() {
                 Some(TokenTree::Group(ref block)) if block.delimiter() == Delimiter::Brace => {
-                    break self.block(block.stream(), block.span());
+                    break self.block(block.stream(), SpanRange::single_span(block.span()));
                 },
                 Some(token) => head.push(token),
                 None => {
-                    let span = at_span.join(keyword_span).unwrap_or(keyword_span);
+                    let span = SpanRange { first: at_span, last: keyword_span };
                     abort!(span, "expected body for this `@for`");
                 },
             }
         };
         ast::Markup::Special {
-            segments: vec![ast::Special { at_span, head: head.into_iter().collect(), body }],
+            segments: vec![ast::Special { at_span: SpanRange::single_span(at_span), head: head.into_iter().collect(), body }],
         }
     }
 
@@ -340,17 +337,17 @@ impl Parser {
         let (arms, arms_span) = loop {
             match self.next() {
                 Some(TokenTree::Group(ref body)) if body.delimiter() == Delimiter::Brace => {
-                    let span = body.span();
+                    let span = SpanRange::single_span(body.span());
                     break (self.with_input(body.stream()).match_arms(), span);
                 },
                 Some(token) => head.push(token),
                 None => {
-                    let span = at_span.join(keyword_span).unwrap_or(keyword_span);
+                    let span = SpanRange { first: at_span, last: keyword_span };
                     abort!(span, "expected body for this `@match`");
                 },
             }
         };
-        ast::Markup::Match { at_span, head: head.into_iter().collect(), arms, arms_span }
+        ast::Markup::Match { at_span: SpanRange::single_span(at_span), head: head.into_iter().collect(), arms, arms_span }
     }
 
     fn match_arms(&mut self) -> Vec<ast::MatchArm> {
@@ -389,7 +386,7 @@ impl Parser {
         let body = match self.next() {
             // $pat => { $stmts }
             Some(TokenTree::Group(ref body)) if body.delimiter() == Delimiter::Brace => {
-                let body = self.block(body.stream(), body.span());
+                let body = self.block(body.stream(), SpanRange::single_span(body.span()));
                 // Trailing commas are optional if the match arm is a braced block
                 if let Some(TokenTree::Punct(ref punct)) = self.peek() {
                     if punct.as_char() == ',' {
@@ -400,15 +397,13 @@ impl Parser {
             },
             // $pat => $expr
             Some(first_token) => {
-                let mut span = first_token.span();
+                let mut span = SpanRange::single_span(first_token.span());
                 let mut body = vec![first_token];
                 loop {
                     match self.next() {
                         Some(TokenTree::Punct(ref punct)) if punct.as_char() == ',' => break,
                         Some(token) => {
-                            if let Some(bigger_span) = span.join(token.span()) {
-                                span = bigger_span;
-                            }
+                            span.last = token.span();
                             body.push(token);
                         },
                         None => break,
@@ -441,8 +436,8 @@ impl Parser {
                     }
                 },
                 None => {
-                    let tokens_span = ast::span_tokens(tokens);
-                    let span = at_span.join(tokens_span).unwrap_or(tokens_span);
+                    let mut span = ast::span_tokens(tokens);
+                    span.first = at_span;
                     abort!(span, "unexpected end of `@let` expression");
                 }
             }
@@ -459,8 +454,8 @@ impl Parser {
                     }
                 },
                 None => {
-                    let tokens_span = ast::span_tokens(tokens);
-                    let span = at_span.join(tokens_span).unwrap_or(tokens_span);
+                    let mut span = ast::span_tokens(tokens);
+                    span.first = at_span;
                     abort!(
                         span,
                         "unexpected end of `@let` expression";
@@ -469,7 +464,7 @@ impl Parser {
                 },
             }
         }
-        ast::Markup::Let { at_span, tokens: tokens.into_iter().collect() }
+        ast::Markup::Let { at_span: SpanRange::single_span(at_span), tokens: tokens.into_iter().collect() }
     }
 
     /// Parses an element node.
@@ -486,7 +481,7 @@ impl Parser {
             if punct.as_char() == ';' || punct.as_char() == '/' => {
                 // Void element
                 self.advance();
-                ast::ElementBody::Void { semi_span: punct.span() }
+                ast::ElementBody::Void { semi_span: SpanRange::single_span(punct.span()) }
             },
             _ => {
                 match self.markup() {
@@ -546,20 +541,20 @@ impl Parser {
                     self.commit(attempt);
                     let name = self.class_or_id_name();
                     let toggler = self.attr_toggler();
-                    attrs.push(ast::Attr::Class { dot_span: punct.span(), name, toggler });
+                    attrs.push(ast::Attr::Class { dot_span: SpanRange::single_span(punct.span()), name, toggler });
                 },
                 // ID shorthand
                 (None, Some(TokenTree::Punct(ref punct))) if punct.as_char() == '#' => {
                     self.commit(attempt);
                     let name = self.class_or_id_name();
-                    attrs.push(ast::Attr::Id { hash_span: punct.span(), name });
+                    attrs.push(ast::Attr::Id { hash_span: SpanRange::single_span(punct.span()), name });
                 },
                 // If it's not a valid attribute, backtrack and bail out
                 _ => break,
             }
         }
 
-        let mut attr_map: HashMap<String, Vec<Span>> = HashMap::new();
+        let mut attr_map: HashMap<String, Vec<SpanRange>> = HashMap::new();
         let mut has_class = false;
         for attr in &attrs {
             let name = match attr {
@@ -607,7 +602,7 @@ impl Parser {
                 self.advance();
                 Some(ast::Toggler {
                     cond: group.stream(),
-                    cond_span: group.span(),
+                    cond_span: SpanRange::single_span(group.span()),
                 })
             },
             _ => None,
@@ -657,7 +652,7 @@ impl Parser {
     }
 
     /// Parses the given token stream as a Maud expression.
-    fn block(&mut self, body: TokenStream, outer_span: Span) -> ast::Block {
+    fn block(&mut self, body: TokenStream, outer_span: SpanRange) -> ast::Block {
         let markups = self.with_input(body).markups();
         ast::Block { markups, outer_span }
     }