Skip to content

Enable unstable APIs for stable types. #2586

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 14, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 17 additions & 4 deletions crates/webidl/src/generator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -624,19 +624,21 @@ pub struct DictionaryField {
pub js_name: String,
pub ty: Type,
pub required: bool,
pub unstable: bool,
}

impl DictionaryField {
fn generate_rust(&self, options: &Options, parent_name: String, unstable: bool) -> TokenStream {
fn generate_rust(&self, options: &Options, parent_name: String) -> TokenStream {
let DictionaryField {
name,
js_name,
ty,
required: _,
unstable,
} = self;

let unstable_attr = maybe_unstable_attr(unstable);
let unstable_docs = maybe_unstable_docs(unstable);
let unstable_attr = maybe_unstable_attr(*unstable);
let unstable_docs = maybe_unstable_docs(*unstable);

let mut features = BTreeSet::new();

Expand Down Expand Up @@ -708,6 +710,15 @@ impl Dictionary {
}
}

// The constructor is unstable if any of the fields are
let (unstable_ctor, unstable_ctor_docs) = match unstable {
true => (None, None),
false => {
let unstable = fields.iter().any(|f| f.unstable);
(maybe_unstable_attr(unstable), maybe_unstable_docs(unstable))
}
};

required_features.remove(&name.to_string());

let cfg_features = get_cfg_features(options, &required_features);
Expand All @@ -725,7 +736,7 @@ impl Dictionary {

let fields = fields
.into_iter()
.map(|field| field.generate_rust(options, name.to_string(), *unstable))
.map(|field| field.generate_rust(options, name.to_string()))
.collect::<Vec<_>>();

quote! {
Expand All @@ -745,9 +756,11 @@ impl Dictionary {

#unstable_attr
impl #name {
#unstable_ctor
#cfg_features
#ctor_doc_comment
#unstable_docs
#unstable_ctor_docs
pub fn new(#(#required_args),*) -> Self {
#[allow(unused_mut)]
let mut ret: Self = ::wasm_bindgen::JsCast::unchecked_into(::js_sys::Object::new());
Expand Down
80 changes: 66 additions & 14 deletions crates/webidl/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,22 +25,23 @@ use crate::generator::{
use crate::idl_type::ToIdlType;
use crate::traverse::TraverseType;
use crate::util::{
camel_case_ident, is_structural, read_dir, shouty_snake_case_ident, snake_case_ident, throws,
webidl_const_v_to_backend_const_v, TypePosition,
camel_case_ident, is_structural, is_type_unstable, read_dir, shouty_snake_case_ident,
snake_case_ident, throws, webidl_const_v_to_backend_const_v, TypePosition,
};
use anyhow::Context;
use anyhow::Result;
use proc_macro2::{Ident, TokenStream};
use quote::ToTokens;
use sourcefile::SourceFile;
use std::collections::{BTreeMap, BTreeSet};
use std::collections::{BTreeMap, BTreeSet, HashSet};
use std::ffi::OsStr;
use std::fmt;
use std::fs;
use std::path::{Path, PathBuf};
use std::process::Command;
use wasm_bindgen_backend::util::rust_ident;
use weedle::attribute::ExtendedAttributeList;
use weedle::common::Identifier;
use weedle::dictionary::DictionaryMember;
use weedle::interface::InterfaceMember;
use weedle::Parse;
Expand Down Expand Up @@ -126,6 +127,22 @@ fn parse(
definitions.first_pass(&mut first_pass_record, ApiStability::Stable)?;

let unstable_definitions = parse_source(unstable_source)?;

// Gather unstable type Identifiers so that stable APIs can be downgraded
// to unstable if they accept one of these types
let unstable_types: HashSet<Identifier> = unstable_definitions
.iter()
.flat_map(|definition| {
use weedle::Definition::*;
match definition {
Dictionary(v) => Some(v.identifier),
Enum(v) => Some(v.identifier),
Interface(v) => Some(v.identifier),
_ => None,
}
})
.collect();

unstable_definitions.first_pass(&mut first_pass_record, ApiStability::Unstable)?;

let mut types: BTreeMap<String, Program> = BTreeMap::new();
Expand All @@ -138,7 +155,14 @@ fn parse(
for (js_name, d) in first_pass_record.dictionaries.iter() {
let name = rust_ident(&camel_case_ident(js_name));
let program = types.entry(name.to_string()).or_default();
first_pass_record.append_dictionary(&options, program, name, js_name.to_string(), d);
first_pass_record.append_dictionary(
&options,
program,
name,
js_name.to_string(),
d,
&unstable_types,
);
}
for (js_name, n) in first_pass_record.namespaces.iter() {
let name = rust_ident(&snake_case_ident(js_name));
Expand All @@ -148,7 +172,14 @@ fn parse(
for (js_name, d) in first_pass_record.interfaces.iter() {
let name = rust_ident(&camel_case_ident(js_name));
let program = types.entry(name.to_string()).or_default();
first_pass_record.append_interface(&options, program, name, js_name.to_string(), d);
first_pass_record.append_interface(
&options,
program,
name,
js_name.to_string(),
&unstable_types,
d,
);
}
for (js_name, d) in first_pass_record.callback_interfaces.iter() {
let name = rust_ident(&camel_case_ident(js_name));
Expand Down Expand Up @@ -252,6 +283,7 @@ impl<'src> FirstPassRecord<'src> {
name: Ident,
js_name: String,
data: &first_pass::DictionaryData<'src>,
unstable_types: &HashSet<Identifier>,
) {
let def = match data.definition {
Some(def) => def,
Expand All @@ -264,7 +296,7 @@ impl<'src> FirstPassRecord<'src> {

let mut fields = Vec::new();

if !self.append_dictionary_members(&js_name, &mut fields) {
if !self.append_dictionary_members(&js_name, &mut fields, unstable, unstable_types) {
return;
}

Expand All @@ -278,15 +310,21 @@ impl<'src> FirstPassRecord<'src> {
.to_tokens(&mut program.tokens);
}

fn append_dictionary_members(&self, dict: &'src str, dst: &mut Vec<DictionaryField>) -> bool {
fn append_dictionary_members(
&self,
dict: &'src str,
dst: &mut Vec<DictionaryField>,
unstable: bool,
unstable_types: &HashSet<Identifier>,
) -> 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) {
if !self.append_dictionary_members(parent.identifier.0, dst, unstable, unstable_types) {
return false;
}
}
Expand All @@ -299,7 +337,7 @@ impl<'src> FirstPassRecord<'src> {
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) {
match self.dictionary_field(member, unstable, unstable_types) {
Some(f) => dst.push(f),
None => {
log::warn!(
Expand All @@ -321,7 +359,17 @@ impl<'src> FirstPassRecord<'src> {
return true;
}

fn dictionary_field(&self, field: &'src DictionaryMember<'src>) -> Option<DictionaryField> {
fn dictionary_field(
&self,
field: &'src DictionaryMember<'src>,
unstable: bool,
unstable_types: &HashSet<Identifier>,
) -> Option<DictionaryField> {
let unstable_override = match unstable {
true => true,
false => is_type_unstable(&field.type_, unstable_types),
};

// use argument position now as we're just binding setters
let ty = field
.type_
Expand Down Expand Up @@ -376,6 +424,7 @@ impl<'src> FirstPassRecord<'src> {
name: rust_ident(&snake_case_ident(field.identifier.0)),
js_name: field.identifier.0.to_string(),
ty,
unstable: unstable_override,
})
}

Expand Down Expand Up @@ -424,7 +473,7 @@ impl<'src> FirstPassRecord<'src> {
}
}

for x in self.create_imports(None, id, data, false) {
for x in self.create_imports(None, id, data, false, &HashSet::new()) {
functions.push(Function {
name: x.name,
js_name: x.js_name,
Expand Down Expand Up @@ -465,6 +514,7 @@ impl<'src> FirstPassRecord<'src> {
program: &mut Program,
name: Ident,
js_name: String,
unstable_types: &HashSet<Identifier>,
data: &InterfaceData<'src>,
) {
let unstable = data.stability.is_unstable();
Expand Down Expand Up @@ -505,7 +555,7 @@ impl<'src> FirstPassRecord<'src> {
}

for (id, op_data) in data.operations.iter() {
self.member_operation(&mut methods, data, id, op_data);
self.member_operation(&mut methods, data, id, op_data, unstable_types);
}

for mixin_data in self.all_mixins(&js_name) {
Expand Down Expand Up @@ -533,7 +583,7 @@ impl<'src> FirstPassRecord<'src> {
}

for (id, op_data) in mixin_data.operations.iter() {
self.member_operation(&mut methods, data, id, op_data);
self.member_operation(&mut methods, data, id, op_data, unstable_types);
}
}

Expand Down Expand Up @@ -625,11 +675,12 @@ impl<'src> FirstPassRecord<'src> {
data: &InterfaceData<'src>,
id: &OperationId<'src>,
op_data: &OperationData<'src>,
unstable_types: &HashSet<Identifier>,
) {
let attrs = data.definition_attributes;
let unstable = data.stability.is_unstable();

for method in self.create_imports(attrs, id, op_data, unstable) {
for method in self.create_imports(attrs, id, op_data, unstable, unstable_types) {
methods.push(method);
}
}
Expand Down Expand Up @@ -663,6 +714,7 @@ impl<'src> FirstPassRecord<'src> {
.to_syn_type(pos)
.unwrap()
.unwrap(),
unstable: false,
})
}
_ => {
Expand Down
32 changes: 29 additions & 3 deletions crates/webidl/src/util.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use std::collections::BTreeSet;
use std::collections::{BTreeSet, HashSet};
use std::fs;
use std::iter::FromIterator;
use std::path::{Path, PathBuf};
Expand All @@ -11,7 +11,9 @@ use syn;
use wasm_bindgen_backend::util::{ident_ty, raw_ident, rust_ident};
use weedle;
use weedle::attribute::{ExtendedAttribute, ExtendedAttributeList, IdentifierOrString};
use weedle::common::Identifier;
use weedle::literal::{ConstValue, FloatLit, IntegerLit};
use weedle::types::{NonAnyType, SingleType};

use crate::constants::IMMUTABLE_SLICE_WHITELIST;
use crate::first_pass::{FirstPassRecord, OperationData, OperationId, Signature};
Expand Down Expand Up @@ -204,6 +206,7 @@ impl<'src> FirstPassRecord<'src> {
id: &OperationId<'src>,
data: &OperationData<'src>,
unstable: bool,
unstable_types: &HashSet<Identifier>,
) -> Vec<InterfaceMethod> {
let is_static = data.is_static;

Expand Down Expand Up @@ -437,6 +440,19 @@ impl<'src> FirstPassRecord<'src> {
.map(|(idl_type, orig_arg)| (orig_arg.name.to_string(), idl_type)),
);

// Stable types can have methods that have unstable argument types.
// If any of the arguments types are `unstable` then this method is downgraded
// to be unstable.
let unstable_override = match unstable {
// only downgrade stable methods
false => signature
.orig
.args
.iter()
.any(|arg| is_type_unstable(arg.ty, unstable_types)),
true => true,
};

if let Some(arguments) = arguments {
if let Ok(ret_ty) = ret_ty.to_syn_type(TypePosition::Return) {
ret.push(InterfaceMethod {
Expand All @@ -449,7 +465,7 @@ impl<'src> FirstPassRecord<'src> {
structural,
catch,
variadic,
unstable,
unstable: unstable_override,
});
}
}
Expand Down Expand Up @@ -480,7 +496,7 @@ impl<'src> FirstPassRecord<'src> {
structural,
catch,
variadic: false,
unstable,
unstable: unstable_override,
});
}
}
Expand Down Expand Up @@ -514,6 +530,16 @@ impl<'src> FirstPassRecord<'src> {
}
}

pub fn is_type_unstable(ty: &weedle::types::Type, unstable_types: &HashSet<Identifier>) -> bool {
match ty {
weedle::types::Type::Single(SingleType::NonAny(NonAnyType::Identifier(i))) => {
// Check if the type in the unstable type list
unstable_types.contains(&i.type_)
}
_ => false,
}
}

/// 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 {
Expand Down