Interface types don't always map to valid Web IDL types
Some of the conversions between JS values and interface types are defined in terms of the conversions between JS values and Web IDL types. However, not all of those interface types are valid Web IDL types.
For example:
option<option<T>>- dissallowed, since JSnullcould map to eithernoneorsome(none).union { option<A>, option<B> }- disallowed, since JSnullcould map to either anoneofoption<A>or anoneofoption<B>.union { T, T }- disallowed, since it's impossible to tell whether a value should beTvariant 1 orTvariant 2.
A more complex one:
record foo {
a: u32,
}
record bar {
b: s32,
}
union { foo, bar }
Even though foo and bar can technically be distinguished, Web IDL blanket disallows unions of dictionaries, presumably for simplicity.
How should these cases be handled? Should they be disallowed, or fall back on some less ergonomic representation?
That's a great observation and question. I'll preface this up-front by saying this answer isn't fully fleshed out and may need to be revised, but what I was thinking is that these problematic union cases (which we can statically recognize via a spec-defined predicate on the interface type) would fall back to the generic variant JS representation. The JS binding for variant is currently marked as TBD b/c I didn't have time to fully look at the state of the art here (e.g. in TypeScript, the bindings of other statically-typed compile-to-JS languages, and any relevant TC39 proposals in-flight), but my current working idea is that a (variant (case "a" string) (case "b" u32)) would produce JS values that looked like { a: "hi" } or { b: 42 }.
For your examples:
(option (option T))would correspond to JS values looking like{ some: t },{ some: null }or{ none: undefined }(union (option A) (option B))would correspond to JS values looking like{ '0': null },{ '0': a },{ '1': null },{ '1': b }(since unions are specializations of variants with index-named cases)(union T T)~~would not be a valid interface type~~ [Edit:] would correspond to JS values looking like{ '0': t }or{ '1': t }.(union (record (field "a" u32)) (record (field "b" s32)))would correspond to JS values looking like{ '0': { a: 42 } }or{ '1': { b: -1 } }.
(union T T)would not be a valid interface type (validation/subtyping rules need to be better written up here)
Shouldn't this be fine and result in {0: T} | {1 : T} as elsewhere? This is just a special case of two overlapping types, which are allowed otherwise. Making a special rule for it causes composability/evolution problems: imagine you have a union of two types somewhere, later one of them gets extended upstream such that they happen to become equivalent, and suddenly the downstream union is rendered illegal.
Ah, I hadn't actually thought too carefully about this restriction but those are good points; I think you're right.
Oops, this dropped off my radar. I created #96 adding notes to speak to these corner cases, which I think should resolve this issue. LMKWYT