Skip to content

Commit 921316b

Browse files
committed
Emit compile_error!{} instead of using feature(proc_macro_diagnostic).
This has the advantage that the compiler won't continue to parse our (empty) output after emitting errors, which results in more errors.
1 parent 45c3dca commit 921316b

File tree

4 files changed

+34
-46
lines changed

4 files changed

+34
-46
lines changed

macros/src/embed_python.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use proc_macro2::{Delimiter, Ident, LineColumn, Spacing, Span, TokenStream, TokenTree};
2+
use quote::quote_spanned;
23
use std::collections::BTreeMap;
34
use std::fmt::Write;
45

@@ -21,15 +22,15 @@ impl EmbedPython {
2122
}
2223
}
2324

24-
fn add_whitespace(&mut self, span: Span, loc: LineColumn) -> Result<(), ()> {
25+
fn add_whitespace(&mut self, span: Span, loc: LineColumn) -> Result<(), TokenStream> {
2526
if loc.line > self.loc.line {
2627
while loc.line > self.loc.line {
2728
self.python.push('\n');
2829
self.loc.line += 1;
2930
}
3031
let first_indent = *self.first_indent.get_or_insert(loc.column);
3132
let indent = loc.column.checked_sub(first_indent);
32-
let indent = indent.ok_or_else(|| span.unwrap().error("Invalid indentation").emit())?;
33+
let indent = indent.ok_or_else(|| quote_spanned!(span => compile_error!{"Invalid indentation"}))?;
3334
for _ in 0..indent {
3435
self.python.push(' ');
3536
}
@@ -44,7 +45,7 @@ impl EmbedPython {
4445
Ok(())
4546
}
4647

47-
pub fn add(&mut self, input: TokenStream) -> Result<(), ()> {
48+
pub fn add(&mut self, input: TokenStream) -> Result<(), TokenStream> {
4849
let mut tokens = input.into_iter();
4950

5051
while let Some(token) = tokens.next() {

macros/src/error.rs

Lines changed: 10 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,24 @@
11
use proc_macro2::{Span, TokenStream};
2+
use quote::{quote, quote_spanned};
23
use pyo3::type_object::PyTypeObject;
34
use pyo3::{PyAny, AsPyRef, PyErr, PyResult, Python, ToPyObject};
45

56
/// Format a nice error message for a python compilation error.
6-
pub fn emit_compile_error_msg(py: Python, error: PyErr, tokens: TokenStream) {
7+
pub fn compile_error_msg(py: Python, error: PyErr, tokens: TokenStream) -> TokenStream {
78
let value = error.to_object(py);
89

910
if value.is_none() {
10-
Span::call_site()
11-
.unwrap()
12-
.error(format!("python: {}", error.ptype.as_ref(py).name()))
13-
.emit();
14-
return;
11+
let error = format!("python: {}", error.ptype.as_ref(py).name());
12+
return quote!(compile_error!{#error});
1513
}
1614

1715
if error.matches(py, pyo3::exceptions::SyntaxError::type_object()) {
1816
let line: Option<usize> = value.getattr(py, "lineno").ok().and_then(|x| x.extract(py).ok());
1917
let msg: Option<String> = value.getattr(py, "msg").ok().and_then(|x| x.extract(py).ok());
2018
if let (Some(line), Some(msg)) = (line, msg) {
2119
if let Some(span) = span_for_line(tokens.clone(), line) {
22-
span.unwrap().error(format!("python: {}", msg)).emit();
23-
return;
20+
let error = format!("python: {}", msg);
21+
return quote_spanned!(span => compile_error!{#error});
2422
}
2523
}
2624
}
@@ -30,18 +28,16 @@ pub fn emit_compile_error_msg(py: Python, error: PyErr, tokens: TokenStream) {
3028
if file == Span::call_site().unwrap().source_file().path().to_string_lossy() {
3129
if let Ok(msg) = value.as_ref(py).str() {
3230
if let Some(span) = span_for_line(tokens, line) {
33-
span.unwrap().error(format!("python: {}", msg)).emit();
34-
return;
31+
let error = format!("python: {}", msg);
32+
return quote_spanned!(span => compile_error!{#error});
3533
}
3634
}
3735
}
3836
}
3937
}
4038

41-
Span::call_site()
42-
.unwrap()
43-
.error(format!("python: {}", value.as_ref(py).str().unwrap()))
44-
.emit();
39+
let error = format!("python: {}", value.as_ref(py).str().unwrap());
40+
quote!(compile_error!{#error})
4541
}
4642

4743
fn get_traceback_info(tb: &PyAny) -> PyResult<(String, usize)> {

macros/src/lib.rs

Lines changed: 12 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
#![feature(proc_macro_span)]
2-
#![feature(proc_macro_diagnostic)]
32

43
extern crate proc_macro;
54

@@ -14,7 +13,7 @@ mod embed_python;
1413
mod error;
1514
mod run;
1615

17-
fn python_impl(input: TokenStream) -> Result<TokenStream, ()> {
16+
fn python_impl(input: TokenStream) -> Result<TokenStream, TokenStream> {
1817
let tokens = input.clone();
1918

2019
check_no_attribute(input.clone())?;
@@ -35,11 +34,11 @@ fn python_impl(input: TokenStream) -> Result<TokenStream, ()> {
3534
let py = gil.python();
3635

3736
let code = PyObject::from_owned_ptr_or_err(py, ffi::Py_CompileString(python.as_ptr(), filename.as_ptr(), ffi::Py_file_input))
38-
.map_err(|err| error::emit_compile_error_msg(py, err, tokens))?;
37+
.map_err(|err| error::compile_error_msg(py, err, tokens))?;
3938

4039
Literal::byte_string(
4140
PyBytes::from_owned_ptr_or_err(py, ffi::PyMarshal_WriteObjectToString(code.as_ptr(), pyo3::marshal::VERSION))
42-
.map_err(|_e| Span::call_site().unwrap().error("failed to generate python bytecode").emit())?
41+
.map_err(|_e| quote!(compile_error!{"failed to generate python bytecode"}))?
4342
.as_bytes(),
4443
)
4544
};
@@ -61,7 +60,7 @@ fn python_impl(input: TokenStream) -> Result<TokenStream, ()> {
6160
})
6261
}
6362

64-
fn ct_python_impl(input: TokenStream) -> Result<TokenStream, ()> {
63+
fn ct_python_impl(input: TokenStream) -> Result<TokenStream, TokenStream> {
6564
let tokens = input.clone();
6665

6766
let filename = Span::call_site().unwrap().source_file().path().to_string_lossy().into_owned();
@@ -82,28 +81,23 @@ fn ct_python_impl(input: TokenStream) -> Result<TokenStream, ()> {
8281

8382
let code = unsafe {
8483
PyObject::from_owned_ptr_or_err(py, ffi::Py_CompileString(python.as_ptr(), filename.as_ptr(), ffi::Py_file_input))
85-
.map_err(|err| error::emit_compile_error_msg(py, err, tokens.clone()))?
84+
.map_err(|err| error::compile_error_msg(py, err, tokens.clone()))?
8685
};
8786

8887
run::run_ct_python(py, code, tokens)
8988
}
9089

91-
fn check_no_attribute(input: TokenStream) -> Result<(), ()> {
90+
fn check_no_attribute(input: TokenStream) -> Result<(), TokenStream> {
9291
let mut input = input.into_iter();
9392
if let Some(token) = input.next() {
9493
if token.to_string() == "#"
9594
&& input.next().map_or(false, |t| t.to_string() == "!")
9695
&& input.next().map_or(false, |t| t.to_string().starts_with('['))
9796
{
98-
token
99-
.span()
100-
.unwrap()
101-
.error(
102-
"Attributes in python!{} are no longer supported. \
103-
Use context.run(python!{..}) to use a context.",
104-
)
105-
.emit();
106-
return Err(());
97+
return Err(quote!(compile_error!{
98+
"Attributes in python!{} are no longer supported. \
99+
Use context.run(python!{..}) to use a context.",
100+
}));
107101
}
108102
}
109103
Ok(())
@@ -113,14 +107,14 @@ fn check_no_attribute(input: TokenStream) -> Result<(), ()> {
113107
pub fn python(input: TokenStream1) -> TokenStream1 {
114108
TokenStream1::from(match python_impl(TokenStream::from(input)) {
115109
Ok(tokens) => tokens,
116-
Err(()) => TokenStream::new(),
110+
Err(tokens) => tokens,
117111
})
118112
}
119113

120114
#[proc_macro]
121115
pub fn ct_python(input: TokenStream1) -> TokenStream1 {
122116
TokenStream1::from(match ct_python_impl(TokenStream::from(input)) {
123117
Ok(tokens) => tokens,
124-
Err(()) => quote!(unimplemented!()).into()
118+
Err(tokens) => tokens,
125119
})
126120
}

macros/src/run.rs

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
use crate::error::emit_compile_error_msg;
2-
use proc_macro2::{Span, TokenStream};
1+
use crate::error::compile_error_msg;
2+
use proc_macro2::TokenStream;
33
use pyo3::{ffi, AsPyPointer, PyObject, PyResult, Python};
44
use std::str::FromStr;
55

66
#[cfg(unix)]
7-
fn ensure_libpython_symbols_loaded(py: Python) -> PyResult<()>{
7+
fn ensure_libpython_symbols_loaded(py: Python) -> PyResult<()> {
88
// On Unix, Rustc loads proc-macro crates with RTLD_LOCAL, which (at least
99
// on Linux) means all their dependencies (in our case: libpython) don't
1010
// get their symbols made available globally either. This means that
@@ -47,13 +47,10 @@ fn run_and_capture(py: Python, code: PyObject) -> PyResult<String> {
4747
stdout.call_method0("getvalue")?.extract()
4848
}
4949

50-
pub fn run_ct_python(py: Python, code: PyObject, tokens: TokenStream) -> Result<TokenStream, ()> {
51-
let output = run_and_capture(py, code).map_err(|err| emit_compile_error_msg(py, err, tokens))?;
50+
pub fn run_ct_python(py: Python, code: PyObject, tokens: TokenStream) -> Result<TokenStream, TokenStream> {
51+
let output = run_and_capture(py, code).map_err(|err| compile_error_msg(py, err, tokens))?;
5252

53-
Ok(TokenStream::from_str(&output).map_err(|e| {
54-
Span::call_site()
55-
.unwrap()
56-
.error(format!("Unable to parse output of ct_python!{{}} script: {:?}", e))
57-
.emit()
58-
})?)
53+
// TokenStream::from_str emits any errors directly, so we don't need to do
54+
// anything with the returned LexError.
55+
TokenStream::from_str(&output).map_err(|_| TokenStream::new())
5956
}

0 commit comments

Comments
 (0)