Skip to content

Commit 706b86a

Browse files
Merge pull request #26 from LukasKalbertodt/make-stable
Guard unstable proc_macro things behind `nightly` feature
2 parents 924e82b + f81f1dc commit 706b86a

40 files changed

+288
-150
lines changed

Diff for: .travis.yml

+9
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,13 @@ language: rust
22
rust:
33
- nightly
44

5+
script:
6+
- cargo build --verbose
7+
- cargo test --verbose
8+
- cargo build --verbose --features=nightly
9+
- cargo test --verbose --features=nightly
10+
11+
env:
12+
- RUST_FLAGS="--deny warnings"
13+
514
cache: cargo

Diff for: Cargo.toml

+8-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
cargo-features = ["edition"]
2+
13
[package]
24
name = "auto_impl"
35
version = "0.2.0"
@@ -13,15 +15,20 @@ keywords = ["plugin"]
1315
categories = ["development-tools"]
1416
readme = "README.md"
1517
autotests = true
18+
edition = '2018'
1619

1720
[badges]
1821
travis-ci = { repository = "KodrAus/auto_impl" }
1922

23+
2024
[lib]
2125
proc-macro = true
2226

27+
[features]
28+
nightly = ["proc-macro2/nightly"]
29+
2330
[dependencies]
24-
proc-macro2 = { version = "0.4.6", features = ["nightly"] }
31+
proc-macro2 = { version = "0.4.6" }
2532
quote = "0.6.3"
2633
syn = { version = "0.14.4", features = ["full"] }
2734

Diff for: examples/error_messages.rs

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
//! This file showcases a few error messages emitted by `auto_impl`. You have
2+
//! to add specific lines to see the error. Then simply compile with:
3+
//!
4+
//! ```
5+
//! cargo build --example error_messages
6+
//! ```
7+
//!
8+
//! If you want to see nicer error messages, add `--features=nightly`.
9+
#![allow(unused_imports, dead_code)]
10+
11+
use auto_impl::auto_impl;
12+
13+
14+
// Shows the error message for the case that `#[auto_impl]` was used with
15+
// incorrect proxy types. Only proxy types like `&` and `Box` are allowed. Add
16+
// this next line to see the error!
17+
//#[auto_impl(Boxxi)]
18+
trait Foo {
19+
fn foo(&self) -> u32;
20+
}
21+
22+
// Shows the error message for the case the `#[auto_impl]` wasn't applied to a
23+
// valid trait (in this case a struct). Add this next line to see the error!
24+
//#[auto_impl(&, Box)]
25+
struct Bar {
26+
x: u32,
27+
}
28+
29+
30+
fn main() {}

Diff for: examples/greet_closure.rs

-4
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,3 @@
1-
#![feature(use_extern_macros)]
2-
3-
extern crate auto_impl;
4-
51
use auto_impl::auto_impl;
62

73

Diff for: examples/names.rs

-2
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,7 @@
2929

3030
// This code is really ugly on purpose...
3131
#![allow(non_snake_case, dead_code, unused_variables)]
32-
#![feature(use_extern_macros)]
3332

34-
extern crate auto_impl;
3533
use auto_impl::auto_impl;
3634

3735

Diff for: examples/refs.rs

-4
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,3 @@
1-
#![feature(use_extern_macros)]
2-
3-
extern crate auto_impl;
4-
51
use std::fmt::Display;
62

73
use auto_impl::auto_impl;

Diff for: src/analyze.rs

+18-7
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
use std::collections::HashSet;
22

3-
use proc_macro::Span;
43
use proc_macro2::Span as Span2;
54
use syn::{
65
Ident, ItemTrait, Lifetime, Block,
@@ -38,7 +37,7 @@ const PROXY_LT_PARAM_NAME: &str = "'__auto_impl_proxy_lifetime";
3837
/// name, we'll use the ugly `PROXY_TY_PARAM_NAME` and `PROXY_LT_PARAM_NAME`.
3938
///
4039
/// This method returns two idents: (type_parameter, lifetime_parameter).
41-
pub(crate) fn find_suitable_param_names(trait_def: &ItemTrait) -> (Ident, Lifetime) {
40+
crate fn find_suitable_param_names(trait_def: &ItemTrait) -> (Ident, Lifetime) {
4241
// Define the visitor that just collects names
4342
struct IdentCollector<'ast> {
4443
ty_names: HashSet<&'ast Ident>,
@@ -71,11 +70,6 @@ pub(crate) fn find_suitable_param_names(trait_def: &ItemTrait) -> (Ident, Lifeti
7170
visit_item_trait(&mut visitor, trait_def);
7271

7372

74-
fn param_span() -> Span2 {
75-
// TODO: change for stable builds
76-
Span::def_site().into()
77-
}
78-
7973
fn char_to_ident(c: u8) -> Ident {
8074
let arr = [c];
8175
let s = ::std::str::from_utf8(&arr).unwrap();
@@ -101,3 +95,20 @@ pub(crate) fn find_suitable_param_names(trait_def: &ItemTrait) -> (Ident, Lifeti
10195

10296
(ty_name, lt)
10397
}
98+
99+
/// On nightly, we use `def_site` hygiene which puts our names into another
100+
/// universe than the names of the user. This is not strictly required as our
101+
/// name is already pretty much guaranteed to not conflict with another name,
102+
/// but this is cleaner and just the correct thing to do.
103+
#[cfg(feature = "nightly")]
104+
fn param_span() -> Span2 {
105+
::proc_macro::Span::def_site().into()
106+
}
107+
108+
/// On stable, we use `call_site()` hygiene. That means that our names could
109+
/// theoretically collide with names of the user. But we made sure this doesn't
110+
/// happen.
111+
#[cfg(not(feature = "nightly"))]
112+
fn param_span() -> Span2 {
113+
Span2::call_site()
114+
}

Diff for: src/diag.rs

+168-3
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,26 @@
1-
use proc_macro::{Diagnostic, Span};
1+
//! This module has two purposes:
2+
//!
3+
//! 1. Provide the convenience method `emit_with_attr_note` and add it via
4+
//! extension trait to `Diagnostic`.
5+
//!
6+
//! 2. Make `Diagnostic` work on stable by providing an own `Diagnostic` type
7+
//! that prints the messages in a less-nice way. That way, other modules
8+
//! don't have to worry about the stable/nightly distinction. `SpanExt` is
9+
//! an extension trait that adds the `err()` method to the `Span` type. That
10+
//! method works exactly like `Span::error()` but returns "our"
11+
//! `Diagnostic`. Other modules can simply `use diag::SpanExt` and use
12+
//! `.err()` on spans.
13+
//!
214
15+
use proc_macro::{Span, TokenStream};
316

17+
18+
/// Extension trait that adds a convenience method to `Diagnostic`. This is
19+
/// simply to reduce duplicate code in other modules.
420
pub trait DiagnosticExt {
521
/// Helper function to add a note to the diagnostic (with a span pointing
6-
/// to the `auto_impl` attribute) and emit the error. Additionally,
7-
/// `Err(())` is always returned.
22+
/// to the `auto_impl` attribute) and emit the error. An `Err(())` is
23+
/// always returned.
824
fn emit_with_attr_note<T>(self) -> Result<T, ()>;
925
}
1026

@@ -16,3 +32,152 @@ impl DiagnosticExt for Diagnostic {
1632
Err(())
1733
}
1834
}
35+
36+
37+
// ==============================================================
38+
// Logic for stable/nightly mode starts here.
39+
//
40+
// First, we define a `Diagnostic` type. If we compile with the `nightly`
41+
// feature, it's simple a typedef to `proc_macro::Diagnostic`. If we don't
42+
// compile in nightly mode, we can't use that type, since it's still unstable.
43+
// So in that case, we define our own type that tries to mimic the original
44+
// `Diagnostic`.
45+
46+
#[cfg(feature = "nightly")]
47+
crate type Diagnostic = ::proc_macro::Diagnostic;
48+
49+
#[cfg(not(feature = "nightly"))]
50+
crate struct Diagnostic {
51+
span: Span,
52+
msg: String,
53+
}
54+
55+
// We provide the methods that `proc_macro::Diagnostic` also has here. Or
56+
// rather: we only implement the subset that this crate actually uses.
57+
//
58+
// When we're not on the nightly compiler, we can't show a nice error. So how
59+
// do we show the error then? The idea is to generate a token stream that
60+
// contains `compile_error!(msg)` macro invocations. This macro is part of the
61+
// standard library and emits `msg` as error. This is fairly useful for our
62+
// case. However, a big limitation is that we can only emit one message. So in
63+
// order to also show notes later added to the `Diagnostic`, we simply add
64+
// "note: {the_note}" to the error string. This is crude and ugly, but it
65+
// works.
66+
//
67+
// What about spans? Good question! Spans are important, but without a proper
68+
// `Diagnostic` API, we can't properly support spans on errors and notes. The
69+
// compiler will point to the `compile_error!()` invocation we generate. But we
70+
// can use another hack to improve the situation slightly! On the token stream
71+
// (containing `compile_error!()`) we generate, we can modify the spans of the
72+
// individual token trees. If we set all spans to the span the error originates
73+
// from, the compiler thinks that the `compile_error!()` code snippet has the
74+
// span from the actual error source. That means that the error message will
75+
// point to the actual error source!
76+
//
77+
// There is only a small problem: this only works when we get a proper span.
78+
// Sadly, on stable, we can only get correct spans for individual token trees,
79+
// not even token streams. We can't combine spans. As a consequence, spans are
80+
// only correct if they come directly from a `TokenTree`. In general, errors
81+
// coming from the `proxy` module have proper spans while errors from other
82+
// modules don't have proper spans (on stable!). "Not proper" means that the
83+
// span is simply `call_site()` -- it points to the `#[auto_impl()]` attribute.
84+
//
85+
// It could be worse, but it's simply true: for good error messages, nightly is
86+
// required.
87+
#[cfg(not(feature = "nightly"))]
88+
impl Diagnostic {
89+
crate fn note(mut self, msg: impl Into<String>) -> Diagnostic {
90+
self.msg += &format!("\n\nnote: {}", msg.into());
91+
self
92+
}
93+
94+
crate fn span_note(mut self, _: Span, msg: impl Into<String>) -> Diagnostic {
95+
// With out span fake method, we can only handle one span. We take the
96+
// one of the original error and ignore additional ones.
97+
self.msg += &format!("\n\nnote: {}", msg.into());
98+
self
99+
}
100+
101+
crate fn emit(self) {
102+
// Create the error token stream that contains the `compile_error!()`
103+
// invocation.
104+
let msg = &self.msg;
105+
let tokens = TokenStream::from(quote! {
106+
compile_error!(#msg);
107+
});
108+
109+
// Set the span of each token tree to the span the error originates
110+
// from.
111+
let tokens = tokens.into_iter()
112+
.map(|mut tt| {
113+
tt.set_span(self.span);
114+
tt
115+
})
116+
.collect();
117+
118+
// Push it to the global list of error streams
119+
ERROR_TOKENS.with(|toks| {
120+
toks.borrow_mut().push(tokens)
121+
});
122+
}
123+
}
124+
125+
// Another problem with our `Diagnostic` hack on stable: the real
126+
// `Diagnostic::emit()` doesn't return anything and modifies global state (it
127+
// prints directly to stdout). We can't simply print! In our case it would be
128+
// correct to pass a `TokenStream` ass the `Err()` variant of a result back up
129+
// the stack and display it at the end. Two problems with that approach:
130+
//
131+
// - That's not how this application was build. Instead, it's build with the
132+
// future `proc_macro` API in mind. And we wouldn't want to change everything
133+
// back once it's stable.
134+
// - On nightly, we don't want to pass TokenStreams up the stack. We can't have
135+
// a completely different structure on nightly vs. on stable.
136+
//
137+
// Thus, we just "simulate" the original `emit()` by also modifying global
138+
// state. We simply have a list of error token streams. This list is added to
139+
// the final token stream at the end (in case of an error). It's not a very
140+
// nice solution, but it's only a hack while stable doesn't offer something
141+
// proper.
142+
#[cfg(not(feature = "nightly"))]
143+
use std::cell::RefCell;
144+
145+
#[cfg(not(feature = "nightly"))]
146+
thread_local! {
147+
static ERROR_TOKENS: RefCell<Vec<TokenStream>> = RefCell::new(vec![]);
148+
}
149+
150+
/// On stable, we just copy the error token streams from the global variable.
151+
#[cfg(not(feature = "nightly"))]
152+
crate fn error_tokens() -> TokenStream {
153+
ERROR_TOKENS.with(|toks| toks.borrow().iter().cloned().collect())
154+
}
155+
156+
/// On nightly, we don't use and don't have a strange global variable. Instead,
157+
/// we just return an empty token stream. That's not a problem because all of
158+
/// our errors were already printed.
159+
#[cfg(feature = "nightly")]
160+
crate fn error_tokens() -> TokenStream {
161+
TokenStream::new()
162+
}
163+
164+
/// Extension trait to add the `err()` method to `Span`. This makes it easy to
165+
/// start a `Diagnostic` from a span.
166+
crate trait SpanExt {
167+
fn err(self, msg: impl Into<String>) -> Diagnostic;
168+
}
169+
170+
impl SpanExt for Span {
171+
#[cfg(feature = "nightly")]
172+
fn err(self, msg: impl Into<String>) -> Diagnostic {
173+
Diagnostic::spanned(self, ::proc_macro::Level::Error, msg)
174+
}
175+
176+
#[cfg(not(feature = "nightly"))]
177+
fn err(self, msg: impl Into<String>) -> Diagnostic {
178+
Diagnostic {
179+
span: self,
180+
msg: msg.into(),
181+
}
182+
}
183+
}

0 commit comments

Comments
 (0)