diff --git a/crates/backend/src/ast.rs b/crates/backend/src/ast.rs index 2c3c9661..cfdc2367 100644 --- a/crates/backend/src/ast.rs +++ b/crates/backend/src/ast.rs @@ -42,6 +42,7 @@ pub enum ImportKind { Static(ImportStatic), Type(ImportType), Enum(ImportEnum), + Const(Const), } #[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq, Eq))] @@ -174,6 +175,24 @@ pub struct TypeAlias { pub src: syn::Type, } +#[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq, Eq))] +pub struct Const { + pub vis: syn::Visibility, + pub name: Ident, + pub interface_name: Ident, + pub ty: syn::Type, + pub value: ConstValue, +} + +#[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq, Eq))] +/// same as webidl::ast::ConstValue +pub enum ConstValue { + BooleanLiteral(bool), + FloatLiteral(f64), + IntegerLiteral(i64), + Null, +} + impl Program { pub(crate) fn shared(&self) -> shared::Program { shared::Program { @@ -293,6 +312,7 @@ impl ImportKind { ImportKind::Static(_) => false, ImportKind::Type(_) => false, ImportKind::Enum(_) => false, + ImportKind::Const(_) => false, } } @@ -302,6 +322,7 @@ impl ImportKind { ImportKind::Static(ref f) => shared::ImportKind::Static(f.shared()), ImportKind::Type(ref f) => shared::ImportKind::Type(f.shared()), ImportKind::Enum(ref f) => shared::ImportKind::Enum(f.shared()), + ImportKind::Const(ref f) => shared::ImportKind::Const(f.shared()), } } } @@ -404,3 +425,9 @@ impl StructField { } } } + +impl Const { + fn shared(&self) -> shared::Const { + shared::Const {} + } +} diff --git a/crates/backend/src/codegen.rs b/crates/backend/src/codegen.rs index 1e3e69af..a83d1923 100644 --- a/crates/backend/src/codegen.rs +++ b/crates/backend/src/codegen.rs @@ -501,6 +501,7 @@ impl ToTokens for ast::ImportKind { ast::ImportKind::Static(ref s) => s.to_tokens(tokens), ast::ImportKind::Type(ref t) => t.to_tokens(tokens), ast::ImportKind::Enum(ref e) => e.to_tokens(tokens), + ast::ImportKind::Const(ref c) => c.to_tokens(tokens), } } } @@ -842,6 +843,7 @@ impl<'a> ToTokens for DescribeImport<'a> { ast::ImportKind::Static(_) => return, ast::ImportKind::Type(_) => return, ast::ImportKind::Enum(_) => return, + ast::ImportKind::Const(_) => return, }; let describe_name = format!("__wbindgen_describe_{}", f.shim); let describe_name = Ident::new(&describe_name, Span::call_site()); @@ -958,3 +960,41 @@ impl ToTokens for ast::TypeAlias { }).to_tokens(into); } } + +impl ToTokens for ast::Const { + fn to_tokens(&self, tokens: &mut TokenStream) { + use ast::ConstValue::*; + + let vis = &self.vis; + let name = &self.name; + let interface_name = &self.interface_name; + let ty = &self.ty; + + let value: TokenStream = match self.value { + BooleanLiteral(false) => quote!(false), + BooleanLiteral(true) => quote!(true), + // the actual type is unknown because of typedefs + // so we cannot use std::fxx::INFINITY + // but we can use type inference + FloatLiteral(f) if f.is_infinite() && f.is_sign_positive() => quote!(1.0 / 0.0), + FloatLiteral(f) if f.is_infinite() && f.is_sign_negative() => quote!(-1.0 / 0.0), + FloatLiteral(f) if f.is_nan() => quote!(0.0 / 0.0), + // again no suffix + // panics on +-inf, nan + FloatLiteral(f) => { + let f = Literal::f64_unsuffixed(f); + quote!(#f) + }, + IntegerLiteral(i) => { + let i = Literal::i64_unsuffixed(i); + quote!(#i) + }, + Null => unimplemented!(), + }; + (quote! { + impl #interface_name { + #vis const #name: #ty = #value; + } + }).to_tokens(tokens); + } +} diff --git a/crates/backend/src/defined.rs b/crates/backend/src/defined.rs index d775cb02..84eabe50 100644 --- a/crates/backend/src/defined.rs +++ b/crates/backend/src/defined.rs @@ -106,6 +106,7 @@ impl ImportedTypes for ast::ImportKind { ast::ImportKind::Function(fun) => fun.imported_types(f), ast::ImportKind::Type(ty) => ty.imported_types(f), ast::ImportKind::Enum(enm) => enm.imported_types(f), + ast::ImportKind::Const(c) => c.imported_types(f), } } } @@ -229,6 +230,15 @@ impl ImportedTypes for ast::TypeAlias { } } +impl ImportedTypes for ast::Const { + fn imported_types(&self, f: &mut F) + where + F: FnMut(&Ident, ImportedTypeKind), + { + self.ty.imported_types(f); + } +} + /// Remove any methods, statics, &c, that reference types that are *not* /// defined. pub trait RemoveUndefinedImports { diff --git a/crates/cli-support/src/js/mod.rs b/crates/cli-support/src/js/mod.rs index 46c9adcd..9a9a7271 100644 --- a/crates/cli-support/src/js/mod.rs +++ b/crates/cli-support/src/js/mod.rs @@ -1758,6 +1758,7 @@ impl<'a, 'b> SubContext<'a, 'b> { } shared::ImportKind::Type(_) => {} shared::ImportKind::Enum(_) => {} + shared::ImportKind::Const(_) => {} } Ok(()) } diff --git a/crates/shared/src/lib.rs b/crates/shared/src/lib.rs index 8df1e534..8985733d 100644 --- a/crates/shared/src/lib.rs +++ b/crates/shared/src/lib.rs @@ -34,6 +34,7 @@ pub enum ImportKind { Static(ImportStatic), Type(ImportType), Enum(ImportEnum), + Const(Const) } #[derive(Deserialize, Serialize)] @@ -124,6 +125,9 @@ pub struct StructField { pub comments: Vec, } +#[derive(Deserialize, Serialize)] +pub struct Const {} + pub fn new_function(struct_name: &str) -> String { let mut name = format!("__wbg_"); name.extend(struct_name.chars().flat_map(|s| s.to_lowercase())); diff --git a/crates/webidl/src/lib.rs b/crates/webidl/src/lib.rs index e54e08bb..13b51e9f 100644 --- a/crates/webidl/src/lib.rs +++ b/crates/webidl/src/lib.rs @@ -29,12 +29,12 @@ use std::path::Path; use backend::defined::{ImportedTypeDefinitions, RemoveUndefinedImports}; use backend::util::{ident_ty, rust_ident, wrap_import_function}; use failure::ResultExt; -use heck::CamelCase; +use heck::{CamelCase, ShoutySnakeCase}; use quote::ToTokens; use util::{ - create_basic_method, create_function, create_getter, create_setter, webidl_ty_to_syn_ty, - TypePosition, + create_basic_method, create_function, create_getter, create_setter, webidl_const_ty_to_syn_ty, + webidl_const_v_to_backend_const_v, webidl_ty_to_syn_ty, TypePosition, }; /// Either `Ok(t)` or `Err(failure::Error)`. @@ -297,9 +297,9 @@ impl<'a> WebidlParse<&'a str> for webidl::ast::InterfaceMember { attr.webidl_parse(program, self_name) } webidl::ast::InterfaceMember::Operation(ref op) => op.webidl_parse(program, self_name), + webidl::ast::InterfaceMember::Const(ref c) => c.webidl_parse(program, self_name), // TODO - webidl::ast::InterfaceMember::Const(_) - | webidl::ast::InterfaceMember::Iterable(_) + webidl::ast::InterfaceMember::Iterable(_) | webidl::ast::InterfaceMember::Maplike(_) | webidl::ast::InterfaceMember::Setlike(_) => { warn!("Unsupported WebIDL interface member: {:?}", self); @@ -474,3 +474,28 @@ impl<'a> WebidlParse<()> for webidl::ast::Enum { Ok(()) } } + +impl<'a> WebidlParse<&'a str> for webidl::ast::Const { + fn webidl_parse( + &self, + program: &mut backend::ast::Program, + interface_name: &'a str, + ) -> Result<()> { + let syn_ty = webidl_const_ty_to_syn_ty(&self.type_); + program.imports.push(backend::ast::Import { + module: None, + version: None, + js_namespace: None, + kind: backend::ast::ImportKind::Const(backend::ast::Const { + vis: syn::Visibility::Public(syn::VisPublic { + pub_token: Default::default(), + }), + name: rust_ident(self.name.to_shouty_snake_case().as_str()), + interface_name: rust_ident(interface_name), + ty: syn_ty, + value: webidl_const_v_to_backend_const_v(&self.value), + }), + }); + Ok(()) + } +} diff --git a/crates/webidl/src/util.rs b/crates/webidl/src/util.rs index b8edae82..042fba08 100644 --- a/crates/webidl/src/util.rs +++ b/crates/webidl/src/util.rs @@ -90,6 +90,35 @@ pub fn webidl_ty_to_syn_ty(ty: &webidl::ast::Type, pos: TypePosition) -> Option< }) } +pub fn webidl_const_ty_to_syn_ty(ty: &webidl::ast::ConstType) -> syn::Type { + use webidl::ast::ConstType::*; + + // similar to webidl_ty_to_syn_ty + match ty { + Boolean => ident_ty(raw_ident("bool")), + Byte => ident_ty(raw_ident("i8")), + Octet => ident_ty(raw_ident("u8")), + RestrictedDouble | UnrestrictedDouble => ident_ty(raw_ident("f64")), + RestrictedFloat | UnrestrictedFloat => ident_ty(raw_ident("f32")), + SignedLong => ident_ty(raw_ident("i32")), + SignedLongLong => ident_ty(raw_ident("i64")), + SignedShort => ident_ty(raw_ident("i16")), + UnsignedLong => ident_ty(raw_ident("u32")), + UnsignedLongLong => ident_ty(raw_ident("u64")), + UnsignedShort => ident_ty(raw_ident("u16")), + Identifier(ref id) => ident_ty(rust_ident(id)), + } +} + +pub fn webidl_const_v_to_backend_const_v(v: &webidl::ast::ConstValue) -> backend::ast::ConstValue { + match *v { + webidl::ast::ConstValue::BooleanLiteral(b) => backend::ast::ConstValue::BooleanLiteral(b), + webidl::ast::ConstValue::FloatLiteral(f) => backend::ast::ConstValue::FloatLiteral(f), + webidl::ast::ConstValue::IntegerLiteral(i) => backend::ast::ConstValue::IntegerLiteral(i), + webidl::ast::ConstValue::Null => backend::ast::ConstValue::Null, + } +} + fn simple_fn_arg(ident: Ident, ty: syn::Type) -> syn::ArgCaptured { syn::ArgCaptured { pat: syn::Pat::Ident(syn::PatIdent { diff --git a/crates/webidl/tests/all/consts.rs b/crates/webidl/tests/all/consts.rs new file mode 100644 index 00000000..dd2265e0 --- /dev/null +++ b/crates/webidl/tests/all/consts.rs @@ -0,0 +1,190 @@ +use super::project; + +#[test] +fn bool() { + project() + .file( + "foo.webidl", + r#" + interface Foo { + const boolean not_true = false; + const boolean not_false = true; + }; + "#, + ) + // a corresponding const in the js implementation is not required + // value is taken directly from idl + .file( + "foo.js", + r#" + export class Foo { + } + "#, + ) + .file( + "src/lib.rs", + r#" + #![feature(proc_macro, wasm_custom_section, wasm_import_module)] + extern crate wasm_bindgen; + use wasm_bindgen::prelude::*; + + pub mod foo; + use foo::Foo; + + #[wasm_bindgen] + pub fn test() { + let falsish: bool = Foo::NOT_TRUE; + assert!(!falsish); + let trueish: bool = Foo::NOT_FALSE; + assert!(trueish); + } + "#, + ) + .test(); +} + +#[test] +fn ints() { + project() + .file( + "foo.webidl", + r#" + interface Byte { + const byte imin = -128; + const byte imax = 127; + const octet umin = 0; + const octet umax = 255; + }; + interface Short { + const short imin = -32768; + const short imax = 32767; + const unsigned short umin = 0; + const unsigned short umax = 65535; + }; + interface Long { + const long imin = -2147483648; + const long imax = 2147483647; + const unsigned long umin = 0; + const unsigned long umax = 4294967295; + }; + interface LongLong { + const long long imin = -9223372036854775808; + const long long imax = 9223372036854775807; + const unsigned long long umin = 0; + // bug in webidl + // https://github.com/sgodwincs/webidl-rs/issues/15 + //const unsigned long long umax = 18446744073709551615; + }; + "#, + ) + // a corresponding const in the js implementation is not required + // value is taken directly from idl + .file( + "foo.js", + r#" + export class Byte { + } + export class Short { + } + export class Long { + } + export class LongLong { + } + "#, + ) + .file( + "src/lib.rs", + r#" + #![feature(proc_macro, wasm_custom_section, wasm_import_module)] + extern crate wasm_bindgen; + use wasm_bindgen::prelude::*; + + pub mod foo; + + #[wasm_bindgen] + pub fn test() { + assert_eq!(foo::Byte::IMIN, i8::min_value()); + assert_eq!(foo::Byte::IMAX, i8::max_value()); + assert_eq!(foo::Byte::UMIN, u8::min_value()); + assert_eq!(foo::Byte::UMAX, u8::max_value()); + + assert_eq!(foo::Short::IMIN, i16::min_value()); + assert_eq!(foo::Short::IMAX, i16::max_value()); + assert_eq!(foo::Short::UMIN, u16::min_value()); + assert_eq!(foo::Short::UMAX, u16::max_value()); + + assert_eq!(foo::Long::IMIN, i32::min_value()); + assert_eq!(foo::Long::IMAX, i32::max_value()); + assert_eq!(foo::Long::UMIN, u32::min_value()); + assert_eq!(foo::Long::UMAX, u32::max_value()); + + assert_eq!(foo::LongLong::IMIN, i64::min_value()); + assert_eq!(foo::LongLong::IMAX, i64::max_value()); + assert_eq!(foo::LongLong::UMIN, u64::min_value()); + //assert_eq!(foo::LongLong::UMAX, u64::max_value()); + } + "#, + ) + .test(); +} + +#[test] +fn floats() { + project() + .file( + "foo.webidl", + r#" + interface floats { + const float f = 0.0; + const unrestricted float neg_inf = -Infinity; + const unrestricted float inf = Infinity; + const unrestricted float nan = NaN; + }; + interface doubles { + const double d = 0.0; + const unrestricted double neg_inf = -Infinity; + const unrestricted double inf = Infinity; + const unrestricted double nan = NaN; + }; + "#, + ) + // a corresponding const in the js implementation is not required + // value is taken directly from idl + .file( + "foo.js", + r#" + export class floats { + } + export class doubles { + } + "#, + ) + .file( + "src/lib.rs", + r#" + #![feature(proc_macro, wasm_custom_section, wasm_import_module)] + extern crate wasm_bindgen; + use wasm_bindgen::prelude::*; + + pub mod foo; + + #[wasm_bindgen] + pub fn test() { + assert_eq!(foo::floats::F, 0.0); + assert!(foo::floats::NEG_INF.is_infinite()); + assert!(foo::floats::NEG_INF.is_sign_negative()); + assert!(foo::floats::INF.is_infinite()); + assert!(foo::floats::INF.is_sign_positive()); + assert!(foo::floats::NAN.is_nan()); + + assert_eq!(foo::doubles::D, 0.0); + assert!(foo::doubles::NEG_INF.is_infinite()); + assert!(foo::doubles::NEG_INF.is_sign_negative()); + assert!(foo::doubles::INF.is_infinite()); + assert!(foo::doubles::INF.is_sign_positive()); + assert!(foo::doubles::NAN.is_nan()); + } + "#, + ) + .test(); +} diff --git a/crates/webidl/tests/all/main.rs b/crates/webidl/tests/all/main.rs index 13a6f789..3815c632 100644 --- a/crates/webidl/tests/all/main.rs +++ b/crates/webidl/tests/all/main.rs @@ -1,6 +1,7 @@ extern crate wasm_bindgen_test_project_builder as project_builder; use project_builder::project; -mod simple; +mod consts; mod enums; +mod simple; mod throws;