feat(generator): support map-like and pair-iterable types#533
feat(generator): support map-like and pair-iterable types#533
Conversation
- Automatically generate `JSIterable` implementations for types defined as iterable in Web IDL. - Generate `asMap` views for map-like types in `js_interop_gen` (e.g., `URLSearchParams`, `FormData`, `Headers`). - Generate `toDart` getters for pair-iterable types like `XRHand`. - Bump minimum SDK constraint to `^3.12.0-0` across packages to support new features like private named parameters. - Update CI workflows to test on `beta` and `dev` instead of `3.10`. - Fix test failures related to SDK version string checks. - Clean up refactorings in `translator.dart` to preserve `followedBy` style for review.
There was a problem hiding this comment.
Code Review
This pull request implements automatic generation of JSIterable implementations and asMap views for Web IDL types, significantly improving the Dart developer experience for interfaces like Headers, FormData, and URLSearchParams. The generator was updated to handle multiple inheritance for JS types, and the SDK requirement was bumped to 3.12.0. Review feedback highlights several correctness issues in the generated map view classes, specifically regarding improper type casting of keys, missing conversions to JS types for interop calls, and potential null-safety crashes in the operator [] and remove implementations.
| ..body = code.Code( | ||
| valueType.symbol == 'JSArray' | ||
| ? ''' | ||
| final k = key as $keyCastType; | ||
| final value = _jsObject.get(k); | ||
| if (value == null) return null; | ||
| return _jsObject.getAll(k); | ||
| ''' | ||
| : ''' | ||
| final value = _jsObject.get(key as $keyCastType); | ||
| return $getConversion; | ||
| ''', | ||
| ), |
There was a problem hiding this comment.
The implementation of operator [] in the generated map view has several correctness issues:
- Incorrect Cast: It uses
key as $keyCastType. If$keyCastTypeis an interop type likeJSString(which happens whenisFromIterableis false) and the user passes a DartString, this will throw aTypeError. It should cast to the Dart type (keyType.symbol) instead. - Missing Type Safety: Per Dart
Mapconventions,operator []should returnnullif the key is not of the expected type, rather than throwing an exception. - Missing JS Conversion: It passes the key directly to
_jsObject.get(). If the interop method expects aJSString, it needs to be converted (e.g., via.toJS). - Null Safety: In the
elseblock, it doesn't check ifvalueisnullbefore applying$getConversion. If$getConversioninvolves a call like.toDartDouble, it will crash on missing keys.
| ? code.Code(''' | ||
| final keys = _jsObject.keys().toDartIterable.toList(); | ||
| for (final k in keys) { | ||
| _jsObject.delete(${_toDartCall('k', keyInteropType.symbol)}); | ||
| } | ||
| ''') |
There was a problem hiding this comment.
In the clear() implementation for synthesized maps, the key k is incorrectly converted to a Dart type before being passed to _jsObject.delete(). Since k is obtained from toDartIterable on a JSIterator, it is already of the interop type (e.g., JSString). Converting it to a Dart String will cause the delete call to fail if it expects the interop type.
..body = info.isFromIterable
? code.Code('''
final keys = _jsObject.keys().toDartIterable.toList();
for (final k in keys) {
_jsObject.delete(k);
}
''')
: const code.Code('_jsObject.clear();'),| valueType.symbol == 'JSArray' | ||
| ? ''' | ||
| final k = key as $keyCastType; | ||
| final values = _jsObject.getAll(k); | ||
| // ignore: prefer_is_empty | ||
| if (values.length == 0) return null; | ||
| _jsObject.delete(k); | ||
| return values; | ||
| ''' | ||
| : ''' | ||
| final k = key as $keyCastType; | ||
| final value = _jsObject.get(k); | ||
| _jsObject.delete(k); | ||
| return $getConversion; | ||
| ''', | ||
| ), |
| final keyConversion = info.isFromIterable | ||
| ? 'key' | ||
| : _toJSCall('key', keyInteropType.symbol); |
There was a problem hiding this comment.
The keyConversion logic for isFromIterable types should still utilize _toJSCall. Even if the map is synthesized from an iterable, the underlying interop methods (like get or delete) may still expect JS types (like JSString) rather than Dart primitives.
final keyConversion = _toJSCall('key', keyInteropType.symbol);
JSIterableimplementations for types defined as iterable in Web IDL.asMapviews for map-like types injs_interop_gen(e.g.,URLSearchParams,FormData,Headers).toDartgetters for pair-iterable types likeXRHand.^3.12.0-0across packages to support new features like private named parameters.betaanddevinstead of3.10.translator.dartto preservefollowedBystyle for review.