Support named "special" operations in WebIDL

This commit adds support for two different features of the "special" operations
in WebIDL. First, it implements the desugaring [described by WebIDL][1] where
this:

    interface Dictionary {
      getter double getProperty(DOMString propertyName);
      setter void setProperty(DOMString propertyName, double propertyValue);
    };

becomes ...

    interface Dictionary {
      double getProperty(DOMString propertyName);
      void setProperty(DOMString propertyName, double propertyValue);

      getter double (DOMString propertyName);
      setter void (DOMString propertyName, double propertyValue);
    };

where specifically a named `getter` generates both a getter and a named
function.

Second it implements the distinction between two different types of getters in
WebIDL, described as:

> Getters and setters come in two varieties: ones that take a DOMString as a
> property name, known as named property getters and named property setters, and
> ones that take an unsigned long as a property index, known as indexed property
> getters and indexed property setters.

The name `get` is given to DOMString arguments, and the name `get_idx` is given
to index property getters.

[1]: https://heycam.github.io/webidl/#idl-special-operations
This commit is contained in:
Alex Crichton
2018-08-07 15:50:27 -07:00
parent c0c27775f3
commit 03eb1b1d01
4 changed files with 85 additions and 61 deletions

View File

@ -65,6 +65,7 @@ fn try_main() -> Result<(), failure::Error> {
.context("writing bindings to output file")?; .context("writing bindings to output file")?;
// run rustfmt on the generated file - really handy for debugging // run rustfmt on the generated file - really handy for debugging
println!("cargo:rerun-if-env-changed=WEBIDL_RUSTFMT_BINDINGS");
if env::var("WEBIDL_RUSTFMT_BINDINGS").is_ok() { if env::var("WEBIDL_RUSTFMT_BINDINGS").is_ok() {
let status = Command::new("rustfmt") let status = Command::new("rustfmt")
.arg(&out_file_path) .arg(&out_file_path)

View File

@ -11,7 +11,7 @@ use std::collections::{BTreeMap, BTreeSet};
use weedle::argument::Argument; use weedle::argument::Argument;
use weedle::attribute::ExtendedAttribute; use weedle::attribute::ExtendedAttribute;
use weedle::interface::StringifierOrStatic; use weedle::interface::{StringifierOrStatic, Special};
use weedle::mixin::MixinMember; use weedle::mixin::MixinMember;
use weedle::namespace::NamespaceMember; use weedle::namespace::NamespaceMember;
use weedle; use weedle;
@ -61,7 +61,7 @@ pub(crate) struct NamespaceData<'src> {
pub(crate) operations: BTreeMap<OperationId<'src>, OperationData<'src>>, pub(crate) operations: BTreeMap<OperationId<'src>, OperationData<'src>>,
} }
#[derive(PartialEq, Eq, PartialOrd, Ord, Debug)] #[derive(PartialEq, Eq, PartialOrd, Ord, Debug, Clone, Copy)]
pub(crate) enum OperationId<'src> { pub(crate) enum OperationId<'src> {
Constructor, Constructor,
Operation(Option<&'src str>), Operation(Option<&'src str>),
@ -171,7 +171,7 @@ fn first_pass_operation<'src>(
record: &mut FirstPassRecord<'src>, record: &mut FirstPassRecord<'src>,
first_pass_operation_type: FirstPassOperationType, first_pass_operation_type: FirstPassOperationType,
self_name: &'src str, self_name: &'src str,
id: OperationId<'src>, ids: &[OperationId<'src>],
arguments: &[Argument<'src>], arguments: &[Argument<'src>],
) -> Result<()> { ) -> Result<()> {
let mut names = Vec::with_capacity(arguments.len()); let mut names = Vec::with_capacity(arguments.len());
@ -181,7 +181,7 @@ fn first_pass_operation<'src>(
Argument::Variadic(variadic) => names.push(variadic.identifier.0), Argument::Variadic(variadic) => names.push(variadic.identifier.0),
} }
} }
match first_pass_operation_type{ let operations = match first_pass_operation_type{
FirstPassOperationType::Interface => { FirstPassOperationType::Interface => {
&mut record &mut record
.interfaces .interfaces
@ -203,14 +203,17 @@ fn first_pass_operation<'src>(
.expect(&format!("not found {} namesace", self_name)) .expect(&format!("not found {} namesace", self_name))
.operations .operations
}, },
};
for id in ids {
operations
.entry(*id)
.and_modify(|operation_data| operation_data.overloaded = true)
.or_default()
.argument_names_same
.entry(names.clone())
.and_modify(|same_argument_names| *same_argument_names = true)
.or_insert(false);
} }
.entry(id)
.and_modify(|operation_data| operation_data.overloaded = true)
.or_default()
.argument_names_same
.entry(names)
.and_modify(|same_argument_names| *same_argument_names = true)
.or_insert(false);
Ok(()) Ok(())
} }
@ -278,7 +281,7 @@ impl<'src> FirstPass<'src, &'src str> for ExtendedAttribute<'src> {
record, record,
FirstPassOperationType::Interface, FirstPassOperationType::Interface,
self_name, self_name,
OperationId::Constructor, &[OperationId::Constructor],
&list.args.body.list, &list.args.body.list,
) )
} }
@ -287,7 +290,7 @@ impl<'src> FirstPass<'src, &'src str> for ExtendedAttribute<'src> {
record, record,
FirstPassOperationType::Interface, FirstPassOperationType::Interface,
self_name, self_name,
OperationId::Constructor, &[OperationId::Constructor],
&[], &[],
) )
} }
@ -298,7 +301,7 @@ impl<'src> FirstPass<'src, &'src str> for ExtendedAttribute<'src> {
record, record,
FirstPassOperationType::Interface, FirstPassOperationType::Interface,
self_name, self_name,
OperationId::Constructor, &[OperationId::Constructor],
&list.args.body.list, &list.args.body.list,
) )
} }
@ -332,7 +335,7 @@ impl<'src> FirstPass<'src, &'src str> for weedle::interface::OperationInterfaceM
return Ok(()); return Ok(());
} }
if !self.specials.is_empty() && self.specials.len() != 1 { if self.specials.len() > 1 {
warn!("Unsupported webidl operation {:?}", self); warn!("Unsupported webidl operation {:?}", self);
return Ok(()) return Ok(())
} }
@ -340,20 +343,20 @@ impl<'src> FirstPass<'src, &'src str> for weedle::interface::OperationInterfaceM
warn!("Unsupported webidl operation {:?}", self); warn!("Unsupported webidl operation {:?}", self);
return Ok(()) return Ok(())
} }
let mut ids = vec![OperationId::Operation(self.identifier.map(|s| s.0))];
for special in self.specials.iter() {
ids.push(match special {
Special::Getter(_) => OperationId::IndexingGetter,
Special::Setter(_) => OperationId::IndexingSetter,
Special::Deleter(_) => OperationId::IndexingDeleter,
Special::LegacyCaller(_) => continue,
});
}
first_pass_operation( first_pass_operation(
record, record,
FirstPassOperationType::Interface, FirstPassOperationType::Interface,
self_name, self_name,
match self.identifier.map(|s| s.0) { &ids,
None => match self.specials.get(0) {
None => OperationId::Operation(None),
Some(weedle::interface::Special::Getter(_)) => OperationId::IndexingGetter,
Some(weedle::interface::Special::Setter(_)) => OperationId::IndexingSetter,
Some(weedle::interface::Special::Deleter(_)) => OperationId::IndexingDeleter,
Some(weedle::interface::Special::LegacyCaller(_)) => return Ok(()),
},
Some(ref name) => OperationId::Operation(Some(name.clone())),
},
&self.args.body.list, &self.args.body.list,
) )
} }
@ -434,7 +437,7 @@ impl<'src> FirstPass<'src, &'src str> for weedle::mixin::OperationMixinMember<'s
record, record,
FirstPassOperationType::Mixin, FirstPassOperationType::Mixin,
self_name, self_name,
OperationId::Operation(self.identifier.map(|s| s.0.clone())), &[OperationId::Operation(self.identifier.map(|s| s.0.clone()))],
&self.args.body.list, &self.args.body.list,
) )
} }
@ -524,7 +527,7 @@ impl<'src> FirstPass<'src, &'src str> for weedle::namespace::OperationNamespaceM
record, record,
FirstPassOperationType::Namespace, FirstPassOperationType::Namespace,
self_name, self_name,
OperationId::Operation(self.identifier.map(|s| s.0.clone())), &[OperationId::Operation(self.identifier.map(|s| s.0.clone()))],
&self.args.body.list, &self.args.body.list,
) )
} }

View File

@ -44,7 +44,7 @@ use proc_macro2::{Ident, Span};
use weedle::argument::Argument; use weedle::argument::Argument;
use weedle::attribute::{ExtendedAttribute, ExtendedAttributeList}; use weedle::attribute::{ExtendedAttribute, ExtendedAttributeList};
use first_pass::{FirstPass, FirstPassRecord}; use first_pass::{FirstPass, FirstPassRecord, OperationId};
use util::{public, webidl_const_v_to_backend_const_v, TypePosition, camel_case_ident, mdn_doc}; use util::{public, webidl_const_v_to_backend_const_v, TypePosition, camel_case_ident, mdn_doc};
use idl_type::{IdlType, ToIdlType}; use idl_type::{IdlType, ToIdlType};
@ -646,6 +646,7 @@ fn member_operation<'src>(
identifier: &Option<weedle::common::Identifier<'src>>, identifier: &Option<weedle::common::Identifier<'src>>,
) -> Result<()> { ) -> Result<()> {
use weedle::interface::StringifierOrStatic::*; use weedle::interface::StringifierOrStatic::*;
use weedle::interface::Special;
if util::is_chrome_only(attrs) { if util::is_chrome_only(attrs) {
return Ok(()); return Ok(());
@ -660,33 +661,52 @@ fn member_operation<'src>(
None => false, None => false,
}; };
for import_function in first_pass.create_basic_method( let mut operation_ids = vec![
args, OperationId::Operation(identifier.map(|s| s.0)),
match identifier.map(|s| s.0) { ];
None if specials.is_empty() => ::first_pass::OperationId::Operation(None), if specials.len() > 1 {
None if specials.len() == 1 => match specials[0] { warn!(
weedle::interface::Special::Getter(_) => ::first_pass::OperationId::IndexingGetter, "Unsupported specials ({:?}) on type {:?}",
weedle::interface::Special::Setter(_) => ::first_pass::OperationId::IndexingSetter, specials,
weedle::interface::Special::Deleter(_) => ::first_pass::OperationId::IndexingDeleter, (self_name, identifier),
weedle::interface::Special::LegacyCaller(_) => return Ok(()), );
}, return Ok(())
Some(ref name) if specials.is_empty() => ::first_pass::OperationId::Operation(Some(name.clone())), } else if specials.len() == 1 {
_ => { let id = match specials[0] {
warn!("Unsupported specials on type {:?}", (self_name, identifier)); Special::Getter(weedle::term::Getter) => OperationId::IndexingGetter,
return Ok(()) Special::Setter(weedle::term::Setter) => OperationId::IndexingSetter,
} Special::Deleter(weedle::term::Deleter) => OperationId::IndexingDeleter,
}, Special::LegacyCaller(weedle::term::LegacyCaller) => return Ok(()),
return_type, };
self_name, operation_ids.push(id);
is_static, }
specials.len() == 1 || first_pass
.interfaces for id in operation_ids {
.get(self_name) let methods = first_pass
.map(|interface_data| interface_data.global) .create_basic_method(
.unwrap_or(false), args,
util::throws(attrs), id,
) { return_type,
program.imports.push(wrap_import_function(import_function)); self_name,
is_static,
match id {
OperationId::IndexingGetter |
OperationId::IndexingSetter |
OperationId::IndexingDeleter => true,
_ => {
first_pass
.interfaces
.get(self_name)
.map(|interface_data| interface_data.global)
.unwrap_or(false)
}
},
util::throws(attrs),
);
for method in methods {
program.imports.push(wrap_import_function(method));
}
} }
Ok(()) Ok(())
} }

View File

@ -370,7 +370,7 @@ impl<'src> FirstPassRecord<'src> {
/// Create a wasm-bindgen method, if possible. /// Create a wasm-bindgen method, if possible.
pub fn create_basic_method( pub fn create_basic_method(
&self, &self,
arguments: &[weedle::argument::Argument], arguments: &[Argument],
operation_id: first_pass::OperationId, operation_id: first_pass::OperationId,
return_type: &weedle::types::ReturnType, return_type: &weedle::types::ReturnType,
self_name: &str, self_name: &str,
@ -392,11 +392,11 @@ impl<'src> FirstPassRecord<'src> {
warn!("Operations without a name are unsupported"); warn!("Operations without a name are unsupported");
return Vec::new(); return Vec::new();
} }
Some(name) => name.to_string(), Some(name) => name,
}, },
first_pass::OperationId::IndexingGetter => "get".to_string(), first_pass::OperationId::IndexingGetter => "get",
first_pass::OperationId::IndexingSetter => "set".to_string(), first_pass::OperationId::IndexingSetter => "set",
first_pass::OperationId::IndexingDeleter => "delete".to_string(), first_pass::OperationId::IndexingDeleter => "delete",
}; };
let kind = backend::ast::ImportFunctionKind::Method { let kind = backend::ast::ImportFunctionKind::Method {
@ -455,7 +455,7 @@ impl<'src> FirstPassRecord<'src> {
/// whether there overloads with same argument names for given argument types /// whether there overloads with same argument names for given argument types
pub fn get_operation_overloading( pub fn get_operation_overloading(
&self, &self,
arguments: &[weedle::argument::Argument], arguments: &[Argument],
operation_id: &first_pass::OperationId, operation_id: &first_pass::OperationId,
self_name: &str, self_name: &str,
namespace: bool, namespace: bool,