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

Declaring modules in WebIDL #592

Open
littledan opened this issue Dec 4, 2018 · 23 comments
Open

Declaring modules in WebIDL #592

littledan opened this issue Dec 4, 2018 · 23 comments
Labels

Comments

@littledan
Copy link
Collaborator

There's talk in various places about adding built-in modules to JavaScript and the Web Platform. For this, it could be useful to add support to WebIDL for defining a module.

namespace/[LegacyNamespace=...] could be a good model here, but lots of details are a bit different, as modules don't have all the flexibility of objects. For example, attributes in modules (which will always be readonly) shouldn't trigger any kind of evaluation/execution when they are accessed.

We could start with modules exporting only functions and interfaces (classes). It could look something like this:

module "std::foo" {
  double sum(double x, double y);
}
[Module="std::foo"]
interface Bar { }

Thoughts?

(this issue should be tagged jsidl)

@annevk annevk added the jsidl label Dec 4, 2018
@annevk
Copy link
Member

annevk commented Dec 4, 2018

It seems more natural to nest the interface inside the module and support "partial module" for splitting things up, if needed.

@littledan
Copy link
Collaborator Author

Well, something like that was my original intuition for namespaces as well, but we ended up going with an extended attribute in #425 ; I was trying to copy that convention. I'd be fine with this instead, though it may be more work in parsers to make it work:

module "std::foo" {
  double sum(double x, double y);
  interface Bar { }
}

