-
Notifications
You must be signed in to change notification settings - Fork 72
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
JS API and nominal/structural typing #84
Comments
Hi, great question and thanks for the thoughtful write-up! (I'll assume in your example that One more-recent development is that, independent of both JS and wasm GC, we need roughly the same "nominal typing" mechanism in pure wasm in the form of abstract types that generate a fresh type upon each module instantiation. It's not clear what the precise final form will be, but this has been discussed briefly in the Type Imports proposal (#6 and #7). The motivation is allowing a module to encapsulate the representation of a type it exports so that it can, e.g., ensure no forged abstract type values. In the context of wasm abstract types, we have the same question that you're asking above. Once we have abstract types sorted out, we should probably refresh MVP-JS.md to, instead of introducing the problem as novel to JS, show how JS's nominal typing corresponds to wasm's abstract types. Getting to your example problem, I think the answer is that, in the presence of nominal/generative types, the structural |
OK, looks like when I added the ;; example4-modified.wat
(module
(type $Point (import "" "Point")
(eq (struct (field $x mut i32) (field $y mut i32))))
(type $Rect
(struct (field $x1 mut (ref $Point)) (field $x2 mut (ref $Point))))
(type $InternalPoint
(struct (field $x mut i32) (field $y mut i32)))
(type $PointSubclass
(struct (field $x mut i32) (field $y mut i32) (field $color mut f64)))
(type $HasPoint
(struct (field $point mut (ref $InternalPoint))))
(func $Helper (param (ref $HasPoint))
(struct.set $HasPoint $point
(get_local 0)
(struct.new $PointSubclass (i32.const 10) (i32.const 20) (f64.const 0.5))
)
(func (export "goWild") (param (ref $Rect))
(call $Helper (get_local 0))
)
) My understanding of the GC proposal itself in its current form is that this code should work:
So I think what it really boils down to is what you phrased as "the structural eq constraint is not enough to ensure type equality" -- to make the vision described in MVP-JS.md work, the imported type must somehow (either through explicit opt-in, or by default always) be non-interchangeable with structurally equivalent non-imported types. Which seems non-trivial to resolve with the general approach of a structural type system. I agree that if the Type Imports proposal end up solving the same problem, then that solution should be applicable here. |
Correct, and so if the imported I also feel the pain of supporting both nominal and structural and it's something I've spent some time trying to find a way out of, but I'm not sure I see a better concrete option at this point. In some sense, I think this is unavoidable: wasm already has structural typing (especially considering the structural type-equality check in cross-instance |
This is addressable in a (smoothly) backwards-compatible matter, so I would not let this concern outweigh very important things like representation-encapsulation or capability-safety. |
Another thought: a key driver behind the plan to let Wasm have structural typing is to enable multi-module ecosystems, where modules can inter-operate as long as they define structurally compatible types, as opposed to having to agree on one canonical type definition. Doesn't the same apply to JavaScript? Existing JavaScript has, let's call it a multi-faceted type system. Certainly, one could classify prototype identity as nominally-typed behavior. But a function like: function foo(o) { return o.bar(); } will happily operate on any object that has a So if we introduce strictly-nominal typing to JavaScript, then (aside from the question whether the JS community at large would approve of such a significant departure from traditional JS) that would run into the same issues as multi-module Wasm: if a JavaScript program wants to import several third-party libraries that operate on In summary, it seems to me that JavaScript Typed Objects and Wasm-GC should (one way or the other) likely end up with similar type systems, because many arguments that apply to the one also apply to the other. In that case, we likely wouldn't have to go through any contortions to deal with mismatches between the two worlds. |
* Remove passive segment func ref shorthand * Drop passive keyword
We have consensus on going with a "no-frills" approach to JS interop in the MVP (#279), so I don't think this issue is relevant anymore. Closing, but feel free to reopen if you disagree. |
The current text of MVP-JS.md goes to some lengths to describe a mechanism to preserve JavaScript's nominal typing through roundtrips through Wasm code. Maybe I'm missing something there... but right now I don't think that this is implementable. It seems to me that this is effectively introducing a fully nominal type system to Wasm-GC, and further does so by requiring each type to magically know whether it is supposed to behave structurally or nominally.
How is a Wasm module that imports a type supposed to know whether it is going to be instantiated from JavaScript, and not from some other environment? If any extra checks are performed only at the boundary (instantiation and value passing), what about module-internal function calls? Let me extend the given example by a helper function:
We can check when
goWild
is called that the argument is a proper$Rect
, but if the call to$Helper
performs regular structural typechecks, then that doesn't prevent the installation of an$InternalPoint
instance on the rect. How is that call supposed to determine that it must perform nominal type checks? Are all imported types supposed to have nominal identity? How does that mesh with subclassing? And how is that different from having to support nominal types in addition to structural types all across the system? And when compiling a Wasm function, how should the compiler decide whether to emit nominal or structural type checks?If I'm just entirely misunderstanding things here, then please educate me.
In case my confusion/concern is valid: I don't have a fully-fledged proposal for an alternative. My general inclination would be to let ourselves be guided by the following principle: rather than introducing JavaScript's types to WebAssembly, the boundary between the two should be shaped such that it acts as an adapter between the different worlds. Maybe an approach that could work is that Wasm objects, when exposed to JavaScript, by default have structural type behavior and indexed access, and there's a way to have Proxy-like views on them to "look at them through the right lens". Very rough strawman to illustrate the concept:
(Regarding what the syntax for such definitions might be, I don't have strong feelings either way; what I know from our JavaScript folks is that in order to get fast startup, it would be desirable for that syntax to be as declarative as possible (and hence lazily/partially executable), as opposed to having to parse, compile, and execute a big chunk of single-invocation JavaScript code, which is a performance concern with the
function makeTypes() {...}
approach in the existing text.)The text was updated successfully, but these errors were encountered: