Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Guard unstable proc_macro things behind nightly feature #26

Merged
merged 13 commits into from
Aug 23, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,13 @@ language: rust
rust:
- nightly

script:
- cargo build --verbose
- cargo test --verbose
- cargo build --verbose --features=nightly
- cargo test --verbose --features=nightly

env:
- RUST_FLAGS="--deny warnings"

cache: cargo
9 changes: 8 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
cargo-features = ["edition"]

[package]
name = "auto_impl"
version = "0.2.0"
Expand All @@ -13,15 +15,20 @@ keywords = ["plugin"]
categories = ["development-tools"]
readme = "README.md"
autotests = true
edition = '2018'

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


[lib]
proc-macro = true

[features]
nightly = ["proc-macro2/nightly"]

[dependencies]
proc-macro2 = { version = "0.4.6", features = ["nightly"] }
proc-macro2 = { version = "0.4.6" }
quote = "0.6.3"
syn = { version = "0.14.4", features = ["full"] }

Expand Down
30 changes: 30 additions & 0 deletions examples/error_messages.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
//! This file showcases a few error messages emitted by `auto_impl`. You have
//! to add specific lines to see the error. Then simply compile with:
//!
//! ```
//! cargo build --example error_messages
//! ```
//!
//! If you want to see nicer error messages, add `--features=nightly`.
#![allow(unused_imports, dead_code)]

use auto_impl::auto_impl;


// Shows the error message for the case that `#[auto_impl]` was used with
// incorrect proxy types. Only proxy types like `&` and `Box` are allowed. Add
// this next line to see the error!
//#[auto_impl(Boxxi)]
trait Foo {
fn foo(&self) -> u32;
}

// Shows the error message for the case the `#[auto_impl]` wasn't applied to a
// valid trait (in this case a struct). Add this next line to see the error!
//#[auto_impl(&, Box)]
struct Bar {
x: u32,
}


fn main() {}
4 changes: 0 additions & 4 deletions examples/greet_closure.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,3 @@
#![feature(use_extern_macros)]

extern crate auto_impl;

use auto_impl::auto_impl;


Expand Down
2 changes: 0 additions & 2 deletions examples/names.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,7 @@

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

extern crate auto_impl;
use auto_impl::auto_impl;


Expand Down
4 changes: 0 additions & 4 deletions examples/refs.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,3 @@
#![feature(use_extern_macros)]

extern crate auto_impl;

use std::fmt::Display;

use auto_impl::auto_impl;
Expand Down
25 changes: 18 additions & 7 deletions src/analyze.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
use std::collections::HashSet;

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


fn param_span() -> Span2 {
// TODO: change for stable builds
Span::def_site().into()
}

fn char_to_ident(c: u8) -> Ident {
let arr = [c];
let s = ::std::str::from_utf8(&arr).unwrap();
Expand All @@ -101,3 +95,20 @@ pub(crate) fn find_suitable_param_names(trait_def: &ItemTrait) -> (Ident, Lifeti

(ty_name, lt)
}

/// On nightly, we use `def_site` hygiene which puts our names into another
/// universe than the names of the user. This is not strictly required as our
/// name is already pretty much guaranteed to not conflict with another name,
/// but this is cleaner and just the correct thing to do.
#[cfg(feature = "nightly")]
fn param_span() -> Span2 {
::proc_macro::Span::def_site().into()
}

/// On stable, we use `call_site()` hygiene. That means that our names could
/// theoretically collide with names of the user. But we made sure this doesn't
/// happen.
#[cfg(not(feature = "nightly"))]
fn param_span() -> Span2 {
Span2::call_site()
}
171 changes: 168 additions & 3 deletions src/diag.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,26 @@
use proc_macro::{Diagnostic, Span};
//! This module has two purposes:
//!
//! 1. Provide the convenience method `emit_with_attr_note` and add it via
//! extension trait to `Diagnostic`.
//!
//! 2. Make `Diagnostic` work on stable by providing an own `Diagnostic` type
//! that prints the messages in a less-nice way. That way, other modules
//! don't have to worry about the stable/nightly distinction. `SpanExt` is
//! an extension trait that adds the `err()` method to the `Span` type. That
//! method works exactly like `Span::error()` but returns "our"
//! `Diagnostic`. Other modules can simply `use diag::SpanExt` and use
//! `.err()` on spans.
//!

use proc_macro::{Span, TokenStream};


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

Expand All @@ -16,3 +32,152 @@ impl DiagnosticExt for Diagnostic {
Err(())
}
}


// ==============================================================
// Logic for stable/nightly mode starts here.
//
// First, we define a `Diagnostic` type. If we compile with the `nightly`
// feature, it's simple a typedef to `proc_macro::Diagnostic`. If we don't
// compile in nightly mode, we can't use that type, since it's still unstable.
// So in that case, we define our own type that tries to mimic the original
// `Diagnostic`.

#[cfg(feature = "nightly")]
crate type Diagnostic = ::proc_macro::Diagnostic;

#[cfg(not(feature = "nightly"))]
crate struct Diagnostic {
span: Span,
msg: String,
}

// We provide the methods that `proc_macro::Diagnostic` also has here. Or
// rather: we only implement the subset that this crate actually uses.
//
// When we're not on the nightly compiler, we can't show a nice error. So how
// do we show the error then? The idea is to generate a token stream that
// contains `compile_error!(msg)` macro invocations. This macro is part of the
// standard library and emits `msg` as error. This is fairly useful for our
// case. However, a big limitation is that we can only emit one message. So in
// order to also show notes later added to the `Diagnostic`, we simply add
// "note: {the_note}" to the error string. This is crude and ugly, but it
// works.
//
// What about spans? Good question! Spans are important, but without a proper
// `Diagnostic` API, we can't properly support spans on errors and notes. The
// compiler will point to the `compile_error!()` invocation we generate. But we
// can use another hack to improve the situation slightly! On the token stream
// (containing `compile_error!()`) we generate, we can modify the spans of the
// individual token trees. If we set all spans to the span the error originates
// from, the compiler thinks that the `compile_error!()` code snippet has the
// span from the actual error source. That means that the error message will
// point to the actual error source!
//
// There is only a small problem: this only works when we get a proper span.
// Sadly, on stable, we can only get correct spans for individual token trees,
// not even token streams. We can't combine spans. As a consequence, spans are
// only correct if they come directly from a `TokenTree`. In general, errors
// coming from the `proxy` module have proper spans while errors from other
// modules don't have proper spans (on stable!). "Not proper" means that the
// span is simply `call_site()` -- it points to the `#[auto_impl()]` attribute.
//
// It could be worse, but it's simply true: for good error messages, nightly is
// required.
#[cfg(not(feature = "nightly"))]
impl Diagnostic {
crate fn note(mut self, msg: impl Into<String>) -> Diagnostic {
self.msg += &format!("\n\nnote: {}", msg.into());
self
}

crate fn span_note(mut self, _: Span, msg: impl Into<String>) -> Diagnostic {
// With out span fake method, we can only handle one span. We take the
// one of the original error and ignore additional ones.
self.msg += &format!("\n\nnote: {}", msg.into());
self
}

crate fn emit(self) {
// Create the error token stream that contains the `compile_error!()`
// invocation.
let msg = &self.msg;
let tokens = TokenStream::from(quote! {
compile_error!(#msg);
});

// Set the span of each token tree to the span the error originates
// from.
let tokens = tokens.into_iter()
.map(|mut tt| {
tt.set_span(self.span);
tt
})
.collect();

// Push it to the global list of error streams
ERROR_TOKENS.with(|toks| {
toks.borrow_mut().push(tokens)
});
}
}

// Another problem with our `Diagnostic` hack on stable: the real
// `Diagnostic::emit()` doesn't return anything and modifies global state (it
// prints directly to stdout). We can't simply print! In our case it would be
// correct to pass a `TokenStream` ass the `Err()` variant of a result back up
// the stack and display it at the end. Two problems with that approach:
//
// - That's not how this application was build. Instead, it's build with the
// future `proc_macro` API in mind. And we wouldn't want to change everything
// back once it's stable.
// - On nightly, we don't want to pass TokenStreams up the stack. We can't have
// a completely different structure on nightly vs. on stable.
//
// Thus, we just "simulate" the original `emit()` by also modifying global
// state. We simply have a list of error token streams. This list is added to
// the final token stream at the end (in case of an error). It's not a very
// nice solution, but it's only a hack while stable doesn't offer something
// proper.
#[cfg(not(feature = "nightly"))]
use std::cell::RefCell;

#[cfg(not(feature = "nightly"))]
thread_local! {
static ERROR_TOKENS: RefCell<Vec<TokenStream>> = RefCell::new(vec![]);
}

/// On stable, we just copy the error token streams from the global variable.
#[cfg(not(feature = "nightly"))]
crate fn error_tokens() -> TokenStream {
ERROR_TOKENS.with(|toks| toks.borrow().iter().cloned().collect())
}

/// On nightly, we don't use and don't have a strange global variable. Instead,
/// we just return an empty token stream. That's not a problem because all of
/// our errors were already printed.
#[cfg(feature = "nightly")]
crate fn error_tokens() -> TokenStream {
TokenStream::new()
}

/// Extension trait to add the `err()` method to `Span`. This makes it easy to
/// start a `Diagnostic` from a span.
crate trait SpanExt {
fn err(self, msg: impl Into<String>) -> Diagnostic;
}

impl SpanExt for Span {
#[cfg(feature = "nightly")]
fn err(self, msg: impl Into<String>) -> Diagnostic {
Diagnostic::spanned(self, ::proc_macro::Level::Error, msg)
}

#[cfg(not(feature = "nightly"))]
fn err(self, msg: impl Into<String>) -> Diagnostic {
Diagnostic {
span: self,
msg: msg.into(),
}
}
}
Loading