Chinedu Francis Nwafili fa1d51fe44
Comment out WebGl tests
2019-01-26 12:19:28 -05:00

761 lines
28 KiB
Rust

use std::iter::FromIterator;
use std::ptr;
use backend;
use backend::util::{ident_ty, leading_colon_path_ty, raw_ident, rust_ident};
use heck::{CamelCase, ShoutySnakeCase, SnakeCase};
use proc_macro2::{Ident, Span};
use syn;
use weedle;
use weedle::attribute::{ExtendedAttribute, ExtendedAttributeList, IdentifierOrString};
use weedle::literal::{ConstValue, FloatLit, IntegerLit};
use first_pass::{FirstPassRecord, OperationData, OperationId, Signature};
use idl_type::{IdlType, ToIdlType};
use std::collections::HashSet;
/// 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<T>`).
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) -> backend::ast::ConstValue {
use backend::ast;
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::ArgCaptured {
syn::ArgCaptured {
pat: syn::Pat::Ident(syn::PatIdent {
by_ref: None,
mutability: None,
ident,
subpat: None,
}),
colon_token: Default::default(),
ty,
}
}
/// Create `()`.
fn unit_ty() -> syn::Type {
syn::Type::Tuple(syn::TypeTuple {
paren_token: Default::default(),
elems: syn::punctuated::Punctuated::new(),
})
}
/// From `T` create `Result<T, ::wasm_bindgen::JsValue>`.
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<T>`.
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<T>`
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<Item = (&'a str, &'a IdlType<'src>)>,
ret: &IdlType<'src>,
kind: backend::ast::ImportFunctionKind,
structural: bool,
catch: bool,
variadic: bool,
doc_comment: Option<String>,
) -> Option<backend::ast::ImportFunction>
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 &backend::ast::ImportFunctionKind::Method {
ref ty,
kind:
backend::ast::MethodKind::Operation(backend::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 => {
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 => {
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(backend::ast::ImportFunction {
function: backend::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(),
},
rust_name: rust_ident(rust_name),
js_ret: js_ret.clone(),
variadic,
catch,
structural,
shim: {
let ns = match kind {
backend::ast::ImportFunctionKind::Normal => "",
backend::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<ExtendedAttributeList>,
container_attrs: Option<&ExtendedAttributeList>,
) -> Option<backend::ast::ImportFunction> {
let kind = backend::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<ExtendedAttributeList>,
container_attrs: Option<&ExtendedAttributeList>,
) -> Option<backend::ast::ImportFunction> {
let kind = backend::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: backend::ast::OperationKind,
) -> backend::ast::ImportFunctionKind {
let operation = backend::ast::Operation {
is_static,
kind: operation_kind,
};
let ty = ident_ty(rust_ident(camel_case_ident(&self_name).as_str()));
backend::ast::ImportFunctionKind::Method {
class: self_name.to_string(),
ty,
kind: backend::ast::MethodKind::Operation(operation),
}
}
pub fn create_imports(
&self,
container_attrs: Option<&ExtendedAttributeList<'src>>,
kind: backend::ast::ImportFunctionKind,
id: &OperationId<'src>,
data: &OperationData<'src>,
) -> Vec<backend::ast::ImportFunction> {
// 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();
'outer: 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 mut idl_type = arg.ty.to_idl_type(self);
let idl_type = 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<IdlType<'a>>,
}
let mut actual_signatures = Vec::new();
for (signature, idl_args) in signatures.iter() {
let mut 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) => {
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 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::<Vec<_>>()
.iter()
.map(|(name, idl_type)| (&name[..], idl_type.clone())),
&ret_ty,
kind.clone(),
structural,
catch,
false,
None,
),
);
}
}
return ret;
}
}
/// 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<ExtendedAttributeList>) -> 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<ExtendedAttributeList>) -> bool {
has_named_attribute(ext_attrs.as_ref(), "NoInterfaceObject")
}
pub fn get_rust_deprecated<'a>(ext_attrs: &Option<ExtendedAttributeList<'a>>) -> 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<ExtendedAttributeList>) -> 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(),
})
}
/// 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.
fn maybe_adjust<'a>(mut idl_type: IdlType<'a>, id: &'a OperationId) -> IdlType<'a> {
let immutable_f32_fns: Vec<&'static str> = vec![
// WebGlRenderingContext
"uniform1fv",
"uniform2fv",
"uniform3fv",
"uniform4fv",
"uniformMatrix2fv",
"uniformMatrix3fv",
"uniformMatrix4fv",
"vertexAttrib1fv",
"vertexAttrib2fv",
"vertexAttrib3fv",
"vertexAttrib4fv",
// TODO: Add another type's functions here. Leave a comment header with the type name
];
let mut immutable_f32_slice_whitelist: HashSet<&'static str> = HashSet::new();
for function_name in immutable_f32_fns.into_iter() {
immutable_f32_slice_whitelist.insert(function_name);
}
// example `op`... -> "vertexAttrib1fv"
if let OperationId::Operation(Some(op)) = id {
// Look up this funtion in our whitelist and see if we should make its
// slice argument immutable.
if immutable_f32_slice_whitelist.get(op).is_some() {
if let IdlType::Union(ref mut union) = idl_type {
if let IdlType::Float32Array { ref mut immutable } = union[0] {
*immutable = true;
}
}
}
}
idl_type
}