Should namespaces use this pattern too, for [LegacyNamespace]? (I guess I'm not sure where to say "legacy", then...)

Partial modules sound important too, I agree.

@domenic
Copy link
Member

domenic commented Dec 4, 2018

I think extended attributes is much more natural. Web IDL doesn't have any nesting so far I believe, and that's quite nice; you just read the top-level to find all constructs. I don't see any benefits of a nesting syntax.

@bzbarsky
Copy link
Collaborator

bzbarsky commented Dec 4, 2018

Should namespaces use this pattern too, for [LegacyNamespace]?

There are namespaces, which have dedicated syntax, and there is the [LegacyNamespace] thing, which is meant for some existing stuff that doesn't actually look like Web IDL namespace, right?

@littledan
Copy link
Collaborator Author

@bzbarsky I don't understand the technical difference; it seems like the main thing is that one is legacy and the other is not. What does a namespace "actually" look like?

How should we decide between @annevk's and @domenic's proposals? I am OK with either.

@annevk
Copy link
Member

annevk commented Dec 5, 2018

My preference is for nesting since we mostly use extended attributes for things we didn't think of initially and bolted on later. It seems to me that if we're gonna add namepacing it should be a first-class citizen syntax-wise.

@littledan
Copy link
Collaborator Author

If interfaces in namespaces becomes more widespread (we'll at least add it for Intl if that is converted to WebIDL), should we make the analogous change for namespaces?

@domenic
Copy link
Member

domenic commented Dec 5, 2018

I don't think extended attributes reflect bolted-on versus not. I think they reflect "modifies something" vs. not.

IMO having classes (and other constructs) be the top-level has a number of nice properties, e.g. implementers often put each class in its own file. I don't think the aesthetic benefits of nesting (which are debatable anyway; not everyone enjoys an extra level of indentation) outweigh the complexity of introducing two levels to the IDL system.

@littledan
Copy link
Collaborator Author

@domenic Would "partial module" be enough to allow file splitting?

@domenic
Copy link
Member

domenic commented Dec 5, 2018

Sure, anything's possible. It's just a matter of what's ergonomic. In that case I see a couple possible outcomes:

  • The spec uses one big module block, but implementers want to continue to use single file per class (e.g. because that's easier for their tooling to process, with 1:1:1 IDL/.h/.cpp files), so they have IDL that does not match the spec. This is especially bad if they're trying to run tools that check against the spec; now those tools need to build in awareness of this situation.

  • The spec uses separate module blocks per class, using partial module there too. Now we're basically in extended attribute land, but with an extra level of indentation. (And we have the confusion where the first class definition has to use module, but the others use partial module, making refactoring annoying.)

@annevk
Copy link
Member

annevk commented Dec 6, 2018

I guess another way of looking at this is what the scope of the name is. Can interface names overlap due to modules and namespaces?

@domenic
Copy link
Member

domenic commented Dec 17, 2018

Can interface names overlap due to modules and namespaces?

I think they should be able to; that's part of the idea of namespaces after all. E.g. you might do import { Connection } from "@std/bluetooth" and import { Connection } from "@std/quic" or similar.

@littledan
Copy link
Collaborator Author

@domenic I think this relates to the question of whether an extended attribute makes sense for putting an interface in a module: if they have the same name, then don't they sort of clash, if they are both at the top level? If you have a partial interface declaration later, how do you determine which one it resolves to?

@domenic
Copy link
Member

domenic commented Dec 18, 2018

I can see how they might "sort of" clash on an intuitive level, but I think it's pretty easy to make a rule that partial declarations would have to have matching [Module] or [LegacyNamespace] declarations.

@bzbarsky
Copy link
Collaborator

Just to chime in on a pure implementation level, right now webidl has various scopes that constrain name resolution (toplevel, dictionary, interface) and all those scopes map to reasonable syntactic constructs. Introducing scopes that don't map to a simple syntactic construct would lead to some amount of parser implementation complexity which may not be warranted...

@littledan
Copy link
Collaborator Author

@bzbarsky Do you mean, in practice, it would be easier to distinguish the scope using an extended attribute?

@bzbarsky
Copy link
Collaborator

No, in practice (at least in the Gecko parser) it would be easier to distinguish the scope by having an explicit syntactic construct with nesting for that scope, like namespace foo { ... } or whatnot.

@domenic
Copy link
Member

domenic commented Dec 18, 2018

That's helpful feedback to have. How does that weigh against the issues around the traditional one interface per file, as discussed in #592 (comment) ?

@bzbarsky
Copy link
Collaborator

Gecko already doesn't do that in some cases. For example, all the WebGL interfaces are in one file, for various reasons.

The file splitting behavior is more or less a convenience; conceptually IDL is all a single chunk of "stuff". Sometimes one wants a separate file for an interface, sometimes a separate file for just a dictionary, etc. In a C++ codebase this is all complicated by the restrictive #include mechanism, which might force or prevent specific file splittings...

I do agree that if we allow splitting a namespace across multiple files then that is conceptually not that much different than extended attributes on an implementation level, since you end up having to create a canonical object representing the namespace scope and then tying all the pieces to it.

@littledan
Copy link
Collaborator Author

littledan commented Mar 2, 2019

Some notes on how it might make sense to specify modules in WebIDL:

Any concerns with this outline?

@domenic
Copy link
Member

domenic commented Mar 5, 2019

It seems like a goal of the design is to allow reuse of names between globals and modules. But, I don't think the current draft PR has this quite figured out. In particular, consider the situation

module "std:dom2" {
  interface Node { };

  interface Foo {
    void op(Node n);
  };
 
  interface Bar : Node {};

  interface mixin Baz;
  Node includes Baz;
};

module "std:dom3" {
  interface Node {};
};

In these situations, which Node is being referred to in the various usage sites?

It seems like there's a lot of assumptions currently baked in to Web IDL about there being one global name table :-/. Probably we'd start needing notions of "fully qualfied name" (like "std:dom2"/Node or something).

@littledan
Copy link
Collaborator Author

Yeah, this is definitely an area of the PR that needs work; how partial interfaces in partial modules actually accumulate the right members (and not members outside the module) is another. I'm thinking to define both partials and name resolution imperatively as building blocks in separate PRs to make this possible.

As for a notion of fully qualified name, how would qualified name be? This is what determines the name for @@toStringTag, but it seems like it'd be unique enough to me.

Do you think there are ever use cases where you'd shadow a name defined globally with one that's local to a module, and still want to refer to the global one within that module? If not, we can save ourselves a bit of hassle by not needing to define IDL syntax for these cases.

@domenic
Copy link
Member

domenic commented Mar 5, 2019

As for a notion of fully qualified name, how would qualified name be? This is what determines the name for @@toStringTag, but it seems like it'd be unique enough to me.

Interesting. I see per the PR the @@toStringTag for the examples would be "std:dom2.Node"?I'm not sure how I feel about that, although in general I'm not sure how I feel about @@toStringTag usage at all. (I vaguely would prefer we stopped using it for new things, maybe.)

Do you think there are ever use cases where you'd shadow a name defined globally with one that's local to a module, and still want to refer to the global one within that module?

I can definitely think of cases, e.g. defining a modern type in a module and wanting to add a utility method to convert from the legacy global type to the modern type. But, I think it is fine for an MVP to not allow such references, and add syntax for them when we actually encounter such cases.

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

Successfully merging a pull request may close this issue.

4 participants