Repository navigation
Make Rust JSG Nullable<T> mean T | null - #8015
Conversation
|
💬 Approved with comments — The conversion semantics, removal of the old API, tests and docs are correct and match each other. The only note is an optional SUGGESTION to mention the new Nullable mapping in src/rust/AGENTS.md. Findings: 1 suggestion The decision is advisory: this comment never approves, blocks or requests changes on the pull request. 🔧 Fix with your agentPaste the following into your coding agent to investigate and fix these issues. 📖 AGENTS.md: 💬 1 suggestionOptional: src/rust/AGENTS.md could mention the new Nullable semantics Recommendation: 💬 Optional Nits
View full review
✅ All other checks passed🔒 Security: ✅ LGTMNo security concerns. The change narrows the Rust JSG type semantics and adds no new trust boundaries. 📋 Code Quality: ✅ LGTMI found no problems in the changed code or in the code it interacts with.
⚡ Performance: ✅ LGTMNo performance concerns. 📝 Documentation: ✅ LGTMThe updated checklist entry and the doc comments in Reviewed commit: |
|
Since last review: 0 resolved, 0 still open, 0 new. Not re-run: api-compat, tests, docs, compat-flags, design-simplicity, jsg-gc, memory-safety, rust-first (no author changes in their files since the last review) Reviewed commit: f7b0802c · github run |
47504e8 to
59d7ab8
Compare
There was a problem hiding this comment.
Thanks for exploring this space further here.
One issue here is that the migration path of a C++ Maybe, which today accepts both undefined and null, would then migrate to Option<Nullable<T>> under JSG Rust - but this would correspond to the type foo?: T | null instead of the type foo: T | null. Thus, this type could only be representable by Nullable<T> in an upgrade path that wants to spit out the same TypeScript types, but then the undefined runtime support would be lost.
If we want to maintain the migration path for both the runtime and typing, I think the right consolidation for this explicit migration might be to extend this change to also treat Nullable<T> as accepting T | null | undefined in the runtime but treating the undefined path as a null coercion, and expressing as the type T | null in the typing output just like the KJ Maybe.
This would then allow Option<Nullable<T>> to represent truly optional nullables, while maintaing the WebIDL convention compat and arity checks.
Ah this is a good point, thanks for pointing it out! I hadn't considered the migration path and WebIDL angle enough. Updated PR according to your feedback |
guybedford
left a comment
There was a problem hiding this comment.
Thanks, this now matches kj::Maybe on both the runtime and the eventual T | null typing, with Option<Nullable<T>> covering optional T?.
Nits:
- The PR description still describes
undefinedas aTypeErrorand recommendsOption<Nullable<T>>+unwrap_orfor Web IDLT?; worth updating since it becomes the squash commit message. - Branch is a few commits behind main, a rebase before landing would be good.
jsg::Nullable<T> had three states (Some, Null, Undefined), so a single type stood for T | null | undefined. That has no counterpart in C++ JSG or in TypeScript, and it means RTTI for a Nullable has to describe both alternatives even when an API only ever uses one. Nullable<T> is now T | null, with Some and Null variants. A value that can also be undefined is written Option<Nullable<T>>, composing with Option<T>, which is T | undefined. The three states stay distinct: undefined is None, null is Some(Nullable::Null). Converting undefined to a Nullable<T> is now a TypeError, where C++ kj::Maybe<T> reads it as empty. A method taking Nullable<T> therefore needs null or a T, which matches how an omitted Nullable<T> argument was already rejected. For Web IDL's T?, which reads undefined as null, take Option<Nullable<T>> and call unwrap_or(Nullable::Null). Rejecting undefined is the stricter choice; accepting it later would not break existing callers, while the reverse would. API changes: - Remove Nullable::Undefined, is_undefined(), is_null_or_undefined(), not_undefined() and coerce_null(). - not_null() returns the contained value instead of Self. - Lenient converts to and from Option<Nullable<T>> rather than Nullable<T>, and to_parts() returns an Option<Nullable<T>>. Nothing outside the jsg tests uses Nullable yet.
Follow Web IDL nullable conversion and C++ kj::Maybe<T>: both null and undefined become Nullable::Null, while output remains T or null. Nullable method arguments remain required, so an explicit undefined is accepted but an omitted argument still throws a TypeError. This lets C++ bindings migrate to Nullable<T> without changing runtime acceptance, arity checks, or their T | null TypeScript representation. Option<Nullable<T>> remains optional and preserves undefined separately. Update conversion, method-argument, evaluation, and struct-field tests, and document the input coercion separately from the output type.
0bc688d to
f7b0802
Compare
jsg::Nullable<T>had three states (Some,Null,Undefined), so a single type representedT | null | undefinedrather than matching C++ JSG nullable values. There was no way to expressT | nullalone.Nullable<T>now representsT | null, withSomeandNullvariants. Input conversion follows Web IDL's nullable conversion and C++kj::Maybe<T>: bothnullandundefinedbecomeNullable::Null. Output conversion produces onlyTornull, matching the eventualT | nullTypeScript type. A method argument of this type remains required: passing explicitundefinedis accepted, but omitting the argument is aTypeError.An optional nullable is written
Option<Nullable<T>>, composing withOption<T>(T | undefined). It permits omission and keeps all three states distinct:undefinedisNone,nullisSome(Nullable::Null), and a value isSome(Nullable::Some(value)). This covers Web IDL'soptional T?, whileNullable<T>coversT?without changing runtime acceptance, arity checks, or typing when migrating C++ bindings.API changes:
Nullable::Undefined,is_undefined(),is_null_or_undefined(),not_undefined()andcoerce_null().not_null()returns the contained value instead ofSelf.Lenientconverts to and fromOption<Nullable<T>>rather thanNullable<T>, andto_parts()returns anOption<Nullable<T>>.Nothing outside the JSG tests uses
Nullableyet.