diff --git a/crates/backend/src/ast.rs b/crates/backend/src/ast.rs index 265f4ac6..c1cc8a1e 100644 --- a/crates/backend/src/ast.rs +++ b/crates/backend/src/ast.rs @@ -106,8 +106,11 @@ pub struct Operation { #[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq, Eq))] pub enum OperationKind { Regular, - Setter(Option), Getter(Option), + Setter(Option), + SpecialGetter, + SpecialSetter, + SpecialDeleter, } #[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq, Eq))] @@ -395,6 +398,9 @@ impl ImportFunction { s.unwrap_or_else(|| self.infer_setter_property()), ) } + OperationKind::SpecialGetter => shared::OperationKind::SpecialGetter, + OperationKind::SpecialSetter => shared::OperationKind::SpecialSetter, + OperationKind::SpecialDeleter => shared::OperationKind::SpecialDeleter, }; shared::MethodKind::Operation(shared::Operation { is_static, kind }) } diff --git a/crates/cli-support/src/js/mod.rs b/crates/cli-support/src/js/mod.rs index 9a63fc5d..2f5b6a36 100644 --- a/crates/cli-support/src/js/mod.rs +++ b/crates/cli-support/src/js/mod.rs @@ -1869,18 +1869,6 @@ impl<'a, 'b> SubContext<'a, 'b> { let location = if *is_static { &class } else { "this" }; match kind { - shared::OperationKind::Getter(g) => format!( - "function() {{ - return {}.{}; - }}", - location, g - ), - shared::OperationKind::Setter(s) => format!( - "function(y) {{ - {}.{} = y; - }}", - location, s - ), shared::OperationKind::Regular => { let nargs = descriptor.unwrap_function().arguments.len(); let mut s = format!("function("); @@ -1902,11 +1890,44 @@ impl<'a, 'b> SubContext<'a, 'b> { s.push_str(");\n}"); s } + shared::OperationKind::Getter(g) => format!( + "function() {{ + return {}.{}; + }}", + location, g + ), + shared::OperationKind::Setter(s) => format!( + "function(y) {{ + {}.{} = y; + }}", + location, s + ), + shared::OperationKind::SpecialGetter => format!( + "function(y) {{ + return {}[y]; + }}", + location + ), + shared::OperationKind::SpecialSetter => format!( + "function(y, z) {{ + {}[y] = z; + }}", + location + ), + shared::OperationKind::SpecialDeleter => format!( + "function(y) {{ + delete {}[y]; + }}", + location + ), } } else { let location = if *is_static { "" } else { ".prototype" }; match kind { + shared::OperationKind::Regular => { + format!("{}{}.{}", class, location, import.function.name) + } shared::OperationKind::Getter(g) => { self.cx.expose_get_inherited_descriptor(); format!( @@ -1921,9 +1942,9 @@ impl<'a, 'b> SubContext<'a, 'b> { class, location, s, ) } - shared::OperationKind::Regular => { - format!("{}{}.{}", class, location, import.function.name) - } + shared::OperationKind::SpecialGetter => panic!("getter should be structural"), + shared::OperationKind::SpecialSetter => panic!("setter should be structural"), + shared::OperationKind::SpecialDeleter => panic!("deleter should be structural"), } }; diff --git a/crates/macro-support/src/parser.rs b/crates/macro-support/src/parser.rs index 2e935843..6fa4f974 100644 --- a/crates/macro-support/src/parser.rs +++ b/crates/macro-support/src/parser.rs @@ -132,6 +132,30 @@ impl BindgenAttrs { .next() } + /// Whether the special getter attributes is present + fn special_getter(&self) -> bool { + self.attrs.iter().any(|a| match *a { + BindgenAttr::SpecialGetter => true, + _ => false, + }) + } + + /// Whether the special setter attributes is present + fn special_setter(&self) -> bool { + self.attrs.iter().any(|a| match *a { + BindgenAttr::SpecialSetter => true, + _ => false, + }) + } + + /// Whether the special deleter attributes is present + fn special_deleter(&self) -> bool { + self.attrs.iter().any(|a| match *a { + BindgenAttr::SpecialDeleter => true, + _ => false, + }) + } + /// Whether the structural attributes is present fn structural(&self) -> bool { self.attrs.iter().any(|a| match *a { @@ -198,6 +222,9 @@ pub enum BindgenAttr { Version(String), Getter(Option), Setter(Option), + SpecialGetter, + SpecialSetter, + SpecialDeleter, Structural, Readonly, JsName(Ident), @@ -239,6 +266,12 @@ impl syn::synom::Synom for BindgenAttr { (val) )=> { BindgenAttr::Setter } | + call!(term, "special_getter") => { |_| BindgenAttr::SpecialGetter } + | + call!(term, "special_setter") => { |_| BindgenAttr::SpecialSetter } + | + call!(term, "special_deleter") => { |_| BindgenAttr::SpecialDeleter } + | call!(term, "structural") => { |_| BindgenAttr::Structural } | call!(term, "readonly") => { |_| BindgenAttr::Readonly } @@ -395,6 +428,15 @@ impl<'a> ConvertToAst<(BindgenAttrs, &'a Option)> for syn::ForeignItemFn if let Some(s) = opts.setter() { operation_kind = ast::OperationKind::Setter(s); } + if opts.special_getter() { + operation_kind = ast::OperationKind::SpecialGetter; + } + if opts.special_setter() { + operation_kind = ast::OperationKind::SpecialSetter; + } + if opts.special_deleter() { + operation_kind = ast::OperationKind::SpecialDeleter; + } let kind = if opts.method() { let class = wasm diff --git a/crates/shared/src/lib.rs b/crates/shared/src/lib.rs index c7b7a69f..dc027abe 100644 --- a/crates/shared/src/lib.rs +++ b/crates/shared/src/lib.rs @@ -70,6 +70,9 @@ pub enum OperationKind { Regular, Getter(String), Setter(String), + SpecialGetter, + SpecialSetter, + SpecialDeleter, } #[derive(Deserialize, Serialize)] diff --git a/crates/webidl/src/first_pass.rs b/crates/webidl/src/first_pass.rs index 6242ed09..e594f95d 100644 --- a/crates/webidl/src/first_pass.rs +++ b/crates/webidl/src/first_pass.rs @@ -38,7 +38,10 @@ pub(crate) struct InterfaceData { #[derive(PartialEq, Eq, PartialOrd, Ord)] pub(crate) enum OperationId { Constructor, - Operation(Option) + Operation(Option), + SpecialGetter, + SpecialSetter, + SpecialDeleter, } #[derive(Default)] @@ -302,8 +305,9 @@ impl<'b> FirstPass<&'b str> for webidl::ast::Operation { match self { webidl::ast::Operation::Regular(op) => op.first_pass(record, self_name), webidl::ast::Operation::Static(op) => op.first_pass(record, self_name), + webidl::ast::Operation::Special(op) => op.first_pass(record, self_name), // TODO - webidl::ast::Operation::Special(_) | webidl::ast::Operation::Stringifier(_) => { + webidl::ast::Operation::Stringifier(_) => { warn!("Unsupported WebIDL operation: {:?}", self); Ok(()) } @@ -333,6 +337,28 @@ impl<'b> FirstPass<&'b str> for webidl::ast::StaticOperation { } } +impl<'b> FirstPass<&'b str> for webidl::ast::SpecialOperation { + fn first_pass<'a>(&'a self, record: &mut FirstPassRecord<'a>, self_name: &'b str) -> Result<()> { + first_pass_operation( + record, + self_name, + match self.name { + None => match self.special_keywords.iter().next() { + Some(webidl::ast::Special::Getter) => OperationId::SpecialGetter, + Some(webidl::ast::Special::Setter) => OperationId::SpecialSetter, + Some(webidl::ast::Special::Deleter) => OperationId::SpecialDeleter, + Some(webidl::ast::Special::LegacyCaller) => return Ok(()), + None => { + panic!("unsupported special operation: {:?} of {}", self, self_name); + } + }, + Some(ref name) => OperationId::Operation(Some(name.clone())), + }, + &self.arguments, + ) + } +} + impl FirstPass<()> for webidl::ast::Mixin { fn first_pass<'a>(&'a self, record: &mut FirstPassRecord<'a>, (): ()) -> Result<()> { use webidl::ast::Mixin::*; diff --git a/crates/webidl/src/lib.rs b/crates/webidl/src/lib.rs index ec41686d..735152e5 100644 --- a/crates/webidl/src/lib.rs +++ b/crates/webidl/src/lib.rs @@ -307,7 +307,7 @@ impl<'a> WebidlParse<&'a webidl::ast::NonPartialInterface> for webidl::ast::Exte let (overloaded, same_argument_names) = first_pass.get_operation_overloading( arguments, - ::first_pass::OperationId::Constructor, + &::first_pass::OperationId::Constructor, &interface.name, ); @@ -475,8 +475,9 @@ impl<'a> WebidlParse<&'a str> for webidl::ast::Operation { match self { webidl::ast::Operation::Regular(op) => op.webidl_parse(program, first_pass, self_name), webidl::ast::Operation::Static(op) => op.webidl_parse(program, first_pass, self_name), + webidl::ast::Operation::Special(op) => op.webidl_parse(program, first_pass, self_name), // TODO - webidl::ast::Operation::Special(_) | webidl::ast::Operation::Stringifier(_) => { + webidl::ast::Operation::Stringifier(_) => { warn!("Unsupported WebIDL operation: {:?}", self); Ok(()) } @@ -627,8 +628,6 @@ impl<'a> WebidlParse<&'a str> for webidl::ast::RegularOperation { return Ok(()); } - let throws = util::throws(&self.extended_attributes); - first_pass .create_basic_method( &self @@ -636,11 +635,16 @@ impl<'a> WebidlParse<&'a str> for webidl::ast::RegularOperation { .iter() .map(|argument| argument.apply_typedefs(first_pass)) .collect::>(), - self.name.as_ref(), + ::first_pass::OperationId::Operation(self.name.clone()), &self.return_type.apply_typedefs(first_pass), self_name, false, - throws, + first_pass + .interfaces + .get(self_name) + .map(|interface_data| interface_data.global) + .unwrap_or(false), + util::throws(&self.extended_attributes), ) .map(wrap_import_function) .map(|import| program.imports.push(import)); @@ -660,7 +664,41 @@ impl<'a> WebidlParse<&'a str> for webidl::ast::StaticOperation { return Ok(()); } - let throws = util::throws(&self.extended_attributes); + first_pass + .create_basic_method( + &self + .arguments + .iter() + .map(|argument| argument.apply_typedefs(first_pass)) + .collect::>(), + ::first_pass::OperationId::Operation(self.name.clone()), + &self.return_type.apply_typedefs(first_pass), + self_name, + true, + first_pass + .interfaces + .get(self_name) + .map(|interface_data| interface_data.global) + .unwrap_or(false), + util::throws(&self.extended_attributes), + ) + .map(wrap_import_function) + .map(|import| program.imports.push(import)); + + Ok(()) + } +} + +impl<'a> WebidlParse<&'a str> for webidl::ast::SpecialOperation { + fn webidl_parse( + &self, + program: &mut backend::ast::Program, + first_pass: &FirstPassRecord<'_>, + self_name: &'a str, + ) -> Result<()> { + if util::is_chrome_only(&self.extended_attributes) { + return Ok(()); + } first_pass .create_basic_method( @@ -669,11 +707,23 @@ impl<'a> WebidlParse<&'a str> for webidl::ast::StaticOperation { .iter() .map(|argument| argument.apply_typedefs(first_pass)) .collect::>(), - self.name.as_ref(), + match self.name { + None => match self.special_keywords.iter().next() { + Some(webidl::ast::Special::Getter) => ::first_pass::OperationId::SpecialGetter, + Some(webidl::ast::Special::Setter) => ::first_pass::OperationId::SpecialSetter, + Some(webidl::ast::Special::Deleter) => ::first_pass::OperationId::SpecialDeleter, + Some(webidl::ast::Special::LegacyCaller) => return Ok(()), + None => { + panic!("unsupported special operation: {:?} of {}", self, self_name); + } + }, + Some(ref name) => ::first_pass::OperationId::Operation(Some(name.clone())), + }, &self.return_type.apply_typedefs(first_pass), self_name, + false, true, - throws, + util::throws(&self.extended_attributes), ) .map(wrap_import_function) .map(|import| program.imports.push(import)); diff --git a/crates/webidl/src/util.rs b/crates/webidl/src/util.rs index 507a32b9..18379cb4 100644 --- a/crates/webidl/src/util.rs +++ b/crates/webidl/src/util.rs @@ -604,24 +604,31 @@ impl<'a> FirstPassRecord<'a> { pub fn create_basic_method( &self, arguments: &[webidl::ast::Argument], - name: Option<&String>, + operation_id: ::first_pass::OperationId, return_type: &webidl::ast::ReturnType, self_name: &str, is_static: bool, + structural: bool, catch: bool, ) -> Option { let (overloaded, same_argument_names) = self.get_operation_overloading( arguments, - ::first_pass::OperationId::Operation(name.cloned()), + &operation_id, self_name, ); - let name = match name { - None => { - warn!("Operations without a name are unsupported"); - return None; - } - Some(ref name) => name, + let name = match &operation_id { + ::first_pass::OperationId::Constructor => panic!("constructors are unsupported"), + ::first_pass::OperationId::Operation(name) => match name { + None => { + warn!("Operations without a name are unsupported"); + return None; + } + Some(ref name) => name.clone(), + }, + ::first_pass::OperationId::SpecialGetter => "get".to_string(), + ::first_pass::OperationId::SpecialSetter => "set".to_string(), + ::first_pass::OperationId::SpecialDeleter => "delete".to_string(), }; let kind = backend::ast::ImportFunctionKind::Method { @@ -629,7 +636,13 @@ impl<'a> FirstPassRecord<'a> { ty: ident_ty(rust_ident(camel_case_ident(&self_name).as_str())), kind: backend::ast::MethodKind::Operation(backend::ast::Operation { is_static, - kind: backend::ast::OperationKind::Regular, + kind: match &operation_id { + ::first_pass::OperationId::Constructor => panic!("constructors are unsupported"), + ::first_pass::OperationId::Operation(_) => backend::ast::OperationKind::Regular, + ::first_pass::OperationId::SpecialGetter => backend::ast::OperationKind::SpecialGetter, + ::first_pass::OperationId::SpecialSetter => backend::ast::OperationKind::SpecialSetter, + ::first_pass::OperationId::SpecialDeleter => backend::ast::OperationKind::SpecialDeleter, + }, }), }; @@ -645,7 +658,19 @@ impl<'a> FirstPassRecord<'a> { } } }; - let doc_comment = Some(format!("The `{}()` method\n\n{}", name, mdn_doc(self_name, Some(name)))); + let doc_comment = match &operation_id { + ::first_pass::OperationId::Constructor => panic!("constructors are unsupported"), + ::first_pass::OperationId::Operation(_) => Some( + format!( + "The `{}()` method\n\n{}", + name, + mdn_doc(self_name, Some(&name)) + ) + ), + ::first_pass::OperationId::SpecialGetter => Some("The getter\n\n".to_string()), + ::first_pass::OperationId::SpecialSetter => Some("The setter\n\n".to_string()), + ::first_pass::OperationId::SpecialDeleter => Some("The deleter\n\n".to_string()), + }; self.create_function( &name, @@ -656,11 +681,7 @@ impl<'a> FirstPassRecord<'a> { .map(|arg| (&*arg.name, &*arg.type_, arg.variadic)), ret, kind, - self - .interfaces - .get(self_name) - .map(|interface_data| interface_data.global) - .unwrap_or(false), + structural, catch, doc_comment, ) @@ -671,7 +692,7 @@ impl<'a> FirstPassRecord<'a> { pub fn get_operation_overloading( &self, arguments: &[webidl::ast::Argument], - id: ::first_pass::OperationId, + id: &::first_pass::OperationId, self_name: &str, ) -> (bool, bool) { self @@ -680,7 +701,7 @@ impl<'a> FirstPassRecord<'a> { .map(|interface_data| { interface_data .operations - .get(&id) + .get(id) .map(|operation_data| ( operation_data.overloaded,