wasm-bindgen-webidl-0.2.58/Cargo.toml.orig010066400017500001750000000012061360514047700165450ustar0000000000000000[package] name = "wasm-bindgen-webidl" version = "0.2.58" authors = ["The wasm-bindgen Developers"] license = "MIT/Apache-2.0" categories = ["wasm"] repository = "https://github.com/rustwasm/wasm-bindgen/tree/master/crates/webidl" homepage = "https://rustwasm.github.io/wasm-bindgen/" documentation = "https://docs.rs/wasm-bindgen" description = """ Support for parsing WebIDL specific to wasm-bindgen """ edition = "2018" [dependencies] anyhow = "1.0" heck = "0.3" log = "0.4.1" proc-macro2 = "1.0" quote = '1.0' syn = { version = '1.0', features = ['full'] } wasm-bindgen-backend = { version = "=0.2.58", path = "../backend" } weedle = "0.10" wasm-bindgen-webidl-0.2.58/Cargo.toml0000644000000024141360516023700130470ustar00# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies # # If you believe there's an error in this file please file an # issue against the rust-lang/cargo repository. If you're # editing this file be aware that the upstream Cargo.toml # will likely look very different (and much more reasonable) [package] edition = "2018" name = "wasm-bindgen-webidl" version = "0.2.58" authors = ["The wasm-bindgen Developers"] description = "Support for parsing WebIDL specific to wasm-bindgen\n" homepage = "https://rustwasm.github.io/wasm-bindgen/" documentation = "https://docs.rs/wasm-bindgen" categories = ["wasm"] license = "MIT/Apache-2.0" repository = "https://github.com/rustwasm/wasm-bindgen/tree/master/crates/webidl" [dependencies.anyhow] version = "1.0" [dependencies.heck] version = "0.3" [dependencies.log] version = "0.4.1" [dependencies.proc-macro2] version = "1.0" [dependencies.quote] version = "1.0" [dependencies.syn] version = "1.0" features = ["full"] [dependencies.wasm-bindgen-backend] version = "=0.2.58" [dependencies.weedle] version = "0.10" wasm-bindgen-webidl-0.2.58/src/first_pass.rs010066400017500001750000000624471347603543600172120ustar0000000000000000//! Because some WebIDL constructs are defined in multiple places //! (keyword `partial` is used to add to an existing construct), //! We need to first walk the webidl to collect all non-partial //! constructs so that we have containers in which to put the //! partial ones. //! //! Only `interface`s, `dictionary`s, `enum`s and `mixin`s can //! be partial. use std::cmp::Ordering; use std::collections::{BTreeMap, BTreeSet}; use proc_macro2::Ident; use weedle; use weedle::argument::Argument; use weedle::attribute::*; use weedle::interface::*; use weedle::mixin::*; use weedle::CallbackInterfaceDefinition; use weedle::{DictionaryDefinition, PartialDictionaryDefinition}; use super::Result; use crate::util; use crate::util::camel_case_ident; /// Collection of constructs that may use partial. #[derive(Default)] pub(crate) struct FirstPassRecord<'src> { pub(crate) builtin_idents: BTreeSet, pub(crate) interfaces: BTreeMap<&'src str, InterfaceData<'src>>, pub(crate) enums: BTreeMap<&'src str, &'src weedle::EnumDefinition<'src>>, /// The mixins, mapping their name to the webidl ast node for the mixin. pub(crate) mixins: BTreeMap<&'src str, MixinData<'src>>, pub(crate) typedefs: BTreeMap<&'src str, &'src weedle::types::Type<'src>>, pub(crate) namespaces: BTreeMap<&'src str, NamespaceData<'src>>, pub(crate) includes: BTreeMap<&'src str, BTreeSet<&'src str>>, pub(crate) dictionaries: BTreeMap<&'src str, DictionaryData<'src>>, pub(crate) callbacks: BTreeSet<&'src str>, pub(crate) callback_interfaces: BTreeMap<&'src str, CallbackInterfaceData<'src>>, pub(crate) immutable_slice_whitelist: BTreeSet<&'static str>, } /// We need to collect interface data during the first pass, to be used later. #[derive(Default)] pub(crate) struct InterfaceData<'src> { /// Whether only partial interfaces were encountered pub(crate) partial: bool, pub(crate) has_interface: bool, pub(crate) deprecated: Option, pub(crate) attributes: Vec<&'src AttributeInterfaceMember<'src>>, pub(crate) consts: Vec<&'src ConstMember<'src>>, pub(crate) operations: BTreeMap, OperationData<'src>>, pub(crate) superclass: Option<&'src str>, pub(crate) definition_attributes: Option<&'src ExtendedAttributeList<'src>>, } /// We need to collect mixin data during the first pass, to be used later. #[derive(Default)] pub(crate) struct MixinData<'src> { /// Whether only partial mixins were encountered pub(crate) partial: bool, pub(crate) attributes: Vec<&'src AttributeMixinMember<'src>>, pub(crate) consts: Vec<&'src ConstMember<'src>>, pub(crate) operations: BTreeMap, OperationData<'src>>, pub(crate) definition_attributes: Option<&'src ExtendedAttributeList<'src>>, } /// We need to collect namespace data during the first pass, to be used later. #[derive(Default)] pub(crate) struct NamespaceData<'src> { pub(crate) operations: BTreeMap, OperationData<'src>>, } #[derive(Default)] pub(crate) struct DictionaryData<'src> { pub(crate) partials: Vec<&'src PartialDictionaryDefinition<'src>>, pub(crate) definition: Option<&'src DictionaryDefinition<'src>>, } pub(crate) struct CallbackInterfaceData<'src> { pub(crate) definition: &'src CallbackInterfaceDefinition<'src>, pub(crate) single_function: bool, } #[derive(PartialEq, Eq, PartialOrd, Ord, Debug, Clone, Copy)] pub(crate) enum OperationId<'src> { Constructor(IgnoreTraits<&'src str>), /// The name of a function in crates/web-sys/webidls/enabled/*.webidl /// /// ex: Operation(Some("vertexAttrib1fv")) Operation(Option<&'src str>), IndexingGetter, IndexingSetter, IndexingDeleter, } #[derive(Default)] pub(crate) struct OperationData<'src> { pub(crate) signatures: Vec>, pub(crate) is_static: bool, } #[derive(Clone, Debug)] pub(crate) struct Signature<'src> { pub(crate) args: Vec>, pub(crate) ret: weedle::types::ReturnType<'src>, pub(crate) attrs: &'src Option>, } #[derive(Clone, Debug)] pub(crate) struct Arg<'src> { pub(crate) name: &'src str, pub(crate) ty: &'src weedle::types::Type<'src>, pub(crate) optional: bool, pub(crate) variadic: bool, } /// Implemented on an AST node to populate the `FirstPassRecord` struct. pub(crate) trait FirstPass<'src, Ctx> { /// Populate `record` with any constructs in `self`. fn first_pass(&'src self, record: &mut FirstPassRecord<'src>, ctx: Ctx) -> Result<()>; } impl<'src> FirstPass<'src, ()> for [weedle::Definition<'src>] { fn first_pass(&'src self, record: &mut FirstPassRecord<'src>, (): ()) -> Result<()> { for def in self { def.first_pass(record, ())?; } Ok(()) } } impl<'src> FirstPass<'src, ()> for weedle::Definition<'src> { fn first_pass(&'src self, record: &mut FirstPassRecord<'src>, (): ()) -> Result<()> { use weedle::Definition::*; match self { Dictionary(dictionary) => dictionary.first_pass(record, ()), PartialDictionary(dictionary) => dictionary.first_pass(record, ()), Enum(enum_) => enum_.first_pass(record, ()), IncludesStatement(includes) => includes.first_pass(record, ()), Interface(interface) => interface.first_pass(record, ()), PartialInterface(interface) => interface.first_pass(record, ()), InterfaceMixin(mixin) => mixin.first_pass(record, ()), PartialInterfaceMixin(mixin) => mixin.first_pass(record, ()), Namespace(namespace) => namespace.first_pass(record, ()), PartialNamespace(namespace) => namespace.first_pass(record, ()), Typedef(typedef) => typedef.first_pass(record, ()), Callback(callback) => callback.first_pass(record, ()), CallbackInterface(iface) => iface.first_pass(record, ()), Implements(_) => Ok(()), } } } impl<'src> FirstPass<'src, ()> for weedle::DictionaryDefinition<'src> { fn first_pass(&'src self, record: &mut FirstPassRecord<'src>, (): ()) -> Result<()> { if util::is_chrome_only(&self.attributes) { return Ok(()); } record .dictionaries .entry(self.identifier.0) .or_default() .definition = Some(self); Ok(()) } } impl<'src> FirstPass<'src, ()> for weedle::PartialDictionaryDefinition<'src> { fn first_pass(&'src self, record: &mut FirstPassRecord<'src>, (): ()) -> Result<()> { if util::is_chrome_only(&self.attributes) { return Ok(()); } record .dictionaries .entry(self.identifier.0) .or_default() .partials .push(self); Ok(()) } } impl<'src> FirstPass<'src, ()> for weedle::EnumDefinition<'src> { fn first_pass(&'src self, record: &mut FirstPassRecord<'src>, (): ()) -> Result<()> { if util::is_chrome_only(&self.attributes) { return Ok(()); } if record.enums.insert(self.identifier.0, self).is_some() { log::info!( "Encountered multiple enum declarations: {}", self.identifier.0 ); } Ok(()) } } impl<'src> FirstPass<'src, ()> for weedle::IncludesStatementDefinition<'src> { fn first_pass(&'src self, record: &mut FirstPassRecord<'src>, (): ()) -> Result<()> { if util::is_chrome_only(&self.attributes) { return Ok(()); } record .includes .entry(self.lhs_identifier.0) .or_default() .insert(self.rhs_identifier.0); Ok(()) } } #[derive(Clone, Copy)] enum FirstPassOperationType { Interface, Mixin, Namespace, } fn first_pass_operation<'src>( record: &mut FirstPassRecord<'src>, first_pass_operation_type: FirstPassOperationType, self_name: &'src str, ids: &[OperationId<'src>], arguments: &'src [Argument<'src>], ret: &weedle::types::ReturnType<'src>, attrs: &'src Option>, is_static: bool, ) { if util::is_chrome_only(attrs) { return; } let mut names = Vec::with_capacity(arguments.len()); for argument in arguments { match argument { Argument::Single(single) => names.push(single.identifier.0), Argument::Variadic(variadic) => names.push(variadic.identifier.0), } } let operations = match first_pass_operation_type { FirstPassOperationType::Interface => { let x = record .interfaces .get_mut(self_name) .expect(&format!("not found {} interface", self_name)); &mut x.operations } FirstPassOperationType::Mixin => { let x = record .mixins .get_mut(self_name) .expect(&format!("not found {} mixin", self_name)); &mut x.operations } FirstPassOperationType::Namespace => { let x = record .namespaces .get_mut(self_name) .expect(&format!("not found {} namespace", self_name)); &mut x.operations } }; let mut args = Vec::with_capacity(arguments.len()); for argument in arguments { let (name, ty, optional, variadic) = match argument { Argument::Single(single) => ( single.identifier.0, &single.type_.type_, single.optional.is_some(), false, ), Argument::Variadic(variadic) => (variadic.identifier.0, &variadic.type_, false, true), }; args.push(Arg { name, ty, optional, variadic, }); } for id in ids { let op = operations.entry(*id).or_default(); op.is_static = is_static; op.signatures.push(Signature { args: args.clone(), ret: ret.clone(), attrs, }); } } impl<'src> FirstPass<'src, ()> for weedle::InterfaceDefinition<'src> { fn first_pass(&'src self, record: &mut FirstPassRecord<'src>, (): ()) -> Result<()> { if util::is_chrome_only(&self.attributes) { return Ok(()); } let interface_data = record.interfaces.entry(self.identifier.0).or_default(); interface_data.partial = false; interface_data.superclass = self.inheritance.map(|s| s.identifier.0); interface_data.definition_attributes = self.attributes.as_ref(); interface_data.deprecated = util::get_rust_deprecated(&self.attributes).map(|s| s.to_string()); interface_data.has_interface = !util::is_no_interface_object(&self.attributes); if let Some(attrs) = &self.attributes { for attr in attrs.body.list.iter() { process_interface_attribute(record, self.identifier.0, attr); } } for member in &self.members.body { member.first_pass(record, self.identifier.0)?; } Ok(()) } } fn process_interface_attribute<'src>( record: &mut FirstPassRecord<'src>, self_name: &'src str, attr: &'src ExtendedAttribute<'src>, ) { let ident = weedle::common::Identifier(self_name); let non_null = weedle::types::MayBeNull { type_: ident, q_mark: None, }; let non_any = weedle::types::NonAnyType::Identifier(non_null); let single = weedle::types::SingleType::NonAny(non_any); let ty = weedle::types::Type::Single(single); let return_ty = weedle::types::ReturnType::Type(ty); match attr { ExtendedAttribute::ArgList(list) if list.identifier.0 == "Constructor" => { first_pass_operation( record, FirstPassOperationType::Interface, self_name, &[OperationId::Constructor(IgnoreTraits(self_name))], &list.args.body.list, &return_ty, &None, false, ); } ExtendedAttribute::NoArgs(other) if (other.0).0 == "Constructor" => { first_pass_operation( record, FirstPassOperationType::Interface, self_name, &[OperationId::Constructor(IgnoreTraits(self_name))], &[], &return_ty, &None, false, ); } ExtendedAttribute::NamedArgList(list) if list.lhs_identifier.0 == "NamedConstructor" => { first_pass_operation( record, FirstPassOperationType::Interface, self_name, &[OperationId::Constructor(IgnoreTraits( list.rhs_identifier.0, ))], &list.args.body.list, &return_ty, &None, false, ); } _ => {} } } impl<'src> FirstPass<'src, ()> for weedle::PartialInterfaceDefinition<'src> { fn first_pass(&'src self, record: &mut FirstPassRecord<'src>, (): ()) -> Result<()> { if util::is_chrome_only(&self.attributes) { return Ok(()); } record .interfaces .entry(self.identifier.0) .or_insert_with(|| InterfaceData { partial: true, ..Default::default() }); for member in &self.members.body { member.first_pass(record, self.identifier.0)?; } Ok(()) } } impl<'src> FirstPass<'src, &'src str> for weedle::interface::InterfaceMember<'src> { fn first_pass( &'src self, record: &mut FirstPassRecord<'src>, self_name: &'src str, ) -> Result<()> { match self { InterfaceMember::Attribute(attr) => attr.first_pass(record, self_name), InterfaceMember::Operation(op) => op.first_pass(record, self_name), InterfaceMember::Const(const_) => { if util::is_chrome_only(&const_.attributes) { return Ok(()); } record .interfaces .get_mut(self_name) .unwrap() .consts .push(const_); Ok(()) } InterfaceMember::Iterable(_iterable) => { log::warn!("Unsupported WebIDL iterable interface member: {:?}", self); Ok(()) } // TODO InterfaceMember::Maplike(_) => { log::warn!("Unsupported WebIDL Maplike interface member: {:?}", self); Ok(()) } InterfaceMember::Stringifier(_) => { log::warn!( "Unsupported WebIDL Stringifier interface member: {:?}", self ); Ok(()) } InterfaceMember::Setlike(_) => { log::warn!("Unsupported WebIDL Setlike interface member: {:?}", self); Ok(()) } } } } impl<'src> FirstPass<'src, &'src str> for weedle::interface::OperationInterfaceMember<'src> { fn first_pass( &'src self, record: &mut FirstPassRecord<'src>, self_name: &'src str, ) -> Result<()> { let is_static = match self.modifier { Some(StringifierOrStatic::Stringifier(_)) => { log::warn!("Unsupported webidl stringifier: {:?}", self); return Ok(()); } Some(StringifierOrStatic::Static(_)) => true, None => false, }; let mut ids = vec![OperationId::Operation(self.identifier.map(|s| s.0))]; if let Some(special) = self.special { match special { Special::Getter(_) => ids.push(OperationId::IndexingGetter), Special::Setter(_) => ids.push(OperationId::IndexingSetter), Special::Deleter(_) => ids.push(OperationId::IndexingDeleter), Special::LegacyCaller(_) => {} }; } first_pass_operation( record, FirstPassOperationType::Interface, self_name, &ids, &self.args.body.list, &self.return_type, &self.attributes, is_static, ); Ok(()) } } impl<'src> FirstPass<'src, &'src str> for weedle::interface::AttributeInterfaceMember<'src> { fn first_pass( &'src self, record: &mut FirstPassRecord<'src>, self_name: &'src str, ) -> Result<()> { if util::is_chrome_only(&self.attributes) { return Ok(()); } record .interfaces .get_mut(self_name) .unwrap() .attributes .push(self); Ok(()) } } impl<'src> FirstPass<'src, ()> for weedle::InterfaceMixinDefinition<'src> { fn first_pass(&'src self, record: &mut FirstPassRecord<'src>, (): ()) -> Result<()> { if util::is_chrome_only(&self.attributes) { return Ok(()); } { let mixin_data = record.mixins.entry(self.identifier.0).or_default(); mixin_data.partial = false; mixin_data.definition_attributes = self.attributes.as_ref(); } for member in &self.members.body { member.first_pass(record, self.identifier.0)?; } Ok(()) } } impl<'src> FirstPass<'src, ()> for weedle::PartialInterfaceMixinDefinition<'src> { fn first_pass(&'src self, record: &mut FirstPassRecord<'src>, (): ()) -> Result<()> { if util::is_chrome_only(&self.attributes) { return Ok(()); } record .mixins .entry(self.identifier.0) .or_insert_with(|| MixinData { partial: true, ..Default::default() }); for member in &self.members.body { member.first_pass(record, self.identifier.0)?; } Ok(()) } } impl<'src> FirstPass<'src, &'src str> for weedle::mixin::MixinMember<'src> { fn first_pass( &'src self, record: &mut FirstPassRecord<'src>, self_name: &'src str, ) -> Result<()> { match self { MixinMember::Operation(op) => op.first_pass(record, self_name), MixinMember::Attribute(a) => a.first_pass(record, self_name), MixinMember::Const(a) => { if util::is_chrome_only(&a.attributes) { return Ok(()); } record.mixins.get_mut(self_name).unwrap().consts.push(a); Ok(()) } MixinMember::Stringifier(_) => { log::warn!("Unsupported WebIDL stringifier mixin member: {:?}", self); Ok(()) } } } } impl<'src> FirstPass<'src, &'src str> for weedle::mixin::OperationMixinMember<'src> { fn first_pass( &'src self, record: &mut FirstPassRecord<'src>, self_name: &'src str, ) -> Result<()> { if self.stringifier.is_some() { log::warn!("Unsupported webidl stringifier: {:?}", self); return Ok(()); } first_pass_operation( record, FirstPassOperationType::Mixin, self_name, &[OperationId::Operation(self.identifier.map(|s| s.0.clone()))], &self.args.body.list, &self.return_type, &self.attributes, false, ); Ok(()) } } impl<'src> FirstPass<'src, &'src str> for weedle::mixin::AttributeMixinMember<'src> { fn first_pass( &'src self, record: &mut FirstPassRecord<'src>, self_name: &'src str, ) -> Result<()> { if util::is_chrome_only(&self.attributes) { return Ok(()); } record .mixins .get_mut(self_name) .unwrap() .attributes .push(self); Ok(()) } } impl<'src> FirstPass<'src, ()> for weedle::TypedefDefinition<'src> { fn first_pass(&'src self, record: &mut FirstPassRecord<'src>, (): ()) -> Result<()> { if util::is_chrome_only(&self.attributes) { return Ok(()); } if record .typedefs .insert(self.identifier.0, &self.type_.type_) .is_some() { log::info!( "Encountered multiple typedef declarations: {}", self.identifier.0 ); } Ok(()) } } impl<'src> FirstPass<'src, ()> for weedle::NamespaceDefinition<'src> { fn first_pass(&'src self, record: &mut FirstPassRecord<'src>, (): ()) -> Result<()> { if util::is_chrome_only(&self.attributes) { return Ok(()); } record.namespaces.entry(self.identifier.0).or_default(); for member in &self.members.body { member.first_pass(record, self.identifier.0)?; } Ok(()) } } impl<'src> FirstPass<'src, ()> for weedle::PartialNamespaceDefinition<'src> { fn first_pass(&'src self, record: &mut FirstPassRecord<'src>, (): ()) -> Result<()> { if util::is_chrome_only(&self.attributes) { return Ok(()); } record.namespaces.entry(self.identifier.0).or_default(); for member in &self.members.body { member.first_pass(record, self.identifier.0)?; } Ok(()) } } impl<'src> FirstPass<'src, &'src str> for weedle::namespace::NamespaceMember<'src> { fn first_pass( &'src self, record: &mut FirstPassRecord<'src>, self_name: &'src str, ) -> Result<()> { match self { weedle::namespace::NamespaceMember::Operation(op) => op.first_pass(record, self_name), _ => Ok(()), } } } impl<'src> FirstPass<'src, &'src str> for weedle::namespace::OperationNamespaceMember<'src> { fn first_pass( &'src self, record: &mut FirstPassRecord<'src>, self_name: &'src str, ) -> Result<()> { first_pass_operation( record, FirstPassOperationType::Namespace, self_name, &[OperationId::Operation(self.identifier.map(|s| s.0.clone()))], &self.args.body.list, &self.return_type, &self.attributes, true, ); Ok(()) } } impl<'src> FirstPass<'src, ()> for weedle::CallbackDefinition<'src> { fn first_pass(&'src self, record: &mut FirstPassRecord<'src>, _: ()) -> Result<()> { record.callbacks.insert(self.identifier.0); Ok(()) } } impl<'src> FirstPass<'src, ()> for weedle::CallbackInterfaceDefinition<'src> { fn first_pass(&'src self, record: &mut FirstPassRecord<'src>, _: ()) -> Result<()> { if util::is_chrome_only(&self.attributes) { return Ok(()); } if self.inheritance.is_some() { log::warn!( "skipping callback interface with inheritance: {}", self.identifier.0 ); return Ok(()); } let data = CallbackInterfaceData { definition: self, single_function: self.members.body.len() == 1, }; record.callback_interfaces.insert(self.identifier.0, data); Ok(()) } } impl<'a> FirstPassRecord<'a> { pub fn all_superclasses<'me>(&'me self, interface: &str) -> impl Iterator + 'me { let mut set = BTreeSet::new(); let mut list = Vec::new(); self.fill_superclasses(interface, &mut set, &mut list); list.into_iter() } fn fill_superclasses( &self, interface: &str, set: &mut BTreeSet<&'a str>, list: &mut Vec, ) { let data = match self.interfaces.get(interface) { Some(data) => data, None => return, }; let superclass = match &data.superclass { Some(class) => class, None => return, }; if self.interfaces.contains_key(superclass) { if set.insert(superclass) { list.push(camel_case_ident(superclass)); self.fill_superclasses(superclass, set, list); } } } pub fn all_mixins<'me>( &'me self, interface: &str, ) -> impl Iterator> + 'me { let mut set = Vec::new(); self.fill_mixins(interface, interface, &mut set); set.into_iter() } fn fill_mixins<'me>( &'me self, self_name: &str, mixin_name: &str, list: &mut Vec<&'me MixinData<'a>>, ) { if let Some(mixin_data) = self.mixins.get(mixin_name) { list.push(mixin_data); } if let Some(mixin_names) = self.includes.get(mixin_name) { for mixin_name in mixin_names { self.fill_mixins(self_name, mixin_name, list); } } } } #[derive(Copy, Clone, Debug)] pub struct IgnoreTraits(pub T); impl PartialEq for IgnoreTraits { fn eq(&self, _other: &IgnoreTraits) -> bool { true } } impl Eq for IgnoreTraits {} impl PartialOrd for IgnoreTraits { fn partial_cmp(&self, _other: &IgnoreTraits) -> Option { Some(Ordering::Equal) } } impl Ord for IgnoreTraits { fn cmp(&self, _other: &IgnoreTraits) -> Ordering { Ordering::Equal } } wasm-bindgen-webidl-0.2.58/src/idl_type.rs010066400017500001750000000710251352460564600166360ustar0000000000000000use proc_macro2::{Ident, Span}; use syn; use wasm_bindgen_backend::util::{ident_ty, leading_colon_path_ty, raw_ident, rust_ident}; use weedle::common::Identifier; use weedle::term; use weedle::types::*; use crate::first_pass::FirstPassRecord; use crate::util::{array, camel_case_ident, option_ty, shared_ref, snake_case_ident, TypePosition}; #[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Debug)] pub(crate) enum IdlType<'a> { Boolean, Byte, Octet, Short, UnsignedShort, Long, UnsignedLong, LongLong, UnsignedLongLong, Float, UnrestrictedFloat, Double, UnrestrictedDouble, DomString, ByteString, UsvString, Object, Symbol, Error, Callback, ArrayBuffer, DataView, Int8Array { immutable: bool, }, Uint8Array { immutable: bool, }, Uint8ClampedArray { immutable: bool, }, Int16Array { immutable: bool, }, Uint16Array { immutable: bool, }, Int32Array { immutable: bool, }, Uint32Array { immutable: bool, }, Float32Array { immutable: bool, }, Float64Array { immutable: bool, }, ArrayBufferView { immutable: bool, }, BufferSource { immutable: bool, }, Interface(&'a str), Dictionary(&'a str), Enum(&'a str), CallbackInterface { name: &'a str, single_function: bool, }, Nullable(Box>), FrozenArray(Box>), Sequence(Box>), Promise(Box>), Record(Box>, Box>), Union(Vec>), Any, Void, UnknownInterface(&'a str), } pub(crate) trait ToIdlType<'a> { fn to_idl_type(&self, record: &FirstPassRecord<'a>) -> IdlType<'a>; } impl<'a> ToIdlType<'a> for UnionType<'a> { fn to_idl_type(&self, record: &FirstPassRecord<'a>) -> IdlType<'a> { let mut idl_types = Vec::with_capacity(self.body.list.len()); for t in &self.body.list { idl_types.push(t.to_idl_type(record)); } IdlType::Union(idl_types) } } impl<'a> ToIdlType<'a> for Type<'a> { fn to_idl_type(&self, record: &FirstPassRecord<'a>) -> IdlType<'a> { match self { Type::Single(t) => t.to_idl_type(record), Type::Union(t) => t.to_idl_type(record), } } } impl<'a> ToIdlType<'a> for SingleType<'a> { fn to_idl_type(&self, record: &FirstPassRecord<'a>) -> IdlType<'a> { match self { SingleType::Any(t) => t.to_idl_type(record), SingleType::NonAny(t) => t.to_idl_type(record), } } } impl<'a> ToIdlType<'a> for NonAnyType<'a> { fn to_idl_type(&self, record: &FirstPassRecord<'a>) -> IdlType<'a> { match self { NonAnyType::Promise(t) => t.to_idl_type(record), NonAnyType::Integer(t) => t.to_idl_type(record), NonAnyType::FloatingPoint(t) => t.to_idl_type(record), NonAnyType::Boolean(t) => t.to_idl_type(record), NonAnyType::Byte(t) => t.to_idl_type(record), NonAnyType::Octet(t) => t.to_idl_type(record), NonAnyType::ByteString(t) => t.to_idl_type(record), NonAnyType::DOMString(t) => t.to_idl_type(record), NonAnyType::USVString(t) => t.to_idl_type(record), NonAnyType::Sequence(t) => t.to_idl_type(record), NonAnyType::Object(t) => t.to_idl_type(record), NonAnyType::Symbol(t) => t.to_idl_type(record), NonAnyType::Error(t) => t.to_idl_type(record), NonAnyType::ArrayBuffer(t) => t.to_idl_type(record), NonAnyType::DataView(t) => t.to_idl_type(record), NonAnyType::Int8Array(t) => t.to_idl_type(record), NonAnyType::Int16Array(t) => t.to_idl_type(record), NonAnyType::Int32Array(t) => t.to_idl_type(record), NonAnyType::Uint8Array(t) => t.to_idl_type(record), NonAnyType::Uint16Array(t) => t.to_idl_type(record), NonAnyType::Uint32Array(t) => t.to_idl_type(record), NonAnyType::Uint8ClampedArray(t) => t.to_idl_type(record), NonAnyType::Float32Array(t) => t.to_idl_type(record), NonAnyType::Float64Array(t) => t.to_idl_type(record), NonAnyType::FrozenArrayType(t) => t.to_idl_type(record), NonAnyType::ArrayBufferView(t) => t.to_idl_type(record), NonAnyType::BufferSource(t) => t.to_idl_type(record), NonAnyType::RecordType(t) => t.to_idl_type(record), NonAnyType::Identifier(t) => t.to_idl_type(record), } } } impl<'a> ToIdlType<'a> for SequenceType<'a> { fn to_idl_type(&self, record: &FirstPassRecord<'a>) -> IdlType<'a> { IdlType::Sequence(Box::new(self.generics.body.to_idl_type(record))) } } impl<'a> ToIdlType<'a> for FrozenArrayType<'a> { fn to_idl_type(&self, record: &FirstPassRecord<'a>) -> IdlType<'a> { IdlType::FrozenArray(Box::new(self.generics.body.to_idl_type(record))) } } impl<'a, T: ToIdlType<'a>> ToIdlType<'a> for MayBeNull { fn to_idl_type(&self, record: &FirstPassRecord<'a>) -> IdlType<'a> { let inner_idl_type = self.type_.to_idl_type(record); if self.q_mark.is_some() { IdlType::Nullable(Box::new(inner_idl_type)) } else { inner_idl_type } } } impl<'a> ToIdlType<'a> for PromiseType<'a> { fn to_idl_type(&self, record: &FirstPassRecord<'a>) -> IdlType<'a> { IdlType::Promise(Box::new(self.generics.body.to_idl_type(record))) } } impl<'a> ToIdlType<'a> for IntegerType { fn to_idl_type(&self, record: &FirstPassRecord<'a>) -> IdlType<'a> { match self { IntegerType::LongLong(t) => t.to_idl_type(record), IntegerType::Long(t) => t.to_idl_type(record), IntegerType::Short(t) => t.to_idl_type(record), } } } impl<'a> ToIdlType<'a> for LongLongType { fn to_idl_type(&self, _record: &FirstPassRecord<'a>) -> IdlType<'a> { if self.unsigned.is_some() { IdlType::UnsignedLongLong } else { IdlType::LongLong } } } impl<'a> ToIdlType<'a> for LongType { fn to_idl_type(&self, _record: &FirstPassRecord<'a>) -> IdlType<'a> { if self.unsigned.is_some() { IdlType::UnsignedLong } else { IdlType::Long } } } impl<'a> ToIdlType<'a> for ShortType { fn to_idl_type(&self, _record: &FirstPassRecord<'a>) -> IdlType<'a> { if self.unsigned.is_some() { IdlType::UnsignedShort } else { IdlType::Short } } } impl<'a> ToIdlType<'a> for FloatingPointType { fn to_idl_type(&self, record: &FirstPassRecord<'a>) -> IdlType<'a> { match self { FloatingPointType::Float(t) => t.to_idl_type(record), FloatingPointType::Double(t) => t.to_idl_type(record), } } } impl<'a> ToIdlType<'a> for FloatType { fn to_idl_type(&self, _record: &FirstPassRecord<'a>) -> IdlType<'a> { if self.unrestricted.is_some() { IdlType::UnrestrictedFloat } else { IdlType::Float } } } impl<'a> ToIdlType<'a> for DoubleType { fn to_idl_type(&self, _record: &FirstPassRecord<'a>) -> IdlType<'a> { if self.unrestricted.is_some() { IdlType::UnrestrictedDouble } else { IdlType::Double } } } impl<'a> ToIdlType<'a> for RecordType<'a> { fn to_idl_type(&self, record: &FirstPassRecord<'a>) -> IdlType<'a> { IdlType::Record( Box::new(self.generics.body.0.to_idl_type(record)), Box::new(self.generics.body.2.to_idl_type(record)), ) } } impl<'a> ToIdlType<'a> for StringType { fn to_idl_type(&self, record: &FirstPassRecord<'a>) -> IdlType<'a> { match self { StringType::Byte(t) => t.to_idl_type(record), StringType::DOM(t) => t.to_idl_type(record), StringType::USV(t) => t.to_idl_type(record), } } } impl<'a> ToIdlType<'a> for UnionMemberType<'a> { fn to_idl_type(&self, record: &FirstPassRecord<'a>) -> IdlType<'a> { match self { UnionMemberType::Single(t) => t.to_idl_type(record), UnionMemberType::Union(t) => t.to_idl_type(record), } } } impl<'a> ToIdlType<'a> for ConstType<'a> { fn to_idl_type(&self, record: &FirstPassRecord<'a>) -> IdlType<'a> { match self { ConstType::Integer(t) => t.to_idl_type(record), ConstType::FloatingPoint(t) => t.to_idl_type(record), ConstType::Boolean(t) => t.to_idl_type(record), ConstType::Byte(t) => t.to_idl_type(record), ConstType::Octet(t) => t.to_idl_type(record), ConstType::Identifier(t) => t.to_idl_type(record), } } } impl<'a> ToIdlType<'a> for ReturnType<'a> { fn to_idl_type(&self, record: &FirstPassRecord<'a>) -> IdlType<'a> { match self { ReturnType::Void(t) => t.to_idl_type(record), ReturnType::Type(t) => t.to_idl_type(record), } } } impl<'a> ToIdlType<'a> for AttributedType<'a> { fn to_idl_type(&self, record: &FirstPassRecord<'a>) -> IdlType<'a> { self.type_.to_idl_type(record) } } impl<'a> ToIdlType<'a> for AttributedNonAnyType<'a> { fn to_idl_type(&self, record: &FirstPassRecord<'a>) -> IdlType<'a> { self.type_.to_idl_type(record) } } impl<'a> ToIdlType<'a> for Identifier<'a> { fn to_idl_type(&self, record: &FirstPassRecord<'a>) -> IdlType<'a> { if self.0 == "DOMTimeStamp" { // https://heycam.github.io/webidl/#DOMTimeStamp IdlType::UnsignedLongLong } else if let Some(idl_type) = record.typedefs.get(&self.0) { idl_type.to_idl_type(record) } else if record.interfaces.contains_key(self.0) { IdlType::Interface(self.0) } else if record.dictionaries.contains_key(self.0) { IdlType::Dictionary(self.0) } else if record.enums.contains_key(self.0) { IdlType::Enum(self.0) } else if record.callbacks.contains(self.0) { IdlType::Callback } else if let Some(data) = record.callback_interfaces.get(self.0) { IdlType::CallbackInterface { name: self.0, single_function: data.single_function, } } else if self.0 == "WindowProxy" { // See this for more info: // // https://html.spec.whatwg.org/multipage/window-object.html#windowproxy // // namely this seems to be "legalese" for "this is a `Window`", so // let's translate it as such. IdlType::Interface("Window") } else { log::warn!("Unrecognized type: {}", self.0); IdlType::UnknownInterface(self.0) } } } macro_rules! terms_to_idl_type { ($($t:tt => $r:tt)*) => ($( impl<'a> ToIdlType<'a> for term::$t { fn to_idl_type(&self, _record: &FirstPassRecord<'a>) -> IdlType<'a> { IdlType::$r } } )*); } // We default to arrays being mutable, but in certain cases where we're certain that // slices won't get mutated on the JS side (such as the WebGL APIs) we might, later in the flow, // instead use the immutable version. macro_rules! terms_to_idl_type_maybe_immutable { ($($t:tt => $r:tt)*) => ($( impl<'a> ToIdlType<'a> for term::$t { fn to_idl_type(&self, _record: &FirstPassRecord<'a>) -> IdlType<'a> { IdlType::$r { immutable: false } } } )*); } terms_to_idl_type! { Symbol => Symbol ByteString => ByteString DOMString => DomString USVString => UsvString Any => Any Boolean => Boolean Byte => Byte Double => Double Float => Float Long => Long Object => Object Octet => Octet Short => Short Void => Void ArrayBuffer => ArrayBuffer DataView => DataView Error => Error } terms_to_idl_type_maybe_immutable! { ArrayBufferView => ArrayBufferView BufferSource => BufferSource Float32Array => Float32Array Float64Array => Float64Array Int16Array => Int16Array Int32Array => Int32Array Int8Array => Int8Array Uint16Array => Uint16Array Uint32Array => Uint32Array Uint8Array => Uint8Array Uint8ClampedArray => Uint8ClampedArray } impl<'a> IdlType<'a> { /// Generates a snake case type name. pub(crate) fn push_snake_case_name(&self, dst: &mut String) { match self { IdlType::Boolean => dst.push_str("bool"), IdlType::Byte => dst.push_str("i8"), IdlType::Octet => dst.push_str("u8"), IdlType::Short => dst.push_str("i16"), IdlType::UnsignedShort => dst.push_str("u16"), IdlType::Long => dst.push_str("i32"), IdlType::UnsignedLong => dst.push_str("u32"), IdlType::LongLong => dst.push_str("i64"), IdlType::UnsignedLongLong => dst.push_str("u64"), IdlType::Float | IdlType::UnrestrictedFloat => dst.push_str("f32"), IdlType::Double | IdlType::UnrestrictedDouble => dst.push_str("f64"), IdlType::DomString | IdlType::ByteString | IdlType::UsvString => dst.push_str("str"), IdlType::Object => dst.push_str("object"), IdlType::Symbol => dst.push_str("symbol"), IdlType::Error => dst.push_str("error"), IdlType::Callback => dst.push_str("callback"), IdlType::ArrayBuffer => dst.push_str("array_buffer"), IdlType::DataView => dst.push_str("data_view"), IdlType::Int8Array { .. } => dst.push_str("i8_array"), IdlType::Uint8Array { .. } => dst.push_str("u8_array"), IdlType::Uint8ClampedArray { .. } => dst.push_str("u8_clamped_array"), IdlType::Int16Array { .. } => dst.push_str("i16_array"), IdlType::Uint16Array { .. } => dst.push_str("u16_array"), IdlType::Int32Array { .. } => dst.push_str("i32_array"), IdlType::Uint32Array { .. } => dst.push_str("u32_array"), IdlType::Float32Array { .. } => dst.push_str("f32_array"), IdlType::Float64Array { .. } => dst.push_str("f64_array"), IdlType::ArrayBufferView { .. } => dst.push_str("array_buffer_view"), IdlType::BufferSource { .. } => dst.push_str("buffer_source"), IdlType::Interface(name) => dst.push_str(&snake_case_ident(name)), IdlType::UnknownInterface(name) => dst.push_str(&snake_case_ident(name)), IdlType::Dictionary(name) => dst.push_str(&snake_case_ident(name)), IdlType::Enum(name) => dst.push_str(&snake_case_ident(name)), IdlType::CallbackInterface { name, .. } => dst.push_str(&snake_case_ident(name)), IdlType::Nullable(idl_type) => { dst.push_str("opt_"); idl_type.push_snake_case_name(dst); } IdlType::FrozenArray(idl_type) => { idl_type.push_snake_case_name(dst); dst.push_str("_frozen_array"); } IdlType::Sequence(idl_type) => { idl_type.push_snake_case_name(dst); dst.push_str("_sequence"); } IdlType::Promise(idl_type) => { idl_type.push_snake_case_name(dst); dst.push_str("_promise"); } IdlType::Record(idl_type_from, idl_type_to) => { dst.push_str("record_from_"); idl_type_from.push_snake_case_name(dst); dst.push_str("_to_"); idl_type_to.push_snake_case_name(dst); } IdlType::Union(idl_types) => { dst.push_str("union_of_"); let mut first = true; for idl_type in idl_types { if first { first = false; } else { dst.push_str("_and_"); } idl_type.push_snake_case_name(dst); } } IdlType::Any => dst.push_str("any"), IdlType::Void => dst.push_str("void"), } } /// Converts to syn type if possible. pub(crate) fn to_syn_type(&self, pos: TypePosition) -> Option { let anyref = |ty| { Some(match pos { TypePosition::Argument => shared_ref(ty, false), TypePosition::Return => ty, }) }; let js_sys = |name: &str| { let path = vec![rust_ident("js_sys"), rust_ident(name)]; let ty = leading_colon_path_ty(path); anyref(ty) }; let js_value = { let path = vec![rust_ident("wasm_bindgen"), rust_ident("JsValue")]; anyref(leading_colon_path_ty(path)) }; match self { IdlType::Boolean => Some(ident_ty(raw_ident("bool"))), IdlType::Byte => Some(ident_ty(raw_ident("i8"))), IdlType::Octet => Some(ident_ty(raw_ident("u8"))), IdlType::Short => Some(ident_ty(raw_ident("i16"))), IdlType::UnsignedShort => Some(ident_ty(raw_ident("u16"))), IdlType::Long => Some(ident_ty(raw_ident("i32"))), IdlType::UnsignedLong => Some(ident_ty(raw_ident("u32"))), // Technically these are 64-bit numbers, but we're binding web // APIs that don't actually have return the corresponding 64-bit // type, `BigInt`. Instead the web basically uses floats for these // values. We already expand these types in argument position to // i32/f64 (convenience for i32, losslessness for f64). If we get // here then we're looking at an un-flattened long type such as // dictionary fields or return types. In order to generate bindings // for these functions we just use `f64` here, which should match // exactly what the JS web currently uses anyway. // // Perhaps one day we'll bind to u64/i64 here, but we need `BigInt` // to see more usage! IdlType::LongLong | IdlType::UnsignedLongLong => Some(ident_ty(raw_ident("f64"))), IdlType::Float => Some(ident_ty(raw_ident("f32"))), IdlType::UnrestrictedFloat => Some(ident_ty(raw_ident("f32"))), IdlType::Double => Some(ident_ty(raw_ident("f64"))), IdlType::UnrestrictedDouble => Some(ident_ty(raw_ident("f64"))), IdlType::DomString | IdlType::ByteString | IdlType::UsvString => match pos { TypePosition::Argument => Some(shared_ref(ident_ty(raw_ident("str")), false)), TypePosition::Return => Some(ident_ty(raw_ident("String"))), }, IdlType::Object => js_sys("Object"), IdlType::Symbol => None, IdlType::Error => None, IdlType::ArrayBuffer => js_sys("ArrayBuffer"), IdlType::DataView => None, IdlType::Int8Array { immutable } => Some(array("i8", pos, *immutable)), IdlType::Uint8Array { immutable } => Some(array("u8", pos, *immutable)), IdlType::Uint8ClampedArray { immutable } => Some(clamped(array("u8", pos, *immutable))), IdlType::Int16Array { immutable } => Some(array("i16", pos, *immutable)), IdlType::Uint16Array { immutable } => Some(array("u16", pos, *immutable)), IdlType::Int32Array { immutable } => Some(array("i32", pos, *immutable)), IdlType::Uint32Array { immutable } => Some(array("u32", pos, *immutable)), IdlType::Float32Array { immutable } => Some(array("f32", pos, *immutable)), IdlType::Float64Array { immutable } => Some(array("f64", pos, *immutable)), IdlType::ArrayBufferView { .. } | IdlType::BufferSource { .. } => js_sys("Object"), IdlType::Interface(name) | IdlType::Dictionary(name) | IdlType::CallbackInterface { name, .. } => { let ty = ident_ty(rust_ident(camel_case_ident(name).as_str())); anyref(ty) } IdlType::Enum(name) => Some(ident_ty(rust_ident(camel_case_ident(name).as_str()))), IdlType::Nullable(idl_type) => { let inner = idl_type.to_syn_type(pos)?; // TODO: this is a bit of a hack, but `Option` isn't // supported right now. As a result if we see `JsValue` for our // inner type, leave that as the same when we create a nullable // version of that. That way `any?` just becomes `JsValue` and // it's up to users to dispatch and/or create instances // appropriately. if let syn::Type::Path(path) = &inner { if path.qself.is_none() && path .path .segments .last() .map(|p| p.ident == "JsValue") .unwrap_or(false) { return Some(inner.clone()); } } Some(option_ty(inner)) } IdlType::FrozenArray(_idl_type) => None, // webidl sequences must always be returned as javascript `Array`s. They may accept // anything implementing the @@iterable interface. IdlType::Sequence(_idl_type) => match pos { TypePosition::Argument => js_value, TypePosition::Return => js_sys("Array"), }, IdlType::Promise(_idl_type) => js_sys("Promise"), IdlType::Record(_idl_type_from, _idl_type_to) => None, IdlType::Union(idl_types) => { // Note that most union types have already been expanded to // their components via `flatten`. Unions in a return position // or dictionary fields, however, haven't been flattened, which // means we may need to conver them to a `syn` type. // // Currently this does a bit of a "poor man's" tree traversal by // saying that if all union members are interfaces we can assume // they've all got `Object` as a superclass, so we can take an // object here. If any are not an interface though we // pessimisitcally translate the union into a `JsValue`, // absolutely anything. It's up to the application to figure out // what to do with that. // // TODO: we should probably do a better job here translating // unions to a single type. Two possible strategies could be: // // 1. Use strategy of finding the nearest common subclass // (finding the best type that is suitable for all values of // this union) instead of always assuming `Object`. // 2. Generate enum with payload in Rust for each union type. // Such an enum, however, might have a relatively high // overhead in creating it from a JS value, but would be // cheap to convert from a variant back to a JS value. if idl_types.iter().all(|idl_type| match idl_type { IdlType::Interface(..) => true, _ => false, }) { IdlType::Object.to_syn_type(pos) } else { IdlType::Any.to_syn_type(pos) } } IdlType::Any => js_value, IdlType::Void => None, IdlType::Callback => js_sys("Function"), IdlType::UnknownInterface(_) => None, } } /// Flattens unions recursively. /// /// Works similarly to [flattened union member types], /// but also flattens unions inside generics of other types. /// /// [flattened union member types]: https://heycam.github.io/webidl/#dfn-flattened-union-member-types pub(crate) fn flatten(&self) -> Vec { match self { IdlType::Nullable(idl_type) => idl_type .flatten() .into_iter() .map(Box::new) .map(IdlType::Nullable) .collect(), IdlType::FrozenArray(idl_type) => idl_type .flatten() .into_iter() .map(Box::new) .map(IdlType::FrozenArray) .collect(), IdlType::Sequence(idl_type) => idl_type .flatten() .into_iter() .map(Box::new) .map(IdlType::Sequence) .collect(), IdlType::Promise(idl_type) => idl_type .flatten() .into_iter() .map(Box::new) .map(IdlType::Promise) .collect(), IdlType::Record(idl_type_from, idl_type_to) => { let mut idl_types = Vec::new(); for idl_type_from in idl_type_from.flatten() { for idl_type_to in idl_type_to.flatten() { idl_types.push(IdlType::Record( Box::new(idl_type_from.clone()), Box::new(idl_type_to.clone()), )); } } idl_types } IdlType::Union(idl_types) => idl_types .iter() .flat_map(|idl_type| idl_type.flatten()) .collect(), IdlType::ArrayBufferView { immutable } => vec![ IdlType::ArrayBufferView { immutable: *immutable, }, IdlType::Uint8Array { immutable: *immutable, }, ], IdlType::BufferSource { immutable } => vec![ IdlType::BufferSource { immutable: *immutable, }, IdlType::Uint8Array { immutable: *immutable, }, ], IdlType::LongLong => vec![IdlType::Long, IdlType::Double], IdlType::UnsignedLongLong => vec![IdlType::UnsignedLong, IdlType::Double], IdlType::CallbackInterface { name, single_function: true, } => { // According to the webidl spec [1] single-function callback // interfaces can also be replaced in arguments with simply a // single callable function, which we map to a `Callback`. // // [1]: https://heycam.github.io/webidl/#es-user-objects vec![ IdlType::Callback, IdlType::CallbackInterface { name, single_function: false, }, ] } idl_type @ _ => vec![idl_type.clone()], } } } #[test] fn idl_type_flatten_test() { use self::IdlType::*; assert_eq!( Union(vec![ Interface("Node"), Union(vec![Sequence(Box::new(Long),), Interface("Event"),]), Nullable(Box::new(Union(vec![ Interface("XMLHttpRequest"), DomString, ])),), Sequence(Box::new(Union(vec![ Sequence(Box::new(Double),), Interface("NodeList"), ])),), ]) .flatten(), vec![ Interface("Node"), Sequence(Box::new(Long)), Interface("Event"), Nullable(Box::new(Interface("XMLHttpRequest"))), Nullable(Box::new(DomString)), Sequence(Box::new(Sequence(Box::new(Double)))), Sequence(Box::new(Interface("NodeList"))), ], ); } /// From `T` create `::wasm_bindgen::Clamped` fn clamped(t: syn::Type) -> syn::Type { let arguments = syn::PathArguments::AngleBracketed(syn::AngleBracketedGenericArguments { colon2_token: None, lt_token: Default::default(), args: vec![syn::GenericArgument::Type(t)].into_iter().collect(), gt_token: Default::default(), }); let ident = raw_ident("Clamped"); let seg = syn::PathSegment { ident, arguments }; syn::TypePath { qself: None, path: syn::Path { leading_colon: Some(Default::default()), segments: vec![Ident::new("wasm_bindgen", Span::call_site()).into(), seg] .into_iter() .collect(), }, } .into() } wasm-bindgen-webidl-0.2.58/src/lib.rs010066400017500001750000000714431357124272600155750ustar0000000000000000/*! # `wasm_bindgen_webidl` Converts WebIDL into wasm-bindgen's internal AST form, so that bindings can be emitted for the types and methods described in the WebIDL. */ #![deny(missing_docs)] #![deny(missing_debug_implementations)] #![doc(html_root_url = "https://docs.rs/wasm-bindgen-webidl/0.2")] mod first_pass; mod idl_type; mod util; use crate::first_pass::{CallbackInterfaceData, OperationData}; use crate::first_pass::{FirstPass, FirstPassRecord, InterfaceData, OperationId}; use crate::idl_type::ToIdlType; use crate::util::{ camel_case_ident, mdn_doc, public, shouty_snake_case_ident, snake_case_ident, webidl_const_v_to_backend_const_v, TypePosition, }; use anyhow::{bail, Result}; use proc_macro2::{Ident, Span}; use quote::{quote, ToTokens}; use std::collections::{BTreeSet, HashSet}; use std::env; use std::fmt; use std::fmt::Display; use std::fs; use std::iter::FromIterator; use wasm_bindgen_backend::ast; use wasm_bindgen_backend::defined::ImportedTypeReferences; use wasm_bindgen_backend::defined::{ImportedTypeDefinitions, RemoveUndefinedImports}; use wasm_bindgen_backend::util::{ident_ty, raw_ident, rust_ident, wrap_import_function}; use wasm_bindgen_backend::TryToTokens; use weedle::attribute::ExtendedAttributeList; use weedle::dictionary::DictionaryMember; use weedle::interface::InterfaceMember; struct Program { main: ast::Program, submodules: Vec<(String, ast::Program)>, } /// A parse error indicating where parsing failed #[derive(Debug)] pub struct WebIDLParseError(pub usize); impl fmt::Display for WebIDLParseError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "failed to parse webidl at byte position {}", self.0) } } impl std::error::Error for WebIDLParseError {} /// Parse a string of WebIDL source text into a wasm-bindgen AST. fn parse(webidl_source: &str, allowed_types: Option<&[&str]>) -> Result { let definitions = match weedle::parse(webidl_source) { Ok(def) => def, Err(e) => { match &e { weedle::Err::Incomplete(needed) => bail!("needed {:?} more bytes", needed), weedle::Err::Error(cx) | weedle::Err::Failure(cx) => { // Note that #[allow] here is a workaround for Geal/nom#843 // because the `Context` type here comes from `nom` and if // something else in our crate graph enables the // `verbose-errors` feature then we need to still compiled // against the changed enum definition. #[allow(unreachable_patterns)] let remaining = match cx { weedle::Context::Code(remaining, _) => remaining.len(), _ => 0, }; let pos = webidl_source.len() - remaining; bail!(WebIDLParseError(pos)) } } } }; let mut first_pass_record: FirstPassRecord = Default::default(); first_pass_record.builtin_idents = builtin_idents(); first_pass_record.immutable_slice_whitelist = immutable_slice_whitelist(); definitions.first_pass(&mut first_pass_record, ())?; let mut program = Default::default(); let mut submodules = Vec::new(); let allowed_types = allowed_types.map(|list| list.iter().cloned().collect::>()); let filter = |name: &str| match &allowed_types { Some(set) => set.contains(name), None => true, }; for (name, e) in first_pass_record.enums.iter() { if filter(&camel_case_ident(name)) { first_pass_record.append_enum(&mut program, e); } } for (name, d) in first_pass_record.dictionaries.iter() { if filter(&camel_case_ident(name)) { first_pass_record.append_dictionary(&mut program, d); } } for (name, n) in first_pass_record.namespaces.iter() { if filter(&snake_case_ident(name)) { let prog = first_pass_record.append_ns(name, n); submodules.push((snake_case_ident(name).to_string(), prog)); } } for (name, d) in first_pass_record.interfaces.iter() { if filter(&camel_case_ident(name)) { first_pass_record.append_interface(&mut program, name, d); } } for (name, d) in first_pass_record.callback_interfaces.iter() { if filter(&camel_case_ident(name)) { first_pass_record.append_callback_interface(&mut program, d); } } // Prune out `extends` annotations that aren't defined as these shouldn't // prevent the type from being usable entirely. They're just there for // `AsRef` and such implementations. for import in program.imports.iter_mut() { if let ast::ImportKind::Type(t) = &mut import.kind { t.extends.retain(|n| { let ident = &n.segments.last().unwrap().ident; first_pass_record.builtin_idents.contains(ident) || filter(&ident.to_string()) }); } } Ok(Program { main: program, submodules: submodules, }) } /// Compile the given WebIDL source text into Rust source text containing /// `wasm-bindgen` bindings to the things described in the WebIDL. pub fn compile(webidl_source: &str, allowed_types: Option<&[&str]>) -> Result { let ast = parse(webidl_source, allowed_types)?; Ok(compile_ast(ast)) } fn builtin_idents() -> BTreeSet { BTreeSet::from_iter( vec![ "str", "char", "bool", "JsValue", "u8", "i8", "u16", "i16", "u32", "i32", "u64", "i64", "usize", "isize", "f32", "f64", "Result", "String", "Vec", "Option", "Array", "ArrayBuffer", "Object", "Promise", "Function", "Clamped", ] .into_iter() .map(|id| proc_macro2::Ident::new(id, proc_macro2::Span::call_site())), ) } fn immutable_slice_whitelist() -> BTreeSet<&'static str> { BTreeSet::from_iter(vec![ // WebGlRenderingContext, WebGl2RenderingContext "uniform1fv", "uniform2fv", "uniform3fv", "uniform4fv", "uniform1iv", "uniform2iv", "uniform3iv", "uniform4iv", "uniformMatrix2fv", "uniformMatrix3fv", "uniformMatrix4fv", "vertexAttrib1fv", "vertexAttrib2fv", "vertexAttrib3fv", "vertexAttrib4fv", "bufferData", "bufferSubData", "texImage2D", "texSubImage2D", "compressedTexImage2D", // WebGl2RenderingContext "uniform1uiv", "uniform2uiv", "uniform3uiv", "uniform4uiv", "texImage3D", "texSubImage3D", "compressedTexImage3D", "clearBufferfv", "clearBufferiv", "clearBufferuiv", // TODO: Add another type's functions here. Leave a comment header with the type name ]) } /// Run codegen on the AST to generate rust code. fn compile_ast(mut ast: Program) -> String { // Iteratively prune all entries from the AST which reference undefined // fields. Each pass may remove definitions of types and so we need to // reexecute this pass to see if we need to keep removing types until we // reach a steady state. let builtin = builtin_idents(); let mut all_definitions = BTreeSet::new(); let track = env::var_os("__WASM_BINDGEN_DUMP_FEATURES"); loop { let mut defined = builtin.clone(); { let mut cb = |id: &Ident| { defined.insert(id.clone()); if track.is_some() { all_definitions.insert(id.clone()); } }; ast.main.imported_type_definitions(&mut cb); for (name, m) in ast.submodules.iter() { cb(&Ident::new(name, Span::call_site())); m.imported_type_references(&mut cb); } } let changed = ast .main .remove_undefined_imports(&|id| defined.contains(id)) || ast .submodules .iter_mut() .any(|(_, m)| m.remove_undefined_imports(&|id| defined.contains(id))); if !changed { break; } } if let Some(path) = track { let contents = all_definitions .into_iter() .filter(|def| !builtin.contains(def)) .map(|s| format!("{} = []", s)) .collect::>() .join("\n"); fs::write(path, contents).unwrap(); } let mut tokens = proc_macro2::TokenStream::new(); if let Err(e) = ast.main.try_to_tokens(&mut tokens) { e.panic(); } for (name, m) in ast.submodules.iter() { let mut m_tokens = proc_macro2::TokenStream::new(); if let Err(e) = m.try_to_tokens(&mut m_tokens) { e.panic(); } let name = Ident::new(name, Span::call_site()); (quote! { pub mod #name { #m_tokens } }) .to_tokens(&mut tokens); } tokens.to_string() } impl<'src> FirstPassRecord<'src> { fn append_enum(&self, program: &mut ast::Program, enum_: &'src weedle::EnumDefinition<'src>) { let variants = &enum_.values.body.list; program.imports.push(ast::Import { module: ast::ImportModule::None, js_namespace: None, kind: ast::ImportKind::Enum(ast::ImportEnum { vis: public(), name: rust_ident(camel_case_ident(enum_.identifier.0).as_str()), variants: variants .iter() .map(|v| { if !v.0.is_empty() { rust_ident(camel_case_ident(&v.0).as_str()) } else { rust_ident("None") } }) .collect(), variant_values: variants.iter().map(|v| v.0.to_string()).collect(), rust_attrs: vec![syn::parse_quote!(#[derive(Copy, Clone, PartialEq, Debug)])], }), }); } // tons more data for what's going on here at // https://www.w3.org/TR/WebIDL-1/#idl-dictionaries fn append_dictionary( &self, program: &mut ast::Program, data: &first_pass::DictionaryData<'src>, ) { let def = match data.definition { Some(def) => def, None => return, }; let mut fields = Vec::new(); if !self.append_dictionary_members(def.identifier.0, &mut fields) { return; } let name = rust_ident(&camel_case_ident(def.identifier.0)); let extra_feature = name.to_string(); for field in fields.iter_mut() { let mut doc_comment = Some(format!( "Configure the `{}` field of this object\n", field.js_name, )); self.append_required_features_doc(&*field, &mut doc_comment, &[&extra_feature]); field.doc_comment = doc_comment; } let mut doc_comment = format!("The `{}` dictionary\n", def.identifier.0); if let Some(s) = self.required_doc_string(vec![name.clone()]) { doc_comment.push_str(&s); } let mut dict = ast::Dictionary { name, fields, ctor: true, doc_comment: Some(doc_comment), ctor_doc_comment: None, }; let mut ctor_doc_comment = Some(format!("Construct a new `{}`\n", def.identifier.0)); self.append_required_features_doc(&dict, &mut ctor_doc_comment, &[&extra_feature]); dict.ctor_doc_comment = ctor_doc_comment; program.dictionaries.push(dict); } fn append_dictionary_members( &self, dict: &'src str, dst: &mut Vec, ) -> bool { let dict_data = &self.dictionaries[&dict]; let definition = dict_data.definition.unwrap(); // > The order of the dictionary members on a given dictionary is // > such that inherited dictionary members are ordered before // > non-inherited members ... if let Some(parent) = &definition.inheritance { if !self.append_dictionary_members(parent.identifier.0, dst) { return false; } } // > ... and the dictionary members on the one dictionary // > definition (including any partial dictionary definitions) are // > ordered lexicographically by the Unicode codepoints that // > comprise their identifiers. let start = dst.len(); let members = definition.members.body.iter(); let partials = dict_data.partials.iter().flat_map(|d| &d.members.body); for member in members.chain(partials) { match self.dictionary_field(member) { Some(f) => dst.push(f), None => { log::warn!( "unsupported dictionary field {:?}", (dict, member.identifier.0), ); // If this is required then we can't support the // dictionary at all, but if it's not required we can // avoid generating bindings for the field and keep // going otherwise. if member.required.is_some() { return false; } } } } dst[start..].sort_by_key(|f| f.js_name.clone()); return true; } fn dictionary_field( &self, field: &'src DictionaryMember<'src>, ) -> Option { // use argument position now as we're just binding setters let ty = field .type_ .to_idl_type(self) .to_syn_type(TypePosition::Argument)?; // Slice types aren't supported because they don't implement // `Into` match ty { syn::Type::Reference(ref i) => match &*i.elem { syn::Type::Slice(_) => return None, _ => (), }, syn::Type::Path(ref path, ..) => // check that our inner don't contains slices either { for seg in path.path.segments.iter() { if let syn::PathArguments::AngleBracketed(ref arg) = seg.arguments { for elem in &arg.args { if let syn::GenericArgument::Type(syn::Type::Reference(ref i)) = elem { match &*i.elem { syn::Type::Slice(_) => return None, _ => (), } } } } } } _ => (), }; // Similarly i64/u64 aren't supported because they don't // implement `Into` let mut any_64bit = false; ty.imported_type_references(&mut |i| { any_64bit = any_64bit || i == "u64" || i == "i64"; }); if any_64bit { return None; } Some(ast::DictionaryField { required: field.required.is_some(), rust_name: rust_ident(&snake_case_ident(field.identifier.0)), js_name: field.identifier.0.to_string(), ty, doc_comment: None, }) } fn append_ns( &'src self, name: &'src str, ns: &'src first_pass::NamespaceData<'src>, ) -> ast::Program { let mut ret = Default::default(); for (id, data) in ns.operations.iter() { self.append_ns_member(&mut ret, name, id, data); } return ret; } fn append_ns_member( &self, module: &mut ast::Program, self_name: &'src str, id: &OperationId<'src>, data: &OperationData<'src>, ) { let name = match id { OperationId::Operation(Some(name)) => name, OperationId::Constructor(_) | OperationId::Operation(None) | OperationId::IndexingGetter | OperationId::IndexingSetter | OperationId::IndexingDeleter => { log::warn!("Unsupported unnamed operation: on {:?}", self_name); return; } }; let doc_comment = format!( "The `{}.{}()` function\n\n{}", self_name, name, mdn_doc(self_name, Some(&name)) ); let kind = ast::ImportFunctionKind::Normal; let extra = snake_case_ident(self_name); let extra = &[&extra[..]]; for mut import_function in self.create_imports(None, kind, id, data) { let mut doc = Some(doc_comment.clone()); self.append_required_features_doc(&import_function, &mut doc, extra); import_function.doc_comment = doc; module.imports.push(ast::Import { module: ast::ImportModule::None, js_namespace: Some(raw_ident(self_name)), kind: ast::ImportKind::Function(import_function), }); } } fn append_const( &self, program: &mut ast::Program, self_name: &'src str, member: &'src weedle::interface::ConstMember<'src>, ) { let idl_type = member.const_type.to_idl_type(self); let ty = match idl_type.to_syn_type(TypePosition::Return) { Some(ty) => ty, None => { log::warn!( "Cannot convert const type to syn type: {:?} in {:?} on {:?}", idl_type, member, self_name ); return; } }; program.consts.push(ast::Const { vis: public(), name: rust_ident(shouty_snake_case_ident(member.identifier.0).as_str()), class: Some(rust_ident(camel_case_ident(&self_name).as_str())), ty, value: webidl_const_v_to_backend_const_v(&member.const_value), }); } fn append_interface( &self, program: &mut ast::Program, name: &'src str, data: &InterfaceData<'src>, ) { let mut doc_comment = Some(format!("The `{}` object\n\n{}", name, mdn_doc(name, None),)); let mut attrs = Vec::new(); attrs.push(syn::parse_quote!( #[derive(Debug, Clone, PartialEq, Eq)] )); self.add_deprecated(data, &mut attrs); let mut import_type = ast::ImportType { vis: public(), rust_name: rust_ident(camel_case_ident(name).as_str()), js_name: name.to_string(), attrs, doc_comment: None, instanceof_shim: format!("__widl_instanceof_{}", name), is_type_of: if data.has_interface { None } else { Some(syn::parse_quote! { |_| false }) }, extends: Vec::new(), vendor_prefixes: Vec::new(), }; // whitelist a few names that have known polyfills match name { "AudioContext" | "OfflineAudioContext" => { import_type .vendor_prefixes .push(Ident::new("webkit", Span::call_site())); } _ => {} } let extra = camel_case_ident(name); let extra = &[&extra[..]]; self.append_required_features_doc(&import_type, &mut doc_comment, extra); import_type.extends = self .all_superclasses(name) .map(|name| Ident::new(&name, Span::call_site()).into()) .chain(Some(Ident::new("Object", Span::call_site()).into())) .collect(); import_type.doc_comment = doc_comment; program.imports.push(ast::Import { module: ast::ImportModule::None, js_namespace: None, kind: ast::ImportKind::Type(import_type), }); for (id, op_data) in data.operations.iter() { self.member_operation(program, name, data, id, op_data); } for member in data.consts.iter() { self.append_const(program, name, member); } for member in data.attributes.iter() { self.member_attribute( program, name, data, member.modifier, member.readonly.is_some(), &member.type_, member.identifier.0, &member.attributes, data.definition_attributes, ); } for mixin_data in self.all_mixins(name) { for (id, op_data) in mixin_data.operations.iter() { self.member_operation(program, name, data, id, op_data); } for member in &mixin_data.consts { self.append_const(program, name, member); } for member in &mixin_data.attributes { self.member_attribute( program, name, data, if let Some(s) = member.stringifier { Some(weedle::interface::StringifierOrInheritOrStatic::Stringifier(s)) } else { None }, member.readonly.is_some(), &member.type_, member.identifier.0, &member.attributes, data.definition_attributes, ); } } } fn member_attribute( &self, program: &mut ast::Program, self_name: &'src str, data: &InterfaceData<'src>, modifier: Option, readonly: bool, type_: &'src weedle::types::AttributedType<'src>, identifier: &'src str, attrs: &'src Option>, container_attrs: Option<&'src ExtendedAttributeList<'src>>, ) { use weedle::interface::StringifierOrInheritOrStatic::*; let is_static = match modifier { Some(Stringifier(_)) => unreachable!(), // filtered out earlier Some(Inherit(_)) => false, Some(Static(_)) => true, None => false, }; for mut import_function in self.create_getter( identifier, &type_.type_, self_name, is_static, attrs, container_attrs, ) { let mut doc = import_function.doc_comment.take(); self.append_required_features_doc(&import_function, &mut doc, &[]); import_function.doc_comment = doc; program.imports.push(wrap_import_function(import_function)); } if !readonly { for mut import_function in self.create_setter( identifier, &type_.type_, self_name, is_static, attrs, container_attrs, ) { let mut doc = import_function.doc_comment.take(); self.append_required_features_doc(&import_function, &mut doc, &[]); import_function.doc_comment = doc; self.add_deprecated(data, &mut import_function.function.rust_attrs); program.imports.push(wrap_import_function(import_function)); } } } fn member_operation( &self, program: &mut ast::Program, self_name: &str, data: &InterfaceData<'src>, id: &OperationId<'src>, op_data: &OperationData<'src>, ) { let import_function_kind = |opkind| self.import_function_kind(self_name, op_data.is_static, opkind); let kind = match id { OperationId::Constructor(ctor_name) => { let self_ty = ident_ty(rust_ident(&camel_case_ident(self_name))); ast::ImportFunctionKind::Method { class: ctor_name.0.to_string(), ty: self_ty.clone(), kind: ast::MethodKind::Constructor, } } OperationId::Operation(_) => import_function_kind(ast::OperationKind::Regular), OperationId::IndexingGetter => import_function_kind(ast::OperationKind::IndexingGetter), OperationId::IndexingSetter => import_function_kind(ast::OperationKind::IndexingSetter), OperationId::IndexingDeleter => { import_function_kind(ast::OperationKind::IndexingDeleter) } }; let doc = match id { OperationId::Operation(None) => Some(String::new()), OperationId::Constructor(_) => Some(format!( "The `new {}(..)` constructor, creating a new \ instance of `{0}`\n\n{}", self_name, mdn_doc(self_name, Some(self_name)) )), OperationId::Operation(Some(name)) => Some(format!( "The `{}()` method\n\n{}", name, mdn_doc(self_name, Some(name)) )), OperationId::IndexingGetter => Some(format!("The indexing getter\n\n")), OperationId::IndexingSetter => Some(format!("The indexing setter\n\n")), OperationId::IndexingDeleter => Some(format!("The indexing deleter\n\n")), }; let attrs = data.definition_attributes; for mut method in self.create_imports(attrs, kind, id, op_data) { let mut doc = doc.clone(); self.append_required_features_doc(&method, &mut doc, &[]); method.doc_comment = doc; self.add_deprecated(data, &mut method.function.rust_attrs); program.imports.push(wrap_import_function(method)); } } fn add_deprecated(&self, data: &InterfaceData<'src>, dst: &mut Vec) { let msg = match &data.deprecated { Some(s) => s, None => return, }; dst.push(syn::parse_quote!( #[deprecated(note = #msg)] )); } fn append_required_features_doc( &self, item: impl ImportedTypeReferences, doc: &mut Option, extra: &[&str], ) { let doc = match doc { Some(doc) => doc, None => return, }; let mut required = extra .iter() .map(|s| Ident::new(s, Span::call_site())) .collect::>(); item.imported_type_references(&mut |f| { if !self.builtin_idents.contains(f) { required.insert(f.clone()); } }); if required.len() == 0 { return; } if let Some(extra) = self.required_doc_string(required) { doc.push_str(&extra); } } fn required_doc_string( &self, features: impl IntoIterator, ) -> Option { let features = features.into_iter().collect::>(); if features.len() == 0 { return None; } let list = features .iter() .map(|ident| format!("`{}`", ident)) .collect::>() .join(", "); Some(format!( "\n\n*This API requires the following crate features \ to be activated: {}*", list, )) } fn append_callback_interface( &self, program: &mut ast::Program, item: &CallbackInterfaceData<'src>, ) { let mut fields = Vec::new(); for member in item.definition.members.body.iter() { match member { InterfaceMember::Operation(op) => { let identifier = match op.identifier { Some(i) => i.0, None => continue, }; let pos = TypePosition::Argument; fields.push(ast::DictionaryField { required: false, rust_name: rust_ident(&snake_case_ident(identifier)), js_name: identifier.to_string(), ty: idl_type::IdlType::Callback.to_syn_type(pos).unwrap(), doc_comment: None, }); } _ => { log::warn!( "skipping callback interface member on {}", item.definition.identifier.0 ); } } } program.dictionaries.push(ast::Dictionary { name: rust_ident(&camel_case_ident(item.definition.identifier.0)), fields, ctor: true, doc_comment: None, ctor_doc_comment: None, }); } } wasm-bindgen-webidl-0.2.58/src/util.rs010066400017500001750000000716331360470371600160030ustar0000000000000000use std::iter::FromIterator; use std::ptr; use heck::{CamelCase, ShoutySnakeCase, SnakeCase}; use proc_macro2::{Ident, Span}; use syn; use wasm_bindgen_backend::ast; use wasm_bindgen_backend::util::{ident_ty, leading_colon_path_ty, raw_ident, rust_ident}; use weedle; use weedle::attribute::{ExtendedAttribute, ExtendedAttributeList, IdentifierOrString}; use weedle::literal::{ConstValue, FloatLit, IntegerLit}; use crate::first_pass::{FirstPassRecord, OperationData, OperationId, Signature}; use crate::idl_type::{IdlType, ToIdlType}; /// For variadic operations an overload with a `js_sys::Array` argument is generated alongside with /// `operation_name_0`, `operation_name_1`, `operation_name_2`, ..., `operation_name_n` overloads /// which have the count of arguments for passing values to the variadic argument /// in their names, where `n` is this constant. const MAX_VARIADIC_ARGUMENTS_COUNT: usize = 7; /// Take a type and create an immutable shared reference to that type. pub(crate) fn shared_ref(ty: syn::Type, mutable: bool) -> syn::Type { syn::TypeReference { and_token: Default::default(), lifetime: None, mutability: if mutable { Some(syn::token::Mut::default()) } else { None }, elem: Box::new(ty), } .into() } /// Fix case of identifiers like `HTMLBRElement` or `texImage2D` fn fix_ident(identifier: &str) -> String { identifier .replace("HTML", "HTML_") .replace("1D", "_1d") .replace("2D", "_2d") .replace("3D", "_3d") } /// Convert an identifier to camel case pub fn camel_case_ident(identifier: &str) -> String { fix_ident(identifier).to_camel_case() } /// Convert an identifier to shouty snake case pub fn shouty_snake_case_ident(identifier: &str) -> String { fix_ident(identifier).to_shouty_snake_case() } /// Convert an identifier to snake case pub fn snake_case_ident(identifier: &str) -> String { fix_ident(identifier).to_snake_case() } // Returns a link to MDN pub fn mdn_doc(class: &str, method: Option<&str>) -> String { let mut link = format!("https://developer.mozilla.org/en-US/docs/Web/API/{}", class); if let Some(method) = method { link.push_str(&format!("/{}", method)); } format!("[MDN Documentation]({})", link).into() } // Array type is borrowed for arguments (`&mut [T]` or `&[T]`) and owned for return value (`Vec`). pub(crate) fn array(base_ty: &str, pos: TypePosition, immutable: bool) -> syn::Type { match pos { TypePosition::Argument => { shared_ref( slice_ty(ident_ty(raw_ident(base_ty))), /*mutable =*/ !immutable, ) } TypePosition::Return => vec_ty(ident_ty(raw_ident(base_ty))), } } /// Map a webidl const value to the correct wasm-bindgen const value pub fn webidl_const_v_to_backend_const_v(v: &ConstValue) -> ast::ConstValue { use std::f64::{INFINITY, NAN, NEG_INFINITY}; match *v { ConstValue::Boolean(b) => ast::ConstValue::BooleanLiteral(b.0), ConstValue::Float(FloatLit::NegInfinity(_)) => ast::ConstValue::FloatLiteral(NEG_INFINITY), ConstValue::Float(FloatLit::Infinity(_)) => ast::ConstValue::FloatLiteral(INFINITY), ConstValue::Float(FloatLit::NaN(_)) => ast::ConstValue::FloatLiteral(NAN), ConstValue::Float(FloatLit::Value(s)) => { ast::ConstValue::FloatLiteral(s.0.parse().unwrap()) } ConstValue::Integer(lit) => { let mklit = |orig_text: &str, base: u32, offset: usize| { let (negative, text) = if orig_text.starts_with("-") { (true, &orig_text[1..]) } else { (false, orig_text) }; if text == "0" { return ast::ConstValue::SignedIntegerLiteral(0); } let text = &text[offset..]; let n = u64::from_str_radix(text, base) .unwrap_or_else(|_| panic!("literal too big: {}", orig_text)); if negative { let n = if n > (i64::min_value() as u64).wrapping_neg() { panic!("literal too big: {}", orig_text) } else { n.wrapping_neg() as i64 }; ast::ConstValue::SignedIntegerLiteral(n) } else { ast::ConstValue::UnsignedIntegerLiteral(n) } }; match lit { IntegerLit::Hex(h) => mklit(h.0, 16, 2), // leading 0x IntegerLit::Oct(h) => mklit(h.0, 8, 1), // leading 0 IntegerLit::Dec(h) => mklit(h.0, 10, 0), } } ConstValue::Null(_) => ast::ConstValue::Null, } } /// From `ident` and `Ty`, create `ident: Ty` for use in e.g. `fn(ident: Ty)`. fn simple_fn_arg(ident: Ident, ty: syn::Type) -> syn::PatType { syn::PatType { pat: Box::new(syn::Pat::Ident(syn::PatIdent { attrs: Vec::new(), by_ref: None, ident, mutability: None, subpat: None, })), colon_token: Default::default(), ty: Box::new(ty), attrs: Vec::new(), } } /// Create `()`. fn unit_ty() -> syn::Type { syn::Type::Tuple(syn::TypeTuple { paren_token: Default::default(), elems: syn::punctuated::Punctuated::new(), }) } /// From `T` create `Result`. fn result_ty(t: syn::Type) -> syn::Type { let js_value = leading_colon_path_ty(vec![rust_ident("wasm_bindgen"), rust_ident("JsValue")]); let arguments = syn::PathArguments::AngleBracketed(syn::AngleBracketedGenericArguments { colon2_token: None, lt_token: Default::default(), args: FromIterator::from_iter(vec![ syn::GenericArgument::Type(t), syn::GenericArgument::Type(js_value), ]), gt_token: Default::default(), }); let ident = raw_ident("Result"); let seg = syn::PathSegment { ident, arguments }; let path: syn::Path = seg.into(); let ty = syn::TypePath { qself: None, path }; ty.into() } /// From `T` create `[T]`. pub(crate) fn slice_ty(t: syn::Type) -> syn::Type { syn::TypeSlice { bracket_token: Default::default(), elem: Box::new(t), } .into() } /// From `T` create `Vec`. pub(crate) fn vec_ty(t: syn::Type) -> syn::Type { let arguments = syn::PathArguments::AngleBracketed(syn::AngleBracketedGenericArguments { colon2_token: None, lt_token: Default::default(), args: FromIterator::from_iter(vec![syn::GenericArgument::Type(t)]), gt_token: Default::default(), }); let ident = raw_ident("Vec"); let seg = syn::PathSegment { ident, arguments }; let path: syn::Path = seg.into(); let ty = syn::TypePath { qself: None, path }; ty.into() } /// From `T` create `Option` pub(crate) fn option_ty(t: syn::Type) -> syn::Type { let arguments = syn::PathArguments::AngleBracketed(syn::AngleBracketedGenericArguments { colon2_token: None, lt_token: Default::default(), args: FromIterator::from_iter(vec![syn::GenericArgument::Type(t)]), gt_token: Default::default(), }); let ident = raw_ident("Option"); let seg = syn::PathSegment { ident, arguments }; let path: syn::Path = seg.into(); let ty = syn::TypePath { qself: None, path }; ty.into() } /// Possible positions for a type in a function signature. #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum TypePosition { Argument, Return, } impl<'src> FirstPassRecord<'src> { pub fn create_one_function<'a>( &self, js_name: &str, rust_name: &str, idl_arguments: impl Iterator)>, ret: &IdlType<'src>, kind: ast::ImportFunctionKind, structural: bool, catch: bool, variadic: bool, doc_comment: Option, ) -> Option where 'src: 'a, { // Convert all of the arguments from their IDL type to a `syn` type, // ready to pass to the backend. // // Note that for non-static methods we add a `&self` type placeholder, // but this type isn't actually used so it's just here for show mostly. let mut arguments = if let &ast::ImportFunctionKind::Method { ref ty, kind: ast::MethodKind::Operation(ast::Operation { is_static: false, .. }), .. } = &kind { let mut res = Vec::with_capacity(idl_arguments.size_hint().0 + 1); res.push(simple_fn_arg( raw_ident("self_"), shared_ref(ty.clone(), false), )); res } else { Vec::with_capacity(idl_arguments.size_hint().0) }; let idl_arguments: Vec<_> = idl_arguments.collect(); let arguments_count = idl_arguments.len(); for (i, (argument_name, idl_type)) in idl_arguments.into_iter().enumerate() { let syn_type = match idl_type.to_syn_type(TypePosition::Argument) { Some(t) => t, None => { log::warn!( "Unsupported argument type: {:?} on {:?}", idl_type, rust_name ); return None; } }; let syn_type = if variadic && i == arguments_count - 1 { let path = vec![rust_ident("js_sys"), rust_ident("Array")]; shared_ref(leading_colon_path_ty(path), false) } else { syn_type }; let argument_name = rust_ident(&argument_name.to_snake_case()); arguments.push(simple_fn_arg(argument_name, syn_type)); } // Convert the return type to a `syn` type, handling the `catch` // attribute here to use a `Result` in Rust. let ret = match ret { IdlType::Void => None, ret @ _ => match ret.to_syn_type(TypePosition::Return) { Some(ret) => Some(ret), None => { log::warn!("Unsupported return type: {:?} on {:?}", ret, rust_name); return None; } }, }; let js_ret = ret.clone(); let ret = if catch { Some(ret.map_or_else(|| result_ty(unit_ty()), result_ty)) } else { ret }; Some(ast::ImportFunction { function: ast::Function { name: js_name.to_string(), name_span: Span::call_site(), renamed_via_js_name: false, arguments, ret: ret.clone(), rust_attrs: vec![], rust_vis: public(), r#async: false, }, rust_name: rust_ident(rust_name), js_ret: js_ret.clone(), variadic, catch, structural, assert_no_shim: false, shim: { let ns = match kind { ast::ImportFunctionKind::Normal => "", ast::ImportFunctionKind::Method { ref class, .. } => class, }; raw_ident(&format!("__widl_f_{}_{}", rust_name, ns)) }, kind, doc_comment, }) } /// Create a wasm-bindgen getter method, if possible. pub fn create_getter( &self, name: &str, ty: &weedle::types::Type<'src>, self_name: &str, is_static: bool, attrs: &Option, container_attrs: Option<&ExtendedAttributeList>, ) -> Option { let kind = ast::OperationKind::Getter(Some(raw_ident(name))); let kind = self.import_function_kind(self_name, is_static, kind); let ret = ty.to_idl_type(self); self.create_one_function( &name, &snake_case_ident(name), None.into_iter(), &ret, kind, is_structural(attrs.as_ref(), container_attrs), throws(attrs), false, Some(format!( "The `{}` getter\n\n{}", name, mdn_doc(self_name, Some(name)) )), ) } /// Create a wasm-bindgen setter method, if possible. pub fn create_setter( &self, name: &str, field_ty: &weedle::types::Type<'src>, self_name: &str, is_static: bool, attrs: &Option, container_attrs: Option<&ExtendedAttributeList>, ) -> Option { let kind = ast::OperationKind::Setter(Some(raw_ident(name))); let kind = self.import_function_kind(self_name, is_static, kind); let field_ty = field_ty.to_idl_type(self); self.create_one_function( &name, &format!("set_{}", name).to_snake_case(), Some((name, &field_ty)).into_iter(), &IdlType::Void, kind, is_structural(attrs.as_ref(), container_attrs), throws(attrs), false, Some(format!( "The `{}` setter\n\n{}", name, mdn_doc(self_name, Some(name)) )), ) } pub fn import_function_kind( &self, self_name: &str, is_static: bool, operation_kind: ast::OperationKind, ) -> ast::ImportFunctionKind { let operation = ast::Operation { is_static, kind: operation_kind, }; let ty = ident_ty(rust_ident(camel_case_ident(&self_name).as_str())); ast::ImportFunctionKind::Method { class: self_name.to_string(), ty, kind: ast::MethodKind::Operation(operation), } } pub fn create_imports( &self, container_attrs: Option<&ExtendedAttributeList<'src>>, kind: ast::ImportFunctionKind, id: &OperationId<'src>, data: &OperationData<'src>, ) -> Vec { // First up, prune all signatures that reference unsupported arguments. // We won't consider these until said arguments are implemented. // // Note that we handle optional arguments as well. Optional arguments // should only appear at the end of argument lists and when we see one // we can simply push our signature so far onto the list for the // signature where that and all remaining optional arguments are // undefined. let mut signatures = Vec::new(); for signature in data.signatures.iter() { let mut idl_args = Vec::with_capacity(signature.args.len()); for (i, arg) in signature.args.iter().enumerate() { if arg.optional { assert!( signature.args[i..] .iter() .all(|arg| arg.optional || arg.variadic), "Not optional or variadic argument after optional argument: {:?}", signature.args, ); signatures.push((signature, idl_args.clone())); } let idl_type = arg.ty.to_idl_type(self); let idl_type = self.maybe_adjust(idl_type, id); idl_args.push(idl_type); } signatures.push((signature, idl_args)); } // Next expand all the signatures in `data` into all signatures that // we're going to generate. These signatures will be used to determine // the names for all the various functions. #[derive(Clone)] struct ExpandedSig<'a> { orig: &'a Signature<'a>, args: Vec>, } let mut actual_signatures = Vec::new(); for (signature, idl_args) in signatures.iter() { let start = actual_signatures.len(); // Start off with an empty signature, this'll handle zero-argument // cases and otherwise the loop below will continue to add on to this. actual_signatures.push(ExpandedSig { orig: signature, args: Vec::with_capacity(signature.args.len()), }); for (i, idl_type) in idl_args.iter().enumerate() { // small sanity check assert!(start < actual_signatures.len()); for sig in actual_signatures[start..].iter() { assert_eq!(sig.args.len(), i); } // The first element of the flattened type gets pushed directly // in-place, but all other flattened types will cause new // signatures to be created. let cur = actual_signatures.len(); for (j, idl_type) in idl_type.flatten().into_iter().enumerate() { for k in start..cur { if j == 0 { actual_signatures[k].args.push(idl_type.clone()); } else { let mut sig = actual_signatures[k].clone(); assert_eq!(sig.args.len(), i + 1); sig.args.truncate(i); sig.args.push(idl_type.clone()); actual_signatures.push(sig); } } } } } let (name, force_structural, force_throws) = match id { // Constructors aren't annotated with `[Throws]` extended attributes // (how could they be, since they themselves are extended // attributes?) so we must conservatively assume that they can // always throw. // // From https://heycam.github.io/webidl/#Constructor (emphasis // mine): // // > The prose definition of a constructor must either return an IDL // > value of a type corresponding to the interface the // > `[Constructor]` extended attribute appears on, **or throw an // > exception**. OperationId::Constructor(_) => ("new", false, true), OperationId::Operation(Some(s)) => (*s, false, false), OperationId::Operation(None) => { log::warn!("unsupported unnamed operation"); return Vec::new(); } OperationId::IndexingGetter => ("get", true, false), OperationId::IndexingSetter => ("set", true, false), OperationId::IndexingDeleter => ("delete", true, false), }; let mut ret = Vec::new(); for signature in actual_signatures.iter() { // Ignore signatures with invalid return types // // TODO: overloads probably never change return types, so we should // do this much earlier to avoid all the above work if // possible. let ret_ty = signature.orig.ret.to_idl_type(self); let mut rust_name = snake_case_ident(name); let mut first = true; for (i, arg) in signature.args.iter().enumerate() { // Find out if any other known signature either has the same // name for this argument or a different type for this argument. let mut any_same_name = false; let mut any_different_type = false; let mut any_different = false; let arg_name = signature.orig.args[i].name; for other in actual_signatures.iter() { if other.orig.args.get(i).map(|s| s.name) == Some(arg_name) { if !ptr::eq(signature, other) { any_same_name = true; } } if let Some(other) = other.args.get(i) { if other != arg { any_different_type = true; any_different = true; } } else { any_different = true; } } // If all signatures have the exact same type for this argument, // then there's nothing to disambiguate so we don't modify the // name. if !any_different { continue; } if first { rust_name.push_str("_with_"); first = false; } else { rust_name.push_str("_and_"); } // If this name of the argument for this signature is unique // then that's a bit more human readable so we include it in the // method name. Otherwise the type name should disambiguate // correctly. // // If any signature's argument has the same name as our argument // then we can't use that if the types are also the same because // otherwise it could be ambiguous. if any_same_name && any_different_type { arg.push_snake_case_name(&mut rust_name); } else { rust_name.push_str(&snake_case_ident(arg_name)); } } let structural = force_structural || is_structural(signature.orig.attrs.as_ref(), container_attrs); let catch = force_throws || throws(&signature.orig.attrs); let ret_ty = if id == &OperationId::IndexingGetter { // All indexing getters should return optional values (or // otherwise be marked with catch). match ret_ty { IdlType::Nullable(_) => ret_ty, ref ty @ _ => { if catch { ret_ty } else { IdlType::Nullable(Box::new(ty.clone())) } } } } else { ret_ty }; let variadic = signature.args.len() == signature.orig.args.len() && signature .orig .args .last() .map(|arg| arg.variadic) .unwrap_or(false); ret.extend( self.create_one_function( name, &rust_name, signature .args .iter() .zip(&signature.orig.args) .map(|(idl_type, orig_arg)| (orig_arg.name, idl_type)), &ret_ty, kind.clone(), structural, catch, variadic, None, ), ); if !variadic { continue; } let last_idl_type = &signature.args[signature.args.len() - 1]; let last_name = signature.orig.args[signature.args.len() - 1].name; for i in 0..=MAX_VARIADIC_ARGUMENTS_COUNT { ret.extend( self.create_one_function( name, &format!("{}_{}", rust_name, i), signature.args[..signature.args.len() - 1] .iter() .zip(&signature.orig.args) .map(|(idl_type, orig_arg)| (orig_arg.name.to_string(), idl_type)) .chain((1..=i).map(|j| (format!("{}_{}", last_name, j), last_idl_type))) .collect::>() .iter() .map(|(name, idl_type)| (&name[..], idl_type.clone())), &ret_ty, kind.clone(), structural, catch, false, None, ), ); } } return ret; } /// When generating our web_sys APIs we default to setting slice references that /// get passed to JS as mutable in case they get mutated in JS. /// /// In certain cases we know for sure that the slice will not get mutated - for /// example when working with the WebGlRenderingContext APIs. /// /// Here we implement a whitelist for those cases. This whitelist is currently /// maintained by hand. /// /// When adding to this whitelist add tests to crates/web-sys/tests/wasm/whitelisted_immutable_slices.rs fn maybe_adjust<'a>(&self, mut idl_type: IdlType<'a>, id: &'a OperationId) -> IdlType<'a> { let op = match id { OperationId::Operation(Some(op)) => op, _ => return idl_type, }; if self.immutable_slice_whitelist.contains(op) { flag_slices_immutable(&mut idl_type) } idl_type } } /// Search for an attribute by name in some webidl object's attributes. fn has_named_attribute(list: Option<&ExtendedAttributeList>, attribute: &str) -> bool { let list = match list { Some(list) => list, None => return false, }; list.body.list.iter().any(|attr| match attr { ExtendedAttribute::NoArgs(name) => (name.0).0 == attribute, _ => false, }) } fn has_ident_attribute(list: Option<&ExtendedAttributeList>, ident: &str) -> bool { let list = match list { Some(list) => list, None => return false, }; list.body.list.iter().any(|attr| match attr { ExtendedAttribute::Ident(id) => id.lhs_identifier.0 == ident, ExtendedAttribute::IdentList(id) => id.identifier.0 == ident, _ => false, }) } /// ChromeOnly is for things that are only exposed to privileged code in Firefox. pub fn is_chrome_only(ext_attrs: &Option) -> bool { has_named_attribute(ext_attrs.as_ref(), "ChromeOnly") } /// Whether a webidl object is marked as a no interface object. pub fn is_no_interface_object(ext_attrs: &Option) -> bool { has_named_attribute(ext_attrs.as_ref(), "NoInterfaceObject") } pub fn get_rust_deprecated<'a>(ext_attrs: &Option>) -> Option<&'a str> { ext_attrs .as_ref()? .body .list .iter() .filter_map(|attr| match attr { ExtendedAttribute::Ident(id) => Some(id), _ => None, }) .filter(|attr| attr.lhs_identifier.0 == "RustDeprecated") .filter_map(|ident| match ident.rhs { IdentifierOrString::String(s) => Some(s), IdentifierOrString::Identifier(_) => None, }) .next() .map(|s| s.0) } /// Whether a webidl object is marked as structural. pub fn is_structural( item_attrs: Option<&ExtendedAttributeList>, container_attrs: Option<&ExtendedAttributeList>, ) -> bool { // Note that once host bindings is implemented we'll want to switch this // from `true` to `false`, and then we'll want to largely read information // from the WebIDL about whether to use structural bindings or not. true || has_named_attribute(item_attrs, "Unforgeable") || has_named_attribute(container_attrs, "Unforgeable") || has_ident_attribute(container_attrs, "Global") } /// Whether a webidl object is marked as throwing. pub fn throws(attrs: &Option) -> bool { has_named_attribute(attrs.as_ref(), "Throws") } /// Create a syn `pub` token pub fn public() -> syn::Visibility { syn::Visibility::Public(syn::VisPublic { pub_token: Default::default(), }) } fn flag_slices_immutable(ty: &mut IdlType) { match ty { IdlType::Int8Array { immutable } | IdlType::Uint8Array { immutable } | IdlType::Uint8ClampedArray { immutable } | IdlType::Int16Array { immutable } | IdlType::Uint16Array { immutable } | IdlType::Int32Array { immutable } | IdlType::Uint32Array { immutable } | IdlType::Float32Array { immutable } | IdlType::Float64Array { immutable } | IdlType::ArrayBufferView { immutable } | IdlType::BufferSource { immutable } => *immutable = true, IdlType::Nullable(item) => flag_slices_immutable(item), IdlType::FrozenArray(item) => flag_slices_immutable(item), IdlType::Sequence(item) => flag_slices_immutable(item), IdlType::Promise(item) => flag_slices_immutable(item), IdlType::Record(item1, item2) => { flag_slices_immutable(item1); flag_slices_immutable(item2); } IdlType::Union(list) => { for item in list { flag_slices_immutable(item); } } // catch-all for everything else like Object _ => {} } }