Skip to content

Commit 19be5c9

Browse files
authored
Merge pull request RustPython#845 from RustPython/coolreader18/proc-macro-span-errors
Target rustpython_derive errors at specific source spans
2 parents 319bb80 + 48b0a17 commit 19be5c9

File tree

3 files changed

+291
-71
lines changed

3 files changed

+291
-71
lines changed

derive/src/error.rs

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
// Taken from https://github.com/rustwasm/wasm-bindgen/blob/master/crates/backend/src/error.rs
2+
//
3+
// Copyright (c) 2014 Alex Crichton
4+
//
5+
// Permission is hereby granted, free of charge, to any
6+
// person obtaining a copy of this software and associated
7+
// documentation files (the "Software"), to deal in the
8+
// Software without restriction, including without
9+
// limitation the rights to use, copy, modify, merge,
10+
// publish, distribute, sublicense, and/or sell copies of
11+
// the Software, and to permit persons to whom the Software
12+
// is furnished to do so, subject to the following
13+
// conditions:
14+
//
15+
// The above copyright notice and this permission notice
16+
// shall be included in all copies or substantial portions
17+
// of the Software.
18+
//
19+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
20+
// ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
21+
// TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
22+
// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
23+
// SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
24+
// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
25+
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
26+
// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
27+
// DEALINGS IN THE SOFTWARE.
28+
#![allow(dead_code)]
29+
30+
use proc_macro2::*;
31+
use quote::{ToTokens, TokenStreamExt};
32+
use syn::parse::Error;
33+
34+
macro_rules! err_span {
35+
($span:expr, $($msg:tt)*) => (
36+
$crate::Diagnostic::spanned_error(&$span, format!($($msg)*))
37+
)
38+
}
39+
40+
macro_rules! bail_span {
41+
($($t:tt)*) => (
42+
return Err(err_span!($($t)*).into())
43+
)
44+
}
45+
46+
macro_rules! push_err_span {
47+
($diagnostics:expr, $($t:tt)*) => {
48+
$diagnostics.push(err_span!($($t)*))
49+
};
50+
}
51+
52+
#[derive(Debug)]
53+
pub struct Diagnostic {
54+
inner: Repr,
55+
}
56+
57+
#[derive(Debug)]
58+
enum Repr {
59+
Single {
60+
text: String,
61+
span: Option<(Span, Span)>,
62+
},
63+
SynError(Error),
64+
Multi {
65+
diagnostics: Vec<Diagnostic>,
66+
},
67+
}
68+
69+
impl Diagnostic {
70+
pub fn error<T: Into<String>>(text: T) -> Diagnostic {
71+
Diagnostic {
72+
inner: Repr::Single {
73+
text: text.into(),
74+
span: None,
75+
},
76+
}
77+
}
78+
79+
pub fn span_error<T: Into<String>>(span: Span, text: T) -> Diagnostic {
80+
Diagnostic {
81+
inner: Repr::Single {
82+
text: text.into(),
83+
span: Some((span, span)),
84+
},
85+
}
86+
}
87+
88+
pub fn spanned_error<T: Into<String>>(node: &ToTokens, text: T) -> Diagnostic {
89+
Diagnostic {
90+
inner: Repr::Single {
91+
text: text.into(),
92+
span: extract_spans(node),
93+
},
94+
}
95+
}
96+
97+
pub fn from_vec(diagnostics: Vec<Diagnostic>) -> Result<(), Diagnostic> {
98+
if diagnostics.len() == 0 {
99+
Ok(())
100+
} else {
101+
Err(Diagnostic {
102+
inner: Repr::Multi { diagnostics },
103+
})
104+
}
105+
}
106+
107+
#[allow(unconditional_recursion)]
108+
pub fn panic(&self) -> ! {
109+
match &self.inner {
110+
Repr::Single { text, .. } => panic!("{}", text),
111+
Repr::SynError(error) => panic!("{}", error),
112+
Repr::Multi { diagnostics } => diagnostics[0].panic(),
113+
}
114+
}
115+
}
116+
117+
impl From<Error> for Diagnostic {
118+
fn from(err: Error) -> Diagnostic {
119+
Diagnostic {
120+
inner: Repr::SynError(err),
121+
}
122+
}
123+
}
124+
125+
fn extract_spans(node: &ToTokens) -> Option<(Span, Span)> {
126+
let mut t = TokenStream::new();
127+
node.to_tokens(&mut t);
128+
let mut tokens = t.into_iter();
129+
let start = tokens.next().map(|t| t.span());
130+
let end = tokens.last().map(|t| t.span());
131+
start.map(|start| (start, end.unwrap_or(start)))
132+
}
133+
134+
impl ToTokens for Diagnostic {
135+
fn to_tokens(&self, dst: &mut TokenStream) {
136+
match &self.inner {
137+
Repr::Single { text, span } => {
138+
let cs2 = (Span::call_site(), Span::call_site());
139+
let (start, end) = span.unwrap_or(cs2);
140+
dst.append(Ident::new("compile_error", start));
141+
dst.append(Punct::new('!', Spacing::Alone));
142+
let mut message = TokenStream::new();
143+
message.append(Literal::string(text));
144+
let mut group = Group::new(Delimiter::Brace, message);
145+
group.set_span(end);
146+
dst.append(group);
147+
}
148+
Repr::Multi { diagnostics } => {
149+
for diagnostic in diagnostics {
150+
diagnostic.to_tokens(dst);
151+
}
152+
}
153+
Repr::SynError(err) => {
154+
err.to_compile_error().to_tokens(dst);
155+
}
156+
}
157+
}
158+
}

derive/src/lib.rs

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,22 @@
11
extern crate proc_macro;
22

3+
#[macro_use]
4+
mod error;
5+
mod from_args;
6+
mod pyclass;
7+
8+
use error::Diagnostic;
39
use proc_macro::TokenStream;
10+
use proc_macro2::TokenStream as TokenStream2;
11+
use quote::ToTokens;
412
use syn::{parse_macro_input, AttributeArgs, DeriveInput, Item};
513

6-
mod from_args;
7-
mod pyclass;
14+
fn result_to_tokens(result: Result<TokenStream2, Diagnostic>) -> TokenStream {
15+
match result {
16+
Ok(tokens) => tokens.into(),
17+
Err(diagnostic) => diagnostic.into_token_stream().into(),
18+
}
19+
}
820

921
#[proc_macro_derive(FromArgs, attributes(pyarg))]
1022
pub fn derive_from_args(input: TokenStream) -> TokenStream {
@@ -17,12 +29,12 @@ pub fn derive_from_args(input: TokenStream) -> TokenStream {
1729
pub fn pyclass(attr: TokenStream, item: TokenStream) -> TokenStream {
1830
let attr = parse_macro_input!(attr as AttributeArgs);
1931
let item = parse_macro_input!(item as Item);
20-
pyclass::impl_pyclass(attr, item).into()
32+
result_to_tokens(pyclass::impl_pyclass(attr, item))
2133
}
2234

2335
#[proc_macro_attribute]
2436
pub fn pyimpl(attr: TokenStream, item: TokenStream) -> TokenStream {
2537
let attr = parse_macro_input!(attr as AttributeArgs);
2638
let item = parse_macro_input!(item as Item);
27-
pyclass::impl_pyimpl(attr, item).into()
39+
result_to_tokens(pyclass::impl_pyimpl(attr, item))
2840
}

0 commit comments

Comments
 (0)