2018-07-25 18:08:57 +01:00

463 lines
14 KiB
Rust

use proc_macro2::{Ident, Span};
use shared;
use syn;
/// 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))]
#[derive(Default)]
pub struct Program {
/// rust -> js interfaces
pub exports: Vec<Export>,
/// js -> rust interfaces
pub imports: Vec<Import>,
/// rust enums
pub enums: Vec<Enum>,
/// rust structs
pub structs: Vec<Struct>,
/// rust type aliases
pub type_aliases: Vec<TypeAlias>,
/// rust consts
pub consts: Vec<Const>,
}
/// A rust to js interface. Allows interaction with rust objects/functions
/// from javascript.
#[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq, Eq))]
pub struct Export {
/// The javascript class name.
pub class: Option<Ident>,
/// The type of `self` (either `self`, `&self`, or `&mut self`)
pub method_self: Option<MethodSelf>,
/// The name of the constructor function (e.g. new).
///
/// This allows javascript to expose an `Object` interface, where calling
/// the constructor maps correctly to rust.
pub constructor: Option<String>,
/// The rust function
pub function: Function,
/// Comments extracted from the rust source.
pub comments: Vec<String>,
/// The name of the rust function/method on the rust side.
pub rust_name: Ident,
}
/// The 3 types variations of `self`.
#[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq, Eq))]
pub enum MethodSelf {
/// `self`
ByValue,
/// `&mut self`
RefMutable,
/// `&self`
RefShared,
}
#[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq, Eq))]
pub struct Import {
pub module: Option<String>,
pub version: Option<String>,
pub js_namespace: Option<Ident>,
pub kind: ImportKind,
}
#[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq, Eq))]
pub enum ImportKind {
Function(ImportFunction),
Static(ImportStatic),
Type(ImportType),
Enum(ImportEnum),
}
#[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq, Eq))]
pub struct ImportFunction {
pub function: Function,
pub rust_name: Ident,
pub js_ret: Option<syn::Type>,
pub catch: bool,
pub structural: bool,
pub kind: ImportFunctionKind,
pub shim: Ident,
}
#[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq, Eq))]
pub enum ImportFunctionKind {
Method {
class: String,
ty: syn::Type,
kind: MethodKind,
},
Normal,
}
#[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq, Eq))]
pub enum MethodKind {
Constructor,
Operation(Operation),
}
#[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq, Eq))]
pub struct Operation {
pub is_static: bool,
pub kind: OperationKind,
}
#[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq, Eq))]
pub enum OperationKind {
Regular,
Setter(Option<Ident>),
Getter(Option<Ident>),
}
#[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq, Eq))]
pub struct ImportStatic {
pub vis: syn::Visibility,
pub ty: syn::Type,
pub shim: Ident,
pub rust_name: Ident,
pub js_name: Ident,
}
#[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq, Eq))]
pub struct ImportType {
pub vis: syn::Visibility,
pub name: Ident,
pub attrs: Vec<syn::Attribute>,
}
#[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq, Eq))]
pub struct ImportEnum {
/// The Rust enum's visibility
pub vis: syn::Visibility,
/// The Rust enum's identifiers
pub name: Ident,
/// The Rust identifiers for the variants
pub variants: Vec<Ident>,
/// The JS string values of the variants
pub variant_values: Vec<String>,
/// Attributes to apply to the Rust enum
pub rust_attrs: Vec<syn::Attribute>,
}
#[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq, Eq))]
pub struct Function {
pub name: Ident,
pub arguments: Vec<syn::ArgCaptured>,
pub ret: Option<syn::Type>,
pub rust_attrs: Vec<syn::Attribute>,
pub rust_vis: syn::Visibility,
}
#[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq, Eq))]
pub struct Struct {
pub name: Ident,
pub fields: Vec<StructField>,
pub comments: Vec<String>,
}
#[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq, Eq))]
pub struct StructField {
pub name: Ident,
pub struct_name: Ident,
pub readonly: bool,
pub ty: syn::Type,
pub getter: Ident,
pub setter: Ident,
pub comments: Vec<String>,
}
#[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq, Eq))]
pub struct Enum {
pub name: Ident,
pub variants: Vec<Variant>,
pub comments: Vec<String>,
}
#[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq, Eq))]
pub struct Variant {
pub name: Ident,
pub value: u32,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum TypeKind {
ByRef,
ByMutRef,
ByValue,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum TypeLocation {
ImportArgument,
ImportRet,
ExportArgument,
ExportRet,
}
#[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq, Eq))]
pub struct TypeAlias {
pub vis: syn::Visibility,
pub dest: Ident,
pub src: syn::Type,
}
#[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq))]
pub struct Const {
pub vis: syn::Visibility,
pub name: Ident,
pub class: Option<Ident>,
pub ty: syn::Type,
pub value: ConstValue,
}
#[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq))]
/// same as webidl::ast::ConstValue
pub enum ConstValue {
BooleanLiteral(bool),
FloatLiteral(f64),
SignedIntegerLiteral(i64),
UnsignedIntegerLiteral(u64),
Null,
}
impl Program {
pub(crate) fn shared(&self) -> shared::Program {
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(),
version: shared::version(),
schema_version: shared::SCHEMA_VERSION.to_string(),
}
}
}
impl Function {
fn shared(&self) -> shared::Function {
shared::Function {
name: self.name.to_string(),
}
}
}
impl Export {
/// Mangles a rust -> javascript export, so that the created Ident will be unique over function
/// name and class name, if the function belongs to a javascript class.
pub(crate) fn rust_symbol(&self) -> Ident {
let mut generated_name = String::from("__wasm_bindgen_generated");
if let Some(class) = &self.class {
generated_name.push_str("_");
generated_name.push_str(&class.to_string());
}
generated_name.push_str("_");
generated_name.push_str(&self.function.name.to_string());
Ident::new(&generated_name, Span::call_site())
}
/// This is the name of the shim function that gets exported and takes the raw
/// ABI form of its arguments and converts them back into their normal,
/// "high level" form before calling the actual function.
pub(crate) fn export_name(&self) -> String {
let fn_name = self.function.name.to_string();
match &self.class {
Some(class) => shared::struct_function_export_name(&class.to_string(), &fn_name),
None => shared::free_function_export_name(&fn_name),
}
}
fn shared(&self) -> shared::Export {
let (method, consumed) = match self.method_self {
Some(MethodSelf::ByValue) => (true, true),
Some(_) => (true, false),
None => (false, false),
};
shared::Export {
class: self.class.as_ref().map(|s| s.to_string()),
method,
consumed,
constructor: self.constructor.clone(),
function: self.function.shared(),
comments: self.comments.clone(),
}
}
}
impl Enum {
fn shared(&self) -> shared::Enum {
shared::Enum {
name: self.name.to_string(),
variants: self.variants.iter().map(|v| v.shared()).collect(),
comments: self.comments.clone(),
}
}
}
impl Variant {
fn shared(&self) -> shared::EnumVariant {
shared::EnumVariant {
name: self.name.to_string(),
value: self.value,
}
}
}
impl Import {
fn shared(&self) -> shared::Import {
match (&self.module, &self.version) {
(&Some(ref m), None) if m.starts_with("./") => {}
(&Some(ref m), &Some(_)) if m.starts_with("./") => {
panic!(
"when a module path starts with `./` that indicates \
that a local file is being imported so the `version` \
key cannot also be specified"
);
}
(&Some(_), &Some(_)) => {}
(&Some(_), &None) => panic!(
"when the `module` directive doesn't start with `./` \
then it's interpreted as an NPM package which requires \
a `version` to be specified as well, try using \
#[wasm_bindgen(module = \"...\", version = \"...\")]"
),
(&None, &Some(_)) => {
panic!(
"the #[wasm_bindgen(version = \"...\")] attribute can only \
be used when `module = \"...\"` is also specified"
);
}
(&None, &None) => {}
}
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(),
}
}
}
impl ImportKind {
/// Whether this type can be inside an `impl` block.
pub fn fits_on_impl(&self) -> bool {
match *self {
ImportKind::Function(_) => true,
ImportKind::Static(_) => false,
ImportKind::Type(_) => false,
ImportKind::Enum(_) => false,
}
}
fn shared(&self) -> shared::ImportKind {
match *self {
ImportKind::Function(ref f) => shared::ImportKind::Function(f.shared()),
ImportKind::Static(ref f) => shared::ImportKind::Static(f.shared()),
ImportKind::Type(ref f) => shared::ImportKind::Type(f.shared()),
ImportKind::Enum(ref f) => shared::ImportKind::Enum(f.shared()),
}
}
}
impl ImportFunction {
/// If the rust object has a `fn xxx(&self) -> MyType` method, get the name for a getter in
/// javascript (in this case `xxx`, so you can write `val = obj.xxx`)
fn infer_getter_property(&self) -> String {
self.function.name.to_string()
}
/// If the rust object has a `fn set_xxx(&mut self, MyType)` style method, get the name
/// for a setter in javascript (in this case `xxx`, so you can write `obj.xxx = val`)
fn infer_setter_property(&self) -> String {
let name = self.function.name.to_string();
assert!(name.starts_with("set_"), "setters must start with `set_`");
name[4..].to_string()
}
fn shared(&self) -> shared::ImportFunction {
let method = match self.kind {
ImportFunctionKind::Method {
ref class,
ref kind,
..
} => {
let kind = match kind {
MethodKind::Constructor => shared::MethodKind::Constructor,
MethodKind::Operation(Operation { is_static, kind }) => {
let is_static = *is_static;
let kind = match kind {
OperationKind::Regular => shared::OperationKind::Regular,
OperationKind::Getter(g) => {
let g = g.as_ref().map(|g| g.to_string());
shared::OperationKind::Getter(
g.unwrap_or_else(|| self.infer_getter_property()),
)
}
OperationKind::Setter(s) => {
let s = s.as_ref().map(|s| s.to_string());
shared::OperationKind::Setter(
s.unwrap_or_else(|| self.infer_setter_property()),
)
}
};
shared::MethodKind::Operation(shared::Operation { is_static, kind })
}
};
Some(shared::MethodData {
class: class.clone(),
kind,
})
}
ImportFunctionKind::Normal => None,
};
shared::ImportFunction {
shim: self.shim.to_string(),
catch: self.catch,
method,
structural: self.structural,
function: self.function.shared(),
}
}
}
impl ImportStatic {
fn shared(&self) -> shared::ImportStatic {
shared::ImportStatic {
name: self.js_name.to_string(),
shim: self.shim.to_string(),
}
}
}
impl ImportType {
fn shared(&self) -> shared::ImportType {
shared::ImportType {}
}
}
impl ImportEnum {
fn shared(&self) -> shared::ImportEnum {
shared::ImportEnum {}
}
}
impl Struct {
fn shared(&self) -> shared::Struct {
shared::Struct {
name: self.name.to_string(),
fields: self.fields.iter().map(|s| s.shared()).collect(),
comments: self.comments.clone(),
}
}
}
impl StructField {
fn shared(&self) -> shared::StructField {
shared::StructField {
name: self.name.to_string(),
readonly: self.readonly,
comments: self.comments.clone(),
}
}
}