Skip to content
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

How to automate WebIDL type conversions and reflected attributes? #31

Closed
domenic opened this issue Jul 25, 2014 · 1 comment
Closed

Comments

@domenic
Copy link
Owner

domenic commented Jul 25, 2014

In all cases let's analyze this particular interface, since it's representative of a wide cross-section of features.

interface Foo : Bar {
    attribute unsigned long x;
    readonly attribute unsigned long y;
    boolean method(DOMString arg);
}

In this case we also want that "the x attribute must reflect the content attribute of the same name". Note that reflecting has special rules for each type that are largely about string parsing, and cannot be fully encapsulated in the WebIDL type alone.

.idl files generating wrappers

We'd commit an IDL file almost exactly like the above, but we'd add the [Reflect] annotation that is often used by implementers:

// Foo.idl
interface Foo : Bar {
    [Reflect] attribute unsigned long x;
    readonly attribute long y;
    boolean method(DOMString arg);
}

Then we'd write the following ES6:

// FooImpl.js
class FooImpl {
    get y() { return Math.random() * 1000; }
    method(arg) { return arg.toLowerCase(); }
}

Things to note:

  • We omit extends Bar and anything related to x since they are determined entirely by the IDL
  • We write the method/getter bodies assuming the correct type has been passed to us, and that the correct conversions will happen to our return types.

What will then happen is that we do something like

registerImpl("custom-foo", FooImpl);

and registerImpl generates a wrapper class Foo that delegates to FooImpl and does all the other stuff determined by the IDL. If we were to write it out, it would look like

// FooGenerated.js
import reflector from "./lib/webidl/reflector";
import conversions from "./lib/webidl/conversions";

class Foo extends Bar {
    get x() { return reflector["unsigned long"].get(this, "x"); }
    set x(v) { reflector["unsigned long"].set(this, "x", v); }

    get y() {
        var implGetter = Object.getOwnPropertyDescriptor(FooImpl.prototype, "y").get;
        var implResult = implGetter.call(this);
        return conversions["long"](implResult);
    }

    method(arg) {
        arg = conversions["DOMString"](arg);
        var implMethod = FooImpl.prototype.method;
        var implResult = implMethod.call(this, arg);
        return conversions["boolean"](implResult);
    }
}

Traceur Annotations

This solution avoids an external IDL file in favor of baking the same information into the source code. That has a lot of attraction to it.

Annotations are an experimental feature of Traceur. They purely decorate code with information that can later be used. There are a few possibilities, especially with regard to reflected attributes:

// Foo1.js
import { unsignedLong, long, boolean, DOMString } from "./lib/webidl/types";
import { Reflect } from "./lib/webidl/annotations";

class Foo extends Bar {
    @unsignedLong @Reflect get x() {};
    @unsignedLong @Reflect set x() {};

    @long get y() { return Math.random() * 1000; }

    @boolean method(@DOMString arg) { return arg.toLowerCase(); }
}
// Foo2.js
import { unsignedLong, long, boolean, DOMString } from "./lib/webidl/types";
import { Reflect } from "./lib/webidl/annotations";

@Reflect("x", unsignedLong)
class Foo extends Bar {
    @long get y() { return Math.random() * 1000; }

    @boolean method(@DOMString arg) { return arg.toLowerCase(); }
}

Again we'd use a level of indirection to translate the annotated class into one with the desired behavior, e.g.

registerAnnotated("custom-foo", Foo);

Things to note:

  • The inheritance hierarchy stays in the file
  • In Foo1.js, you have dummy a dummy getter and setter, with the type information repeated. But in Foo2.js, you have to move the Reflect annotation outside the class
  • This is no longer standard ES6

Traceur Types

Using Traceur's types feature we can do something a bit less messy, at least assuming we can fix a bug that currently exists where return types have no affect unless you check the type-assertions option.

// Foo1.js
import { unsignedLong, long, boolean, DOMString } from "./lib/webidl/types";
import { Reflect } from "./lib/webidl/annotations";

class Foo extends Bar {
    @Reflect get x() : unsignedLong {};
    @Reflect set x(v : unsignedLong) {};

    get y() : long { return Math.random() * 1000; }

    method(arg : DOMString) : boolean { return arg.toLowerCase(); }
}

There's similarly a Foo2.js which omits the dummy getter/setter in favor of @Reflect("x", unsignedLong)

My thoughts:

  • Very similar to annotations.
  • Postprocessing is still required

Yehuda's Decorators

Yehuda has a proposal for decorators. This is not implemented in Traceur, and so the fact we'd need to shave that yak might kill this idea out of the gate. But it does help our use cases quite a lot over annotations, as you will see.

// Foo.js
import { unsignedLong, long, boolean, DOMString } from "./lib/webidl/types";
import { Reflect, params } from "./lib/webidl/annotations";

class Foo extends Bar {
    +Reflect("x", unsignedLong)
    -long get y() { return Math.random() * 1000; }
    -boolean -params(DOMString) method(arg) { return arg.toLowerCase(); }
}

Things to note:

  • This would not require generating a wrapper based on inert information, like the types and annotations versions do. The decorators would be able to modify the code themselves. Nice.
  • Having the reflect be inside the class is a big plus.
  • The parameter situation is really ugly for that method. That probably kills this idea, to be honest. We could try combining with types, but now we're two nonstandard extensions deep.

Closing Statement

So far I like .idl files and Traceur types the most. I think I am leaning toward .idl files, largely because Traceur types are nonstandard (and I am still not satisfied with how they would handle reflected things).

@domenic
Copy link
Owner Author

domenic commented Aug 6, 2014

I am going to go with the ".idl files generating wrappers" approach. Opening a new issue to track that.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant