mirror of
https://github.com/fluencelabs/wasm-bindgen
synced 2025-06-12 12:31:22 +00: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:
@ -2,6 +2,8 @@ use proc_macro2::{Ident, Span};
|
||||
use shared;
|
||||
use syn;
|
||||
|
||||
use Diagnostic;
|
||||
|
||||
/// An abstract syntax tree representing a rust program. Contains
|
||||
/// extra information for joining up this rust code with javascript.
|
||||
#[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq))]
|
||||
@ -223,15 +225,17 @@ pub enum ConstValue {
|
||||
}
|
||||
|
||||
impl Program {
|
||||
pub(crate) fn shared(&self) -> shared::Program {
|
||||
shared::Program {
|
||||
pub(crate) fn shared(&self) -> Result<shared::Program, Diagnostic> {
|
||||
Ok(shared::Program {
|
||||
exports: self.exports.iter().map(|a| a.shared()).collect(),
|
||||
structs: self.structs.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(),
|
||||
schema_version: shared::SCHEMA_VERSION.to_string(),
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -305,7 +309,7 @@ impl Variant {
|
||||
}
|
||||
|
||||
impl Import {
|
||||
fn shared(&self) -> shared::Import {
|
||||
fn shared(&self) -> Result<shared::Import, Diagnostic> {
|
||||
match (&self.module, &self.version) {
|
||||
(&Some(ref m), None) if m.starts_with("./") => {}
|
||||
(&Some(ref m), &Some(_)) if m.starts_with("./") => {
|
||||
@ -330,12 +334,12 @@ impl Import {
|
||||
}
|
||||
(&None, &None) => {}
|
||||
}
|
||||
shared::Import {
|
||||
Ok(shared::Import {
|
||||
module: self.module.clone(),
|
||||
version: self.version.clone(),
|
||||
js_namespace: self.js_namespace.as_ref().map(|s| s.to_string()),
|
||||
kind: self.kind.shared(),
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,19 +2,31 @@ use std::collections::HashSet;
|
||||
use std::sync::Mutex;
|
||||
use std::sync::atomic::{AtomicUsize, Ordering, ATOMIC_USIZE_INIT};
|
||||
|
||||
use ast;
|
||||
use proc_macro2::{Ident, Literal, Span, TokenStream};
|
||||
use quote::ToTokens;
|
||||
use serde_json;
|
||||
use shared;
|
||||
use syn;
|
||||
|
||||
use ast;
|
||||
use Diagnostic;
|
||||
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
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
fn try_to_tokens(&self, tokens: &mut TokenStream) -> Result<(), Diagnostic> {
|
||||
for export in self.exports.iter() {
|
||||
export.to_tokens(tokens);
|
||||
export.try_to_tokens(tokens)?;
|
||||
}
|
||||
for s in self.structs.iter() {
|
||||
s.to_tokens(tokens);
|
||||
@ -30,13 +42,13 @@ impl ToTokens for ast::Program {
|
||||
|
||||
if let Some(ns) = &i.js_namespace {
|
||||
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);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
i.kind.to_tokens(tokens);
|
||||
i.kind.try_to_tokens(tokens)?;
|
||||
}
|
||||
for e in self.enums.iter() {
|
||||
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 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
|
||||
// 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] =
|
||||
*#generated_static_value;
|
||||
}).to_tokens(tokens);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@ -276,8 +290,10 @@ impl ToTokens for ast::StructField {
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTokens for ast::Export {
|
||||
fn to_tokens(self: &ast::Export, into: &mut TokenStream) {
|
||||
impl TryToTokens for ast::Export {
|
||||
fn try_to_tokens(self: &ast::Export, into: &mut TokenStream)
|
||||
-> Result<(), Diagnostic>
|
||||
{
|
||||
let generated_name = self.rust_symbol();
|
||||
let export_name = self.export_name();
|
||||
let mut args = vec![];
|
||||
@ -461,17 +477,21 @@ impl ToTokens for ast::Export {
|
||||
#(<#argtys as WasmDescribe>::describe();)*
|
||||
#describe_ret
|
||||
}).to_tokens(into);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTokens for ast::ImportKind {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
impl TryToTokens for ast::ImportKind {
|
||||
fn try_to_tokens(&self, tokens: &mut TokenStream) -> Result<(), Diagnostic> {
|
||||
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::Type(ref t) => t.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 {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
impl TryToTokens for ast::ImportFunction {
|
||||
fn try_to_tokens(&self, tokens: &mut TokenStream) -> Result<(), Diagnostic> {
|
||||
let mut class_ty = None;
|
||||
let mut is_method = false;
|
||||
match self.kind {
|
||||
@ -827,6 +847,8 @@ impl ToTokens for ast::ImportFunction {
|
||||
} else {
|
||||
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;
|
||||
|
||||
pub use codegen::TryToTokens;
|
||||
pub use error::Diagnostic;
|
||||
|
||||
pub mod ast;
|
||||
mod codegen;
|
||||
pub mod defined;
|
||||
mod error;
|
||||
pub mod util;
|
||||
|
Reference in New Issue
Block a user