Assert that DOM structs have the correct first field

DOM structs embed their parent type as their first field. This
introduces a `.parent()` method to the DOM struct that returns its first
field, and codegens a type assert that ensures that `.parent()` returns
the parent struct.

This generates:

On `#[dom_struct]`:

```rust
impl HasParent for Type {
    type Parent = ParentType;
    fn as_parent(&self) -> ParentType {
        &self.first_field
    }
}
```

In the codegen files:

```rust
impl Type {
    fn __assert_parent_type(&self) {
        let _: &ParentType = self.as_parent();
    }
}
````
This commit is contained in:
Manish Goregaokar 2018-06-29 10:34:09 -07:00
parent e2fca1b228
commit ad198993b1
6 changed files with 109 additions and 9 deletions

4
Cargo.lock generated
View file

@ -722,6 +722,10 @@ dependencies = [
[[package]]
name = "dom_struct"
version = "0.0.1"
dependencies = [
"quote 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 0.14.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "domobject_derive"

View file

@ -1,9 +1,13 @@
[package]
name = "dom_struct"
version = "0.0.1"
authors = ["The Servo Project Developers"]
license = "MPL-2.0"
name = "dom_struct"
publish = false
version = "0.0.1"
[dependencies]
quote = "0.6.3"
syn = { version = "0.14.2", features = ["full"] }
[lib]
path = "lib.rs"

View file

@ -7,7 +7,12 @@
extern crate proc_macro;
use proc_macro::{TokenStream, quote};
#[macro_use]
extern crate quote;
extern crate syn;
use proc_macro::TokenStream;
use syn::*;
#[proc_macro_attribute]
pub fn dom_struct(args: TokenStream, input: TokenStream) -> TokenStream {
@ -23,5 +28,38 @@ pub fn dom_struct(args: TokenStream, input: TokenStream) -> TokenStream {
// Work around https://github.com/rust-lang/rust/issues/46489
let attributes: TokenStream = attributes.to_string().parse().unwrap();
attributes.into_iter().chain(input.into_iter()).collect()
let output: TokenStream = attributes.into_iter().chain(input.into_iter()).collect();
let item: Item = syn::parse(output).unwrap();
if let Item::Struct(s) = item {
let s2 = s.clone();
if s.generics.params.len() > 0 {
return quote!(#s2).into();
}
if let Fields::Named(ref f) = s.fields {
let f = f.named.first().expect("Must have at least one field").into_value();
let ident = f.ident.as_ref().expect("Must have named fields");
let name = &s.ident;
let ty = &f.ty;
quote! (
#s2
impl ::dom::bindings::inheritance::HasParent for #name {
type Parent = #ty;
/// This is used in a type assertion to ensure that
/// the source and webidls agree as to what the parent type is
fn as_parent(&self) -> &#ty {
&self.#ident
}
}
).into()
} else {
panic!("#[dom_struct] only applies to structs with named fields");
}
} else {
panic!("#[dom_struct] only applies to structs");
}
}

View file

@ -2116,6 +2116,10 @@ class CGDOMJSClass(CGThing):
self.descriptor = descriptor
def define(self):
parentName = self.descriptor.getParentName()
if not parentName:
parentName = "::dom::bindings::reflector::Reflector"
args = {
"domClass": DOMClass(self.descriptor),
"enumerateHook": "None",
@ -2161,7 +2165,51 @@ static Class: DOMJSClass = DOMJSClass {
reserved: [0 as *mut _; 3],
},
dom_class: %(domClass)s
};""" % args
};
""" % args
class CGAssertInheritance(CGThing):
"""
Generate a type assertion for inheritance
"""
def __init__(self, descriptor):
CGThing.__init__(self)
self.descriptor = descriptor
def define(self):
parent = self.descriptor.interface.parent
parentName = ""
if parent:
parentName = parent.identifier.name
else:
parentName = "::dom::bindings::reflector::Reflector"
selfName = self.descriptor.interface.identifier.name
if selfName == "PaintRenderingContext2D":
# PaintRenderingContext2D embeds a CanvasRenderingContext2D
# instead of a Reflector as an optimization,
# but this is fine since CanvasRenderingContext2D
# also has a reflector
#
# FIXME *RenderingContext2D should use Inline
parentName = "::dom::canvasrenderingcontext2d::CanvasRenderingContext2D"
args = {
"parentName": parentName,
"selfName": selfName,
}
return """\
impl %(selfName)s {
fn __assert_parent_type(&self) {
use dom::bindings::inheritance::HasParent;
// If this type assertion fails, make sure the first field of your
// DOM struct is of the correct type -- it must be the parent class.
let _: &%(parentName)s = self.as_parent();
}
}
""" % args
def str_to_const_array(s):
@ -6011,6 +6059,8 @@ class CGDescriptor(CGThing):
pass
else:
cgThings.append(CGDOMJSClass(descriptor))
if not descriptor.interface.isIteratorInterface():
cgThings.append(CGAssertInheritance(descriptor))
pass
if descriptor.isGlobal():

View file

@ -41,3 +41,8 @@ pub trait Castable: IDLInterface + DomObject + Sized {
}
}
}
pub trait HasParent {
type Parent;
fn as_parent(&self) -> &Self::Parent;
}

View file

@ -5,11 +5,10 @@
use dom::bindings::codegen::Bindings::BlobBinding::BlobMethods;
use dom::bindings::codegen::Bindings::FileReaderSyncBinding::{FileReaderSyncBinding, FileReaderSyncMethods};
use dom::bindings::error::{Error, Fallible};
use dom::bindings::reflector::reflect_dom_object;
use dom::bindings::reflector::{Reflector, reflect_dom_object};
use dom::bindings::root::DomRoot;
use dom::bindings::str::DOMString;
use dom::blob::Blob;
use dom::eventtarget::EventTarget;
use dom::filereader::FileReaderSharedFunctionality;
use dom::globalscope::GlobalScope;
use dom_struct::dom_struct;
@ -20,13 +19,13 @@ use std::ptr::NonNull;
#[dom_struct]
pub struct FileReaderSync {
eventtarget: EventTarget,
reflector: Reflector,
}
impl FileReaderSync {
pub fn new_inherited() -> FileReaderSync {
FileReaderSync {
eventtarget: EventTarget::new_inherited(),
reflector: Reflector::new(),
}
}