mirror of
https://github.com/rustwasm/wasm-bindgen.git
synced 2024-11-24 06:33:33 +03:00
Prepare to have targeted error diagnostics (#604)
This commit starts to add infrastructure for targeted diagnostics in the `#[wasm_bindgen]` attribute, intended eventually at providing much better errors as they'll be pointing to exactly the code in question rather than always to a `#[wasm_bindgen]` attribute. The general changes are are: * A new `Diagnostic` error type is added to the backend. A `Diagnostic` is created with a textual error or with a span, and it can also be created from a list of diagnostics. A `Diagnostic` implements `ToTokens` which emits a bunch of invocations of `compile_error!` that will cause rustc to later generate errors. * Fallible implementations of `ToTokens` have switched to using a new trait, `TryToTokens`, which returns a `Result` to use `?` with. * The `MacroParse` trait has changed to returning a `Result` to propagate errors upwards. * A new `ui-tests` crate was added which uses `compiletest_rs` to add UI tests. These UI tests will verify that our output improves over time and does not regress. This test suite is added to CI as a new builder as well. * No `Diagnostic` instances are created just yet, everything continues to panic and return `Ok`, with the one exception of the top-level invocations of `syn::parse` which now create a `Diagnostic` and pass it along. This commit does not immediately improve diagnostics but the intention is that it is laying the groundwork for improving diagnostics over time. It should ideally be much easier to contribute improved diagnostics after this commit! cc #601
This commit is contained in:
parent
fce687cf7b
commit
c4dcaee1b9
@ -117,6 +117,12 @@ matrix:
|
|||||||
- cargo test -p webidl-tests --target wasm32-unknown-unknown
|
- cargo test -p webidl-tests --target wasm32-unknown-unknown
|
||||||
if: branch = master
|
if: branch = master
|
||||||
|
|
||||||
|
# UI tests for the macro work just fine
|
||||||
|
- rust: nightly
|
||||||
|
env: JOB=macro-ui
|
||||||
|
script: cargo test -p ui-tests
|
||||||
|
if: branch = master
|
||||||
|
|
||||||
# Dist linux binary
|
# Dist linux binary
|
||||||
- rust: nightly
|
- rust: nightly
|
||||||
env: JOB=dist-linux TARGET=x86_64-unknown-linux-musl
|
env: JOB=dist-linux TARGET=x86_64-unknown-linux-musl
|
||||||
|
@ -44,6 +44,7 @@ members = [
|
|||||||
"crates/test",
|
"crates/test",
|
||||||
"crates/test/sample",
|
"crates/test/sample",
|
||||||
"crates/typescript",
|
"crates/typescript",
|
||||||
|
"crates/macro/ui-tests",
|
||||||
"crates/web-sys",
|
"crates/web-sys",
|
||||||
"crates/webidl",
|
"crates/webidl",
|
||||||
"crates/webidl-tests",
|
"crates/webidl-tests",
|
||||||
|
@ -2,6 +2,8 @@ use proc_macro2::{Ident, Span};
|
|||||||
use shared;
|
use shared;
|
||||||
use syn;
|
use syn;
|
||||||
|
|
||||||
|
use Diagnostic;
|
||||||
|
|
||||||
/// An abstract syntax tree representing a rust program. Contains
|
/// An abstract syntax tree representing a rust program. Contains
|
||||||
/// extra information for joining up this rust code with javascript.
|
/// extra information for joining up this rust code with javascript.
|
||||||
#[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq))]
|
#[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq))]
|
||||||
@ -223,15 +225,17 @@ pub enum ConstValue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Program {
|
impl Program {
|
||||||
pub(crate) fn shared(&self) -> shared::Program {
|
pub(crate) fn shared(&self) -> Result<shared::Program, Diagnostic> {
|
||||||
shared::Program {
|
Ok(shared::Program {
|
||||||
exports: self.exports.iter().map(|a| a.shared()).collect(),
|
exports: self.exports.iter().map(|a| a.shared()).collect(),
|
||||||
structs: self.structs.iter().map(|a| a.shared()).collect(),
|
structs: self.structs.iter().map(|a| a.shared()).collect(),
|
||||||
enums: self.enums.iter().map(|a| a.shared()).collect(),
|
enums: self.enums.iter().map(|a| a.shared()).collect(),
|
||||||
imports: self.imports.iter().map(|a| a.shared()).collect(),
|
imports: self.imports.iter()
|
||||||
|
.map(|a| a.shared())
|
||||||
|
.collect::<Result<_, Diagnostic>>()?,
|
||||||
version: shared::version(),
|
version: shared::version(),
|
||||||
schema_version: shared::SCHEMA_VERSION.to_string(),
|
schema_version: shared::SCHEMA_VERSION.to_string(),
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -305,7 +309,7 @@ impl Variant {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Import {
|
impl Import {
|
||||||
fn shared(&self) -> shared::Import {
|
fn shared(&self) -> Result<shared::Import, Diagnostic> {
|
||||||
match (&self.module, &self.version) {
|
match (&self.module, &self.version) {
|
||||||
(&Some(ref m), None) if m.starts_with("./") => {}
|
(&Some(ref m), None) if m.starts_with("./") => {}
|
||||||
(&Some(ref m), &Some(_)) if m.starts_with("./") => {
|
(&Some(ref m), &Some(_)) if m.starts_with("./") => {
|
||||||
@ -330,12 +334,12 @@ impl Import {
|
|||||||
}
|
}
|
||||||
(&None, &None) => {}
|
(&None, &None) => {}
|
||||||
}
|
}
|
||||||
shared::Import {
|
Ok(shared::Import {
|
||||||
module: self.module.clone(),
|
module: self.module.clone(),
|
||||||
version: self.version.clone(),
|
version: self.version.clone(),
|
||||||
js_namespace: self.js_namespace.as_ref().map(|s| s.to_string()),
|
js_namespace: self.js_namespace.as_ref().map(|s| s.to_string()),
|
||||||
kind: self.kind.shared(),
|
kind: self.kind.shared(),
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,19 +2,31 @@ use std::collections::HashSet;
|
|||||||
use std::sync::Mutex;
|
use std::sync::Mutex;
|
||||||
use std::sync::atomic::{AtomicUsize, Ordering, ATOMIC_USIZE_INIT};
|
use std::sync::atomic::{AtomicUsize, Ordering, ATOMIC_USIZE_INIT};
|
||||||
|
|
||||||
use ast;
|
|
||||||
use proc_macro2::{Ident, Literal, Span, TokenStream};
|
use proc_macro2::{Ident, Literal, Span, TokenStream};
|
||||||
use quote::ToTokens;
|
use quote::ToTokens;
|
||||||
use serde_json;
|
use serde_json;
|
||||||
use shared;
|
use shared;
|
||||||
use syn;
|
use syn;
|
||||||
|
|
||||||
|
use ast;
|
||||||
|
use Diagnostic;
|
||||||
use util::ShortHash;
|
use util::ShortHash;
|
||||||
|
|
||||||
impl ToTokens for ast::Program {
|
pub trait TryToTokens {
|
||||||
|
fn try_to_tokens(&self, tokens: &mut TokenStream) -> Result<(), Diagnostic>;
|
||||||
|
|
||||||
|
fn try_to_token_stream(&self) -> Result<TokenStream, Diagnostic> {
|
||||||
|
let mut tokens = TokenStream::new();
|
||||||
|
self.try_to_tokens(&mut tokens)?;
|
||||||
|
Ok(tokens)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryToTokens for ast::Program {
|
||||||
// Generate wrappers for all the items that we've found
|
// Generate wrappers for all the items that we've found
|
||||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
fn try_to_tokens(&self, tokens: &mut TokenStream) -> Result<(), Diagnostic> {
|
||||||
for export in self.exports.iter() {
|
for export in self.exports.iter() {
|
||||||
export.to_tokens(tokens);
|
export.try_to_tokens(tokens)?;
|
||||||
}
|
}
|
||||||
for s in self.structs.iter() {
|
for s in self.structs.iter() {
|
||||||
s.to_tokens(tokens);
|
s.to_tokens(tokens);
|
||||||
@ -30,13 +42,13 @@ impl ToTokens for ast::Program {
|
|||||||
|
|
||||||
if let Some(ns) = &i.js_namespace {
|
if let Some(ns) = &i.js_namespace {
|
||||||
if types.contains(ns) && i.kind.fits_on_impl() {
|
if types.contains(ns) && i.kind.fits_on_impl() {
|
||||||
let kind = &i.kind;
|
let kind = i.kind.try_to_token_stream()?;
|
||||||
(quote! { impl #ns { #kind } }).to_tokens(tokens);
|
(quote! { impl #ns { #kind } }).to_tokens(tokens);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
i.kind.to_tokens(tokens);
|
i.kind.try_to_tokens(tokens)?;
|
||||||
}
|
}
|
||||||
for e in self.enums.iter() {
|
for e in self.enums.iter() {
|
||||||
e.to_tokens(tokens);
|
e.to_tokens(tokens);
|
||||||
@ -60,7 +72,7 @@ impl ToTokens for ast::Program {
|
|||||||
);
|
);
|
||||||
let generated_static_name = Ident::new(&generated_static_name, Span::call_site());
|
let generated_static_name = Ident::new(&generated_static_name, Span::call_site());
|
||||||
|
|
||||||
let description = serde_json::to_string(&self.shared()).unwrap();
|
let description = serde_json::to_string(&self.shared()?).unwrap();
|
||||||
|
|
||||||
// Each JSON blob is prepended with the length of the JSON blob so when
|
// Each JSON blob is prepended with the length of the JSON blob so when
|
||||||
// all these sections are concatenated in the final wasm file we know
|
// all these sections are concatenated in the final wasm file we know
|
||||||
@ -83,6 +95,8 @@ impl ToTokens for ast::Program {
|
|||||||
pub static #generated_static_name: [u8; #generated_static_length] =
|
pub static #generated_static_name: [u8; #generated_static_length] =
|
||||||
*#generated_static_value;
|
*#generated_static_value;
|
||||||
}).to_tokens(tokens);
|
}).to_tokens(tokens);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -276,8 +290,10 @@ impl ToTokens for ast::StructField {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToTokens for ast::Export {
|
impl TryToTokens for ast::Export {
|
||||||
fn to_tokens(self: &ast::Export, into: &mut TokenStream) {
|
fn try_to_tokens(self: &ast::Export, into: &mut TokenStream)
|
||||||
|
-> Result<(), Diagnostic>
|
||||||
|
{
|
||||||
let generated_name = self.rust_symbol();
|
let generated_name = self.rust_symbol();
|
||||||
let export_name = self.export_name();
|
let export_name = self.export_name();
|
||||||
let mut args = vec![];
|
let mut args = vec![];
|
||||||
@ -461,17 +477,21 @@ impl ToTokens for ast::Export {
|
|||||||
#(<#argtys as WasmDescribe>::describe();)*
|
#(<#argtys as WasmDescribe>::describe();)*
|
||||||
#describe_ret
|
#describe_ret
|
||||||
}).to_tokens(into);
|
}).to_tokens(into);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToTokens for ast::ImportKind {
|
impl TryToTokens for ast::ImportKind {
|
||||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
fn try_to_tokens(&self, tokens: &mut TokenStream) -> Result<(), Diagnostic> {
|
||||||
match *self {
|
match *self {
|
||||||
ast::ImportKind::Function(ref f) => f.to_tokens(tokens),
|
ast::ImportKind::Function(ref f) => f.try_to_tokens(tokens)?,
|
||||||
ast::ImportKind::Static(ref s) => s.to_tokens(tokens),
|
ast::ImportKind::Static(ref s) => s.to_tokens(tokens),
|
||||||
ast::ImportKind::Type(ref t) => t.to_tokens(tokens),
|
ast::ImportKind::Type(ref t) => t.to_tokens(tokens),
|
||||||
ast::ImportKind::Enum(ref e) => e.to_tokens(tokens),
|
ast::ImportKind::Enum(ref e) => e.to_tokens(tokens),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -663,8 +683,8 @@ impl ToTokens for ast::ImportEnum {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToTokens for ast::ImportFunction {
|
impl TryToTokens for ast::ImportFunction {
|
||||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
fn try_to_tokens(&self, tokens: &mut TokenStream) -> Result<(), Diagnostic> {
|
||||||
let mut class_ty = None;
|
let mut class_ty = None;
|
||||||
let mut is_method = false;
|
let mut is_method = false;
|
||||||
match self.kind {
|
match self.kind {
|
||||||
@ -827,6 +847,8 @@ impl ToTokens for ast::ImportFunction {
|
|||||||
} else {
|
} else {
|
||||||
invocation.to_tokens(tokens);
|
invocation.to_tokens(tokens);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
83
crates/backend/src/error.rs
Normal file
83
crates/backend/src/error.rs
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
use proc_macro2::*;
|
||||||
|
use quote::ToTokens;
|
||||||
|
|
||||||
|
pub struct Diagnostic {
|
||||||
|
inner: Repr,
|
||||||
|
}
|
||||||
|
enum Repr {
|
||||||
|
Single {
|
||||||
|
text: String,
|
||||||
|
span: Option<(Span, Span)>,
|
||||||
|
},
|
||||||
|
Multi {
|
||||||
|
diagnostics: Vec<Diagnostic>,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Diagnostic {
|
||||||
|
pub fn error<T: Into<String>>(text: T) -> Diagnostic {
|
||||||
|
Diagnostic {
|
||||||
|
inner: Repr::Single {
|
||||||
|
text: text.into(),
|
||||||
|
span: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn span_error<T: Into<String>>(node: &ToTokens, text: T) -> Diagnostic {
|
||||||
|
Diagnostic {
|
||||||
|
inner: Repr::Single {
|
||||||
|
text: text.into(),
|
||||||
|
span: extract_spans(node),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_vec(diagnostics: Vec<Diagnostic>) -> Result<(), Diagnostic> {
|
||||||
|
if diagnostics.len() == 0 {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(Diagnostic { inner: Repr::Multi { diagnostics } })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(unconditional_recursion)]
|
||||||
|
pub fn panic(&self) -> ! {
|
||||||
|
match &self.inner {
|
||||||
|
Repr::Single { text, .. } => panic!("{}", text),
|
||||||
|
Repr::Multi { diagnostics } => diagnostics[0].panic(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn extract_spans(node: &ToTokens) -> Option<(Span, Span)> {
|
||||||
|
let mut t = TokenStream::new();
|
||||||
|
node.to_tokens(&mut t);
|
||||||
|
let mut tokens = t.into_iter();
|
||||||
|
let start = tokens.next().map(|t| t.span());
|
||||||
|
let end = tokens.last().map(|t| t.span());
|
||||||
|
start.map(|start| (start, end.unwrap_or(start)))
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToTokens for Diagnostic {
|
||||||
|
fn to_tokens(&self, dst: &mut TokenStream) {
|
||||||
|
match &self.inner {
|
||||||
|
Repr::Single { text, span } => {
|
||||||
|
let cs2 = (Span::call_site(), Span::call_site());
|
||||||
|
let (start, end) = span.unwrap_or(cs2);
|
||||||
|
dst.extend(Some(Ident::new("compile_error", start).into()));
|
||||||
|
dst.extend(Some(Punct::new('!', Spacing::Alone).into()));
|
||||||
|
let mut message = TokenStream::new();
|
||||||
|
message.extend(Some(Literal::string(text).into()));
|
||||||
|
let mut group = Group::new(Delimiter::Brace, message);
|
||||||
|
group.set_span(end);
|
||||||
|
dst.extend(Some(group.into()));
|
||||||
|
}
|
||||||
|
Repr::Multi { diagnostics } => {
|
||||||
|
for diagnostic in diagnostics {
|
||||||
|
diagnostic.to_tokens(dst);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -14,7 +14,11 @@ extern crate syn;
|
|||||||
|
|
||||||
extern crate wasm_bindgen_shared as shared;
|
extern crate wasm_bindgen_shared as shared;
|
||||||
|
|
||||||
|
pub use codegen::TryToTokens;
|
||||||
|
pub use error::Diagnostic;
|
||||||
|
|
||||||
pub mod ast;
|
pub mod ast;
|
||||||
mod codegen;
|
mod codegen;
|
||||||
pub mod defined;
|
pub mod defined;
|
||||||
|
mod error;
|
||||||
pub mod util;
|
pub mod util;
|
||||||
|
@ -12,15 +12,26 @@ extern crate wasm_bindgen_shared as shared;
|
|||||||
|
|
||||||
pub use parser::BindgenAttrs;
|
pub use parser::BindgenAttrs;
|
||||||
use parser::MacroParse;
|
use parser::MacroParse;
|
||||||
use quote::ToTokens;
|
use backend::{Diagnostic, TryToTokens};
|
||||||
|
use proc_macro2::TokenStream;
|
||||||
|
|
||||||
mod parser;
|
mod parser;
|
||||||
|
|
||||||
/// Takes the parsed input from a `#[wasm_bindgen]` macro and returns the generated bindings
|
/// Takes the parsed input from a `#[wasm_bindgen]` macro and returns the generated bindings
|
||||||
pub fn expand(item: syn::Item, opts: parser::BindgenAttrs) -> proc_macro2::TokenStream {
|
pub fn expand(attr: TokenStream, input: TokenStream) -> Result<TokenStream, Diagnostic> {
|
||||||
|
let item = syn_parse::<syn::Item>(input, "rust item")?;
|
||||||
|
let opts = syn_parse(attr, "#[wasm_bindgen] attribute options")?;
|
||||||
|
|
||||||
let mut tokens = proc_macro2::TokenStream::new();
|
let mut tokens = proc_macro2::TokenStream::new();
|
||||||
let mut program = backend::ast::Program::default();
|
let mut program = backend::ast::Program::default();
|
||||||
item.macro_parse(&mut program, (Some(opts), &mut tokens));
|
item.macro_parse(&mut program, (Some(opts), &mut tokens))?;
|
||||||
program.to_tokens(&mut tokens);
|
program.try_to_tokens(&mut tokens)?;
|
||||||
tokens
|
Ok(tokens)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn syn_parse<T: syn::synom::Synom>(tokens: TokenStream, name: &str) -> Result<T, Diagnostic> {
|
||||||
|
syn::parse2(tokens.clone())
|
||||||
|
.map_err(|err| {
|
||||||
|
Diagnostic::span_error(&tokens, format!("error parsing {}: {}", name, err))
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
use backend::ast;
|
use backend::ast;
|
||||||
|
use backend::Diagnostic;
|
||||||
use backend::util::{ident_ty, ShortHash};
|
use backend::util::{ident_ty, ShortHash};
|
||||||
use proc_macro2::{Ident, Span, TokenStream, TokenTree};
|
use proc_macro2::{Ident, Span, TokenStream, TokenTree};
|
||||||
use quote::ToTokens;
|
use quote::ToTokens;
|
||||||
@ -590,7 +591,8 @@ pub(crate) trait MacroParse<Ctx> {
|
|||||||
///
|
///
|
||||||
/// The context is used to have access to the attributes on `#[wasm_bindgen]`, and to allow
|
/// The context is used to have access to the attributes on `#[wasm_bindgen]`, and to allow
|
||||||
/// writing to the output `TokenStream`.
|
/// writing to the output `TokenStream`.
|
||||||
fn macro_parse(self, program: &mut ast::Program, context: Ctx);
|
fn macro_parse(self, program: &mut ast::Program, context: Ctx)
|
||||||
|
-> Result<(), Diagnostic>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> MacroParse<(Option<BindgenAttrs>, &'a mut TokenStream)> for syn::Item {
|
impl<'a> MacroParse<(Option<BindgenAttrs>, &'a mut TokenStream)> for syn::Item {
|
||||||
@ -598,7 +600,7 @@ impl<'a> MacroParse<(Option<BindgenAttrs>, &'a mut TokenStream)> for syn::Item {
|
|||||||
self,
|
self,
|
||||||
program: &mut ast::Program,
|
program: &mut ast::Program,
|
||||||
(opts, tokens): (Option<BindgenAttrs>, &'a mut TokenStream),
|
(opts, tokens): (Option<BindgenAttrs>, &'a mut TokenStream),
|
||||||
) {
|
) -> Result<(), Diagnostic> {
|
||||||
match self {
|
match self {
|
||||||
syn::Item::Fn(mut f) => {
|
syn::Item::Fn(mut f) => {
|
||||||
let no_mangle = f
|
let no_mangle = f
|
||||||
@ -629,27 +631,31 @@ impl<'a> MacroParse<(Option<BindgenAttrs>, &'a mut TokenStream)> for syn::Item {
|
|||||||
s.to_tokens(tokens);
|
s.to_tokens(tokens);
|
||||||
}
|
}
|
||||||
syn::Item::Impl(mut i) => {
|
syn::Item::Impl(mut i) => {
|
||||||
(&mut i).macro_parse(program, ());
|
(&mut i).macro_parse(program, ())?;
|
||||||
i.to_tokens(tokens);
|
i.to_tokens(tokens);
|
||||||
}
|
}
|
||||||
syn::Item::ForeignMod(mut f) => {
|
syn::Item::ForeignMod(mut f) => {
|
||||||
let opts = opts.unwrap_or_else(|| BindgenAttrs::find(&mut f.attrs));
|
let opts = opts.unwrap_or_else(|| BindgenAttrs::find(&mut f.attrs));
|
||||||
f.macro_parse(program, opts);
|
f.macro_parse(program, opts)?;
|
||||||
}
|
}
|
||||||
syn::Item::Enum(e) => {
|
syn::Item::Enum(e) => {
|
||||||
e.to_tokens(tokens);
|
e.to_tokens(tokens);
|
||||||
e.macro_parse(program, ());
|
e.macro_parse(program, ())?;
|
||||||
}
|
}
|
||||||
_ => panic!(
|
_ => panic!(
|
||||||
"#[wasm_bindgen] can only be applied to a function, \
|
"#[wasm_bindgen] can only be applied to a function, \
|
||||||
struct, enum, impl, or extern block"
|
struct, enum, impl, or extern block"
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> MacroParse<()> for &'a mut syn::ItemImpl {
|
impl<'a> MacroParse<()> for &'a mut syn::ItemImpl {
|
||||||
fn macro_parse(self, program: &mut ast::Program, (): ()) {
|
fn macro_parse(self, program: &mut ast::Program, (): ())
|
||||||
|
-> Result<(), Diagnostic>
|
||||||
|
{
|
||||||
if self.defaultness.is_some() {
|
if self.defaultness.is_some() {
|
||||||
panic!("default impls are not supported");
|
panic!("default impls are not supported");
|
||||||
}
|
}
|
||||||
@ -672,14 +678,20 @@ impl<'a> MacroParse<()> for &'a mut syn::ItemImpl {
|
|||||||
},
|
},
|
||||||
_ => panic!("unsupported self type in impl"),
|
_ => panic!("unsupported self type in impl"),
|
||||||
};
|
};
|
||||||
|
let mut errors = Vec::new();
|
||||||
for item in self.items.iter_mut() {
|
for item in self.items.iter_mut() {
|
||||||
(&name, item).macro_parse(program, ())
|
if let Err(e) = (&name, item).macro_parse(program, ()) {
|
||||||
|
errors.push(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Diagnostic::from_vec(errors)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, 'b> MacroParse<()> for (&'a Ident, &'b mut syn::ImplItem) {
|
impl<'a, 'b> MacroParse<()> for (&'a Ident, &'b mut syn::ImplItem) {
|
||||||
fn macro_parse(self, program: &mut ast::Program, (): ()) {
|
fn macro_parse(self, program: &mut ast::Program, (): ())
|
||||||
|
-> Result<(), Diagnostic>
|
||||||
|
{
|
||||||
let (class, item) = self;
|
let (class, item) = self;
|
||||||
replace_self(class, item);
|
replace_self(class, item);
|
||||||
let method = match item {
|
let method = match item {
|
||||||
@ -691,7 +703,7 @@ impl<'a, 'b> MacroParse<()> for (&'a Ident, &'b mut syn::ImplItem) {
|
|||||||
};
|
};
|
||||||
match method.vis {
|
match method.vis {
|
||||||
syn::Visibility::Public(_) => {}
|
syn::Visibility::Public(_) => {}
|
||||||
_ => return,
|
_ => return Ok(()),
|
||||||
}
|
}
|
||||||
if method.defaultness.is_some() {
|
if method.defaultness.is_some() {
|
||||||
panic!("default methods are not supported");
|
panic!("default methods are not supported");
|
||||||
@ -728,11 +740,14 @@ impl<'a, 'b> MacroParse<()> for (&'a Ident, &'b mut syn::ImplItem) {
|
|||||||
comments,
|
comments,
|
||||||
rust_name: method.sig.ident.clone(),
|
rust_name: method.sig.ident.clone(),
|
||||||
});
|
});
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MacroParse<()> for syn::ItemEnum {
|
impl MacroParse<()> for syn::ItemEnum {
|
||||||
fn macro_parse(self, program: &mut ast::Program, (): ()) {
|
fn macro_parse(self, program: &mut ast::Program, (): ())
|
||||||
|
-> Result<(), Diagnostic>
|
||||||
|
{
|
||||||
match self.vis {
|
match self.vis {
|
||||||
syn::Visibility::Public(_) => {}
|
syn::Visibility::Public(_) => {}
|
||||||
_ => panic!("only public enums are allowed"),
|
_ => panic!("only public enums are allowed"),
|
||||||
@ -776,11 +791,14 @@ impl MacroParse<()> for syn::ItemEnum {
|
|||||||
variants,
|
variants,
|
||||||
comments,
|
comments,
|
||||||
});
|
});
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MacroParse<BindgenAttrs> for syn::ItemForeignMod {
|
impl MacroParse<BindgenAttrs> for syn::ItemForeignMod {
|
||||||
fn macro_parse(self, program: &mut ast::Program, opts: BindgenAttrs) {
|
fn macro_parse(self, program: &mut ast::Program, opts: BindgenAttrs)
|
||||||
|
-> Result<(), Diagnostic>
|
||||||
|
{
|
||||||
match self.abi.name {
|
match self.abi.name {
|
||||||
Some(ref l) if l.value() == "C" => {}
|
Some(ref l) if l.value() == "C" => {}
|
||||||
None => {}
|
None => {}
|
||||||
@ -816,6 +834,7 @@ impl MacroParse<BindgenAttrs> for syn::ItemForeignMod {
|
|||||||
kind,
|
kind,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,10 +14,9 @@ Definition of the `#[wasm_bindgen]` attribute, an internal dependency
|
|||||||
proc-macro = true
|
proc-macro = true
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
spans = ["proc-macro2/nightly", "wasm-bindgen-macro-support/spans"]
|
spans = ["wasm-bindgen-macro-support/spans"]
|
||||||
xxx_debug_only_print_generated_code = []
|
xxx_debug_only_print_generated_code = []
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
syn = { version = '0.14', features = ['full'] }
|
|
||||||
proc-macro2 = "0.4.9"
|
|
||||||
wasm-bindgen-macro-support = { path = "../macro-support", version = "=0.2.15" }
|
wasm-bindgen-macro-support = { path = "../macro-support", version = "=0.2.15" }
|
||||||
|
quote = "0.6"
|
||||||
|
28
crates/macro/README.md
Normal file
28
crates/macro/README.md
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
# `wasm-bindgen-macro`
|
||||||
|
|
||||||
|
Implementation of the `#[wasm_bindgen]` attribute. See the `wasm-bindgen`
|
||||||
|
documentation for more information about what this macro does.
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
Testing of this macro is done through "ui tests" in the `ui-tests` subdirectory
|
||||||
|
of this crate. Each Rust file in this folder is compiled with the `wasm_bindgen`
|
||||||
|
crate, and the `*.stderr` file sitting next to it is the asserted output of the
|
||||||
|
compiler. If the output matches, the test passes, and if the output doesn't
|
||||||
|
match the test fails. Note that it is also considered a failure if a test
|
||||||
|
actually compiles successfully.
|
||||||
|
|
||||||
|
To add a test:
|
||||||
|
|
||||||
|
* Create `ui-tests/my-awesome-test.rs`
|
||||||
|
* Write an invalid `#[wasm_bindgen]` invocation, testing the error you're
|
||||||
|
generating
|
||||||
|
* Execute `cargo test -p ui-tests`, the test will fail
|
||||||
|
* From within the `ui-tests` folder, execute `./update-all-references.sh`. This
|
||||||
|
should create a `my-awesome-test.stderr` file.
|
||||||
|
* Inspect `my-awesome-test.stderr` to make sure it looks ok
|
||||||
|
* Rerun `cargo test -p ui-tests` and your tests should pass!
|
||||||
|
|
||||||
|
Testing here is a work in progress, see
|
||||||
|
[#601](https://github.com/rustwasm/wasm-bindgen/issues/601) for more
|
||||||
|
information.
|
@ -1,23 +1,21 @@
|
|||||||
#![doc(html_root_url = "https://docs.rs/wasm-bindgen-macro/0.2")]
|
#![doc(html_root_url = "https://docs.rs/wasm-bindgen-macro/0.2")]
|
||||||
|
|
||||||
extern crate proc_macro;
|
extern crate proc_macro;
|
||||||
extern crate proc_macro2;
|
#[macro_use]
|
||||||
extern crate syn;
|
extern crate quote;
|
||||||
extern crate wasm_bindgen_macro_support as macro_support;
|
extern crate wasm_bindgen_macro_support as macro_support;
|
||||||
|
|
||||||
use macro_support::BindgenAttrs;
|
|
||||||
use proc_macro::TokenStream;
|
use proc_macro::TokenStream;
|
||||||
|
|
||||||
#[proc_macro_attribute]
|
#[proc_macro_attribute]
|
||||||
pub fn wasm_bindgen(attr: TokenStream, input: TokenStream) -> TokenStream {
|
pub fn wasm_bindgen(attr: TokenStream, input: TokenStream) -> TokenStream {
|
||||||
let item = syn::parse::<syn::Item>(input.clone()).expect("expected a valid Rust item");
|
match macro_support::expand(attr.into(), input.into()) {
|
||||||
let opts = syn::parse::<BindgenAttrs>(attr).expect("invalid arguments to #[wasm_bindgen]");
|
Ok(tokens) => {
|
||||||
|
if cfg!(feature = "xxx_debug_only_print_generated_code") {
|
||||||
let tokens = macro_support::expand(item, opts);
|
println!("{}", tokens);
|
||||||
|
}
|
||||||
if cfg!(feature = "xxx_debug_only_print_generated_code") {
|
tokens.into()
|
||||||
println!("{}", tokens);
|
}
|
||||||
|
Err(diagnostic) => (quote! { #diagnostic }).into(),
|
||||||
}
|
}
|
||||||
|
|
||||||
tokens.into()
|
|
||||||
}
|
}
|
||||||
|
15
crates/macro/ui-tests/Cargo.toml
Normal file
15
crates/macro/ui-tests/Cargo.toml
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
[package]
|
||||||
|
name = "ui-tests"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["The wasm-bindgen Authors"]
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
path = "test.rs"
|
||||||
|
doctest = false
|
||||||
|
harness = false
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
wasm-bindgen = { path = "../../.." }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
compiletest_rs = "0.3"
|
8
crates/macro/ui-tests/attribute-fails-to-parse.rs
Normal file
8
crates/macro/ui-tests/attribute-fails-to-parse.rs
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
#![feature(use_extern_macros)]
|
||||||
|
|
||||||
|
extern crate wasm_bindgen;
|
||||||
|
|
||||||
|
use wasm_bindgen::prelude::*;
|
||||||
|
|
||||||
|
#[wasm_bindgen(nonsense)]
|
||||||
|
pub fn foo() {}
|
8
crates/macro/ui-tests/attribute-fails-to-parse.stderr
Normal file
8
crates/macro/ui-tests/attribute-fails-to-parse.stderr
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
error: error parsing #[wasm_bindgen] attribute options: failed to parse anything
|
||||||
|
--> $DIR/attribute-fails-to-parse.rs:7:16
|
||||||
|
|
|
||||||
|
7 | #[wasm_bindgen(nonsense)]
|
||||||
|
| ^^^^^^^^
|
||||||
|
|
||||||
|
error: aborting due to previous error
|
||||||
|
|
8
crates/macro/ui-tests/non-public-function.rs
Normal file
8
crates/macro/ui-tests/non-public-function.rs
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
#![feature(use_extern_macros)]
|
||||||
|
|
||||||
|
extern crate wasm_bindgen;
|
||||||
|
|
||||||
|
use wasm_bindgen::prelude::*;
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
fn foo() {}
|
10
crates/macro/ui-tests/non-public-function.stderr
Normal file
10
crates/macro/ui-tests/non-public-function.stderr
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
error: custom attribute panicked
|
||||||
|
--> $DIR/non-public-function.rs:7:1
|
||||||
|
|
|
||||||
|
7 | #[wasm_bindgen]
|
||||||
|
| ^^^^^^^^^^^^^^^
|
||||||
|
|
|
||||||
|
= help: message: can only bindgen public functions
|
||||||
|
|
||||||
|
error: aborting due to previous error
|
||||||
|
|
25
crates/macro/ui-tests/test.rs
Normal file
25
crates/macro/ui-tests/test.rs
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
// ignore-test - not a test
|
||||||
|
|
||||||
|
#![cfg(test)]
|
||||||
|
|
||||||
|
extern crate compiletest_rs as compiletest;
|
||||||
|
|
||||||
|
use std::env;
|
||||||
|
use std::fs;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let mut config = compiletest::Config::default();
|
||||||
|
config.mode = "ui".parse().expect("invalid mode");
|
||||||
|
let mut me = env::current_exe().unwrap();
|
||||||
|
me.pop();
|
||||||
|
config.target_rustcflags = Some(format!("-L {}", me.display()));
|
||||||
|
let src = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
||||||
|
config.src_base = src;
|
||||||
|
|
||||||
|
me.pop();
|
||||||
|
me.pop();
|
||||||
|
config.build_base = me.join("tests/ui");
|
||||||
|
drop(fs::remove_dir_all(&config.build_base));
|
||||||
|
compiletest::run_tests(&config);
|
||||||
|
}
|
23
crates/macro/ui-tests/update-all-references.sh
Executable file
23
crates/macro/ui-tests/update-all-references.sh
Executable file
@ -0,0 +1,23 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
#
|
||||||
|
# Copyright 2015 The Rust Project Developers. See the COPYRIGHT
|
||||||
|
# file at the top-level directory of this distribution and at
|
||||||
|
# http://rust-lang.org/COPYRIGHT.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||||
|
# <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||||
|
# option. This file may not be copied, modified, or distributed
|
||||||
|
# except according to those terms.
|
||||||
|
|
||||||
|
# A script to update the references for all tests. The idea is that
|
||||||
|
# you do a run, which will generate files in the build directory
|
||||||
|
# containing the (normalized) actual output of the compiler. You then
|
||||||
|
# run this script, which will copy those files over. If you find
|
||||||
|
# yourself manually editing a foo.stderr file, you're doing it wrong.
|
||||||
|
#
|
||||||
|
# See all `update-references.sh`, if you just want to update a single test.
|
||||||
|
|
||||||
|
MY_DIR=$(dirname $0)
|
||||||
|
cd $MY_DIR
|
||||||
|
find . -name '*.rs' | xargs ./update-references.sh
|
42
crates/macro/ui-tests/update-references.sh
Executable file
42
crates/macro/ui-tests/update-references.sh
Executable file
@ -0,0 +1,42 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
#
|
||||||
|
# Copyright 2015 The Rust Project Developers. See the COPYRIGHT
|
||||||
|
# file at the top-level directory of this distribution and at
|
||||||
|
# http://rust-lang.org/COPYRIGHT.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||||
|
# <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||||
|
# option. This file may not be copied, modified, or distributed
|
||||||
|
# except according to those terms.
|
||||||
|
|
||||||
|
# A script to update the references for particular tests. The idea is
|
||||||
|
# that you do a run, which will generate files in the build directory
|
||||||
|
# containing the (normalized) actual output of the compiler. This
|
||||||
|
# script will then copy that output and replace the "expected output"
|
||||||
|
# files. You can then commit the changes.
|
||||||
|
#
|
||||||
|
# If you find yourself manually editing a foo.stderr file, you're
|
||||||
|
# doing it wrong.
|
||||||
|
|
||||||
|
MYDIR=$(dirname $0)
|
||||||
|
|
||||||
|
BUILD_DIR="../../../target/tests/ui"
|
||||||
|
|
||||||
|
while [[ "$1" != "" ]]; do
|
||||||
|
STDERR_NAME="${1/%.rs/.stderr}"
|
||||||
|
STDOUT_NAME="${1/%.rs/.stdout}"
|
||||||
|
shift
|
||||||
|
if [ -f $BUILD_DIR/$STDOUT_NAME ] && \
|
||||||
|
! (diff $BUILD_DIR/$STDOUT_NAME $MYDIR/$STDOUT_NAME >& /dev/null); then
|
||||||
|
echo updating $MYDIR/$STDOUT_NAME
|
||||||
|
cp $BUILD_DIR/$STDOUT_NAME $MYDIR/$STDOUT_NAME
|
||||||
|
fi
|
||||||
|
if [ -f $BUILD_DIR/$STDERR_NAME ] && \
|
||||||
|
! (diff $BUILD_DIR/$STDERR_NAME $MYDIR/$STDERR_NAME >& /dev/null); then
|
||||||
|
echo updating $MYDIR/$STDERR_NAME
|
||||||
|
cp $BUILD_DIR/$STDERR_NAME $MYDIR/$STDERR_NAME
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
|
@ -33,11 +33,11 @@ use std::io::{self, Read};
|
|||||||
use std::iter::FromIterator;
|
use std::iter::FromIterator;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
|
use backend::TryToTokens;
|
||||||
use backend::defined::{ImportedTypeDefinitions, RemoveUndefinedImports};
|
use backend::defined::{ImportedTypeDefinitions, RemoveUndefinedImports};
|
||||||
use backend::util::{ident_ty, rust_ident, wrap_import_function};
|
use backend::util::{ident_ty, rust_ident, wrap_import_function};
|
||||||
use failure::{ResultExt, Fail};
|
use failure::{ResultExt, Fail};
|
||||||
use heck::{ShoutySnakeCase};
|
use heck::{ShoutySnakeCase};
|
||||||
use quote::ToTokens;
|
|
||||||
|
|
||||||
use first_pass::{FirstPass, FirstPassRecord};
|
use first_pass::{FirstPass, FirstPassRecord};
|
||||||
use util::{public, webidl_const_ty_to_syn_ty, webidl_const_v_to_backend_const_v, TypePosition, camel_case_ident, mdn_doc};
|
use util::{public, webidl_const_ty_to_syn_ty, webidl_const_v_to_backend_const_v, TypePosition, camel_case_ident, mdn_doc};
|
||||||
@ -111,7 +111,9 @@ fn compile_ast(mut ast: backend::ast::Program) -> String {
|
|||||||
ast.remove_undefined_imports(&|id| defined.contains(id));
|
ast.remove_undefined_imports(&|id| defined.contains(id));
|
||||||
|
|
||||||
let mut tokens = proc_macro2::TokenStream::new();
|
let mut tokens = proc_macro2::TokenStream::new();
|
||||||
ast.to_tokens(&mut tokens);
|
if let Err(e) = ast.try_to_tokens(&mut tokens) {
|
||||||
|
e.panic();
|
||||||
|
}
|
||||||
tokens.to_string()
|
tokens.to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user