Skip to content

[TrimmableTypeMap] SafeJavaCollectionFactory per-container construction#12159

Merged
jonathanpeppers merged 17 commits into
mainfrom
dev/simonrozsival/trimmable-safe-container-converter-factory
Jul 22, 2026
Merged

[TrimmableTypeMap] SafeJavaCollectionFactory per-container construction#12159
jonathanpeppers merged 17 commits into
mainfrom
dev/simonrozsival/trimmable-safe-container-converter-factory

Conversation

@simonrozsival

@simonrozsival simonrozsival commented Jul 17, 2026

Copy link
Copy Markdown
Member

Note

Replacement for #12131 because its fork head repository was deleted. This branch now lives in dotnet/android.

What

Reworks the trimmable-typemap Java-collection converter (SafeJavaCollectionFactory) so the AOT-safe construction of JavaList<T> / JavaCollection<T> / JavaDictionary<K,V> is explicit and easy to reason about, and hardens a couple of edge cases. Net change touches the two implementation files, SafeJavaCollectionFactory.cs and ValueTypeFactory.cs, plus a targeted JavaConvertTest regression test (the factory name, public entry point name, and JavaConvert.cs are unchanged vs main).

Construction split into explicit, non-overlapping paths

Container kind is resolved once when the converter is selected, producing a container-specific closure (CreateListFromJniHandle / CreateCollectionFromJniHandle / CreateDictionaryFromJniHandle) that captures only its exact generic arguments. Each method handles exactly one wrapper:

  • reference element/key/value args → direct new for the concrete IJavaPeerable rooting anchors; other reference types use MakeGenericType(...) + Activator.CreateInstance, riding NativeAOT's __Canon canonical templates;
  • mapped primitive/nullable argsValueTypeFactory, rooting the exact instantiation with a direct new (no reflection);
  • any other value type (e.g. a custom struct) → this factory declines the conversion so JavaConvert preserves its existing untyped collection fallback, covered for list, collection, and dictionary interface targets by a trimmable-path regression test.

Concrete-literal rooting (replaces [DynamicallyAccessedMembers] for reference wrappers)

Each reference path has an if (elementType == typeof(IJavaPeerable)) branch that constructs the concrete JavaList<IJavaPeerable> / JavaCollection<IJavaPeerable> / JavaDictionary<IJavaPeerable,IJavaPeerable> literal. That branch is effectively unreachable at runtime, but being a statically reachable reference to the concrete instantiation it forces the trimmer/ILC to keep the (IntPtr, JniHandleOwnership) activation constructor of the shared __Canon template. The else branch reuses exactly that canonical constructor for every other reference-argument instantiation. This roots the constructor through real code rather than relying on [DAM] flowing through reflection.

ValueTypeFactory's mixed reference/value dictionaries (JavaDictionary<string,int>, …) use the analogous CreateReferenceMixedDictionary<JavaDictionary<IJavaPeerable,T>> / <JavaDictionary<T,IJavaPeerable>> exemplars to root the JavaDictionary<__Canon,T> / JavaDictionary<T,__Canon> templates, replacing the previous static rooting-token fields. Value/value dictionaries continue to be rooted by the statically-emitted GVM cross-product (new JavaDictionary<TKey,T>()).

Hardening

  • Reject open / partially-open constructed targets (IList<>, IDictionary<T,int>, …) via a ContainsGenericParameters check up front, so they fail cleanly instead of throwing ArgumentException during activation.

Suppressions

The reflective paths carry the minimum verified suppressions: IL3050 (RequiresDynamicCode) and IL2071 on MakeGenericType, plus IL2072 on the Activator.CreateInstance calls whose constructed types ride the rooted canonical templates. The IL2071 justification states the real cause — MakeGenericType cannot prove the individual runtime type arguments satisfy the [DAM(Constructors)] requirements on the wrapper's element parameters, requirements only exercised by the dynamic-code path (the trimmable path never activates elements through the wrapper).

Validation

  • ✅ Builds locally (Mono.Android, Debug, net11.0) with no new warnings and no IL trim/AOT analysis errors.
  • ✅ Added NativeAOT reference-only round trips for list, collection, and dictionary targets; expected wrapper types remain open generic definitions so the closed wrappers must be activated through the rooted canonical templates.

Co-authored-by: Copilot 223556219+Copilot@users.noreply.github.com

simonrozsival and others added 8 commits July 16, 2026 12:14
…rterFactory

Pure rename in preparation for the container converter redesign: the
factory will grow per-container factories and own non-generic collection
targets, so "Collection" no longer describes its scope. Also renames the
entry point TryGetFromJniHandleConverter -> TryCreateConverter and updates
the JavaConvert and ValueTypeFactory references.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: f9ec4f09-ffc5-4f66-99bf-ec40ea2728e8
…ner factories

Split the container converter into explicit, per-container factories
(JavaListTypeFactory, JavaCollectionTypeFactory, JavaDictionaryTypeFactory)
chained via TryCreateFromJniHandle(...) || ..., following the AOT prototype:

- reference element/key/value arguments -> typeof(<>).MakeGenericType + Activator,
  riding NativeAOT's __Canon canonical templates;
- mapped primitive/nullable arguments -> ValueTypeFactory, rooting the exact
  instantiation with a direct `new` (no reflection);
- any other value type (custom struct) -> NotSupportedException, instead of the
  previous silent fall-through to a non-generic wrapper.

TryCreateConverter now also owns non-generic JavaList/JavaCollection/JavaDictionary
targets (and the raw IList/ICollection/IDictionary interfaces), delegating to the
existing FromJniHandle helpers (direct construction, no reflection). JavaConvert's
trimmable branch moves out of the IsGenericType guard so the factory is the single
entry point for all Java collection construction on the trimmable path.

NOTE: not yet built locally (requires the full framework); pending CI/local build.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: f9ec4f09-ffc5-4f66-99bf-ec40ea2728e8
The explicit <Compile> item still referenced the old
SafeJavaCollectionFactory.cs filename, breaking the build with CS2001.
Point it at the renamed SafeContainerConverterFactory.cs.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: f9ec4f09-ffc5-4f66-99bf-ec40ea2728e8
Convert ISafeContainerTypeFactory into an abstract SafeContainerTypeFactory
base class and move the MakeGenericType and CreateInstance reflection helpers
(with their trimming/AOT suppressions) onto it as protected members, so the
per-container factories use them via inheritance instead of sharing them
through the entry-point class.

MakeGenericType now takes a concrete reference-argument wrapper exemplar
(e.g. JavaList<IJavaPeerable>) whose DynamicallyAccessedMembers annotation
roots the __Canon template, making the AOT safety obvious. The dishonest
[return: DAM] is dropped in favor of scoping the constructor suppression to
CreateInstance. ValueTypeFactory's mixed reference/value dictionaries adopt
the same exemplar pattern, removing the dedicated rooting-token fields.

Also rename the dictionary GetValueTypeFactoryOrThrow helper to a pure
TryGetPrimitiveValueTypeFactory Try-pattern method, with an up-front
EnsureReferenceOrPrimitive validation, and revert the entry-point class name
back to SafeJavaCollectionFactory to reduce churn.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 50ab60e5-5b4d-4e5f-abd2-2878d87a10c9
…stification

Revert the JavaConvert.cs restructuring that hoisted the trimmable-typemap
branch out of the generic-target guard. Non-generic collection targets are
already handled identically by the existing IDictionary/IList/ICollection
fallback below, so routing them through SafeJavaCollectionFactory was
redundant. JavaConvert.cs now matches main except for the entry-point method
name. Drop the resulting dead TryCreateNonGenericConverter helper (and the
now-unused System.Collections import) from SafeJavaCollectionFactory.

Also correct the IL2055 suppression justifications on MakeGenericType and
CreateReferenceMixedDictionary: IL2055 is raised because the runtime element
arguments cannot be proven to satisfy the DynamicallyAccessedMembers
(Constructors) requirement on the wrapper's element type parameters, not
because of the wrapper's own constructor. On the trimmable typemap path the
wrapper never activates its elements (peer creation goes through JavaConvert
and the typemap), so the requirement is never exercised.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 50ab60e5-5b4d-4e5f-abd2-2878d87a10c9
…static factories

Replace the DynamicallyAccessedMembers-based rooting of the reference-argument
collection wrappers with a concrete-literal approach that is easier to reason
about. Each factory now branches on the IJavaPeerable element shape: that branch
is a statically reachable reference to the concrete JavaList<IJavaPeerable> /
JavaCollection<IJavaPeerable> / JavaDictionary<IJavaPeerable,IJavaPeerable>
instantiation, so the trimmer/ILC keeps the (IntPtr, JniHandleOwnership)
activation constructor of the shared __Canon template that the reflective else
branch reuses for every other reference-argument instantiation.

Also:
- Reject open and partially-open constructed target types (ContainsGenericParameters)
  up front so they fail cleanly instead of throwing ArgumentException during activation.
- Convert the per-container factories to static classes with a single static
  TryCreateFromJniHandle method (no abstract base class or instances).
- Trim the trimming/AOT suppressions to the verified minimum (IL3050 + IL2055 on
  MakeGenericType) and correct the IL2055 justification: it fires because
  MakeGenericType cannot prove the runtime element type satisfies the wrapper's
  DAM(Constructors) element-parameter requirement, which is never exercised on the
  trimmable path since the wrapper does not activate its elements.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 50ab60e5-5b4d-4e5f-abd2-2878d87a10c9
Merge the three static per-container factory classes into three private static
methods on SafeJavaCollectionFactory (TryCreateListFromJniHandle,
TryCreateCollectionFromJniHandle, TryCreateDictionaryFromJniHandle), moving
their suppressions and helpers onto the class and making ActivationConstructorBinding
a private const. No behavior change.

Also rename the entry point TryCreateConverter back to TryGetFromJniHandleConverter,
which restores the original name and leaves JavaConvert.cs identical to main.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 50ab60e5-5b4d-4e5f-abd2-2878d87a10c9
Add the missing trim suppressions, clarify activation behavior, simplify the mixed dictionary helper, and cover unsupported value-type containers.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 70f63eb7-6599-414c-a947-d860705aa0fa
Copilot AI review requested due to automatic review settings July 17, 2026 13:32

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Refactors the trimmable typemap’s Java-collection conversion path to make NativeAOT-safe construction of JavaList<T>, JavaCollection<T>, and JavaDictionary<K,V> explicit (reference-template vs mapped-primitive vs unsupported value-type), and adds a regression test to ensure unsupported value-type container arguments throw NotSupportedException on the trimmable path.

Changes:

  • Reworks SafeJavaCollectionFactory into explicit per-container construction paths with early rejection of open/partially-open generic targets.
  • Updates ValueTypeFactory mixed reference/value dictionary construction to root the canonical templates via exemplar instantiations rather than static “token” types.
  • Adds a JavaConvertTest regression test and unwraps TargetInvocationException so assertions observe the inner exception type.
Show a summary per file
File Description
tests/Mono.Android-Tests/Mono.Android-Tests/Java.Interop/JavaConvertTest.cs Adds regression coverage for unsupported value-type generic arguments on the trimmable typemap path; improves reflection-invocation exception unwrapping for accurate assertions.
src/Mono.Android/Java.Interop/ValueTypeFactory.cs Adjusts mixed dictionary creation to use exemplar-rooted canonical templates and centralizes reflective activation for those mixed shapes.
src/Mono.Android/Java.Interop/SafeJavaCollectionFactory.cs Splits list/collection/dictionary creation into explicit, non-overlapping paths and hardens behavior for open generic targets and unsupported value-type arguments.

Copilot's findings

  • Files reviewed: 3/3 changed files
  • Comments generated: 0

@simonrozsival

Copy link
Copy Markdown
Member Author

/review

@github-actions

github-actions Bot commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Android PR Reviewer completed successfully!

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Code Review — ⚠️ Minor comments

Verdict: Looks solid. The rework of SafeJavaCollectionFactory into explicit, non-overlapping per-container paths (TryCreateListFromJniHandle / TryCreateCollectionFromJniHandle / TryCreateDictionaryFromJniHandle) is a clear readability win over the previous shape-based dispatch, and the concrete-literal IJavaPeerable rooting branches are a defensible way to keep the __Canon activation constructors alive without leaning on [DAM] reflection flow.

What I verified

  • Dictionary argument routing is correct for all four mixed/uniform combinations (<ref,val>, <val,ref>, <val,val>, <ref,ref>), and unsupported value-type args (DateTime) are rejected up front via EnsureReferenceOrPrimitive before any reflection — matching the new FromJniHandle_UnsupportedValueTypeThrows test cases.
  • ContainsGenericParameters hardening correctly rejects open/partially-open targets before MakeGenericType.
  • Suppression justifications (IL3050/IL2055/IL2072) are specific and accurately describe the __Canon template sharing; the corrected IL2055 rationale reads well.
  • Test harness change (ExceptionDispatchInfo unwrapping of TargetInvocationException) is the right way to assert the inner NotSupportedException.

Comments (both 💡, non-blocking)

  1. Behavioral change: unsupported value-type containers now throw at conversion time instead of falling through to the non-generic JavaList/JavaCollection/JavaDictionary.FromJniHandle fallback — please confirm no consumer depended on that path. (inline)
  2. Null-check inconsistency between SafeJavaCollectionFactory's Activator.CreateInstance results and ValueTypeFactory.CreateReferenceMixedDictionary. (inline)

Notes

  • CI checks were still pending at review time — this is not an approval; verify green CI (and ideally the recommended NativeAOT round-trip called out in the PR description) before merge.

Nice, well-documented change overall. 👍

Generated by Android PR Reviewer for #12159 · 112.3 AIC · ⌖ 13.2 AIC · ⊞ 6.8K
Comment /review to run again

Comment thread src/Mono.Android/Java.Interop/SafeJavaCollectionFactory.cs Outdated
Comment thread src/Mono.Android/Java.Interop/SafeJavaCollectionFactory.cs Outdated
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 51cb0e43-153b-4458-8cf1-bb5def48b541
…tory

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 51cb0e43-153b-4458-8cf1-bb5def48b541
Keep exact NativeAOT factories limited to primitive and nullable value types. Unsupported structs now decline the typed converter so JavaConvert retains its existing untyped collection fallback instead of requiring DateTime-family special cases.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 3deb271e-aea1-4ba1-bad5-2cdc4cf847bb
@jonathanpeppers

Copy link
Copy Markdown
Member

/review

@github-actions

github-actions Bot commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Android PR Reviewer completed successfully!

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Android PR Review — ⚠️ Minor suggestions

Reviewed the reworked trimmable-typemap Java-collection converter (SafeJavaCollectionFactory.cs, ValueTypeFactory.cs) and the new JavaConvertTest regression case.

Assessment

The refactor is clean and the design reasoning is sound. Verified independently:

  • Non-overlapping construction pathsTryCreate{List,Collection,Dictionary}FromJniHandle chained via || each guard on genericDefinition and return cleanly on mismatch, so there's no overlap or double-handling.
  • Argument filtering is correctAreSupportedCollectionArguments rejects any non-primitive value-type argument up front, which is why the indexer accesses on PrimitiveTypeFactories [elementType] inside the value paths are safe (no KeyNotFoundException risk). The new test confirms DateTime/UnsupportedValueType containers fall back to the untyped JavaList/JavaDictionary.
  • Open/partially-open target rejection — the ContainsGenericParameters check correctly short-circuits IList<>, IDictionary<T,int>, etc.
  • Value/value dictionary ordering — traced CreateDictionary → CreateDictionaryWithKey<TKey> and confirmed the resulting JavaDictionary<TKey, T> preserves key/value order.
  • Concrete-literal rooting branches — the IJavaPeerable anchors correctly root the __Canon activation constructor while the else branch reuses it.

Findings

  • 💡 One inline suggestion: null-guard consistency between the reflective branches in SafeJavaCollectionFactory and ValueTypeFactory.CreateReferenceMixedDictionary.

CI

Azure DevOps pipeline is pending (state: pending) at time of review — not yet green. A NativeAOT publish + runtime round-trip (as the PR description notes) is the key confirmation still outstanding; the added regression test only exercises the unsupported-value-type fallback, not the reference/reference or concrete-wrapper rooting paths on an actual AOT publish.

No blocking issues found in the code itself.

Generated by Android PR Reviewer for #12159 · 88 AIC · ⌖ 13.1 AIC · ⊞ 6.8K
Comment /review to run again

Comment thread src/Mono.Android/Java.Interop/SafeJavaCollectionFactory.cs Outdated
Fail fast if Activator.CreateInstance unexpectedly returns null, and annotate successful TryCreate paths as non-null.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 3ae453dc-ec33-4fc5-8393-32ca368d993e
@jonathanpeppers jonathanpeppers added the ready-to-review This PR is ready to review/merge, I think any CI failures are just flaky (ignorable). label Jul 20, 2026
Comment thread src/Mono.Android/Java.Interop/SafeJavaCollectionFactory.cs Outdated
Comment thread src/Mono.Android/Java.Interop/SafeJavaCollectionFactory.cs Outdated
Select the container-specific converter once, capture individual generic arguments, and use direct construction for canonical rooting branches.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 3ae453dc-ec33-4fc5-8393-32ca368d993e
@jonathanpeppers

Copy link
Copy Markdown
Member

/review

@github-actions

github-actions Bot commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Android PR Reviewer completed successfully!

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Review — SafeJavaCollectionFactory per-container construction

Solid, well-reasoned refactor. The split into explicit CreateList/CreateCollection/CreateDictionary paths is much easier to follow than the prior shared reflection helpers, and the concrete-literal IJavaPeerable rooting branches are a legitimate technique to keep the __Canon activation constructor alive without leaning on [DAM] reflection flow. The upfront ContainsGenericParameters rejection is a good hardening addition, and the suppression justifications are unusually thorough and accurate.

I verified the construction logic against the full files:

  • AreSupportedCollectionArguments correctly gates value types to the mapped primitive/nullable set, so unsupported value types cleanly decline and fall back to JavaConvert's untyped path (well covered by the new FromJniHandle_UnsupportedValueTypeUsesUntypedFallback cases).
  • The value/value, value/reference, and reference/value dictionary dispatch in CreateDictionaryFromJniHandle + CreateReferenceMixedDictionary<TExemplar> all resolve to the correct JavaDictionary<K,V> instantiation and canonical template.
  • IntPtr.Zero handles are short-circuited to null before any activation.

No correctness or safety issues found. Two non-blocking 💡 suggestions posted inline:

  1. Reuse the ActivationConstructorBinding constant in ValueTypeFactory rather than re-inlining the same BindingFlags.
  2. Add a trimmable-path regression test for the pure reference/reference reflective branch (IList<Java.Lang.String> / IDictionary<string,string>), which is the exact rooting path the PR reworks and which the description notes is not yet validated under NativeAOT.

Verdict: ⚠️ Needs Changes is not warranted on correctness — treat this as ✅ with suggestions. Note: CI is still pending on the head commit, so this isn't mergeable until the Azure DevOps pipeline goes green; please confirm no new trim/AOT analysis warnings there, and ideally land the reference/reference test before merge.

Generated by Android PR Reviewer for #12159 · 111.9 AIC · ⌖ 13.2 AIC · ⊞ 6.8K
Comment /review to run again

Comment thread src/Mono.Android/Java.Interop/ValueTypeFactory.cs Outdated
jonathanpeppers and others added 2 commits July 21, 2026 14:18
Cover NativeAOT canonical template activation for reference-only list, collection, and dictionary targets, and share the wrapper constructor binding flags.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 3ae453dc-ec33-4fc5-8393-32ca368d993e
Return an explicit untyped collection converter when a known container has an unsupported value-type argument. This avoids JavaConvert reaching Convert.ChangeType for interned peers while keeping supported converter dispatch unchanged.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 3ae453dc-ec33-4fc5-8393-32ca368d993e
@jonathanpeppers

Copy link
Copy Markdown
Member

/review

@github-actions

github-actions Bot commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Android PR Reviewer completed successfully!

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Review — ⚠️ minor suggestions only

I traced the reworked per-container construction across all key/value combinations (reference/reference, value/reference, reference/value, value/value, and unsupported-value fallback) and the dispatch logic is correct and well-structured. The split into per-container closures plus the concrete-literal IJavaPeerable rooting branches is a clear improvement over the previous reflection-token approach.

Verified

  • ✅ No leftover references to the removed ReferenceKeyDictionaryType / ReferenceValueDictionaryType fields or the old SafeJavaCollectionFactory.MakeGenericType / CreateInstance helpers.
  • ContainsGenericParameters early-reject correctly heads off open/partially-open targets before activation.
  • AreSupportedCollectionArguments correctly diverts any unsupported value-type argument (including mixed dictionaries) to the untyped fallback; the new regression test asserts the intended (deliberately non-assignable) fallback shape, matching pre-existing main behavior.
  • ✅ Value/value, value/reference, and reference/value dictionary rooting all resolve to the correct JavaDictionary<K,V> instantiation.

Suggestions (non-blocking, posted inline)

  • 💡 Performance — resolve the ValueTypeFactory once when the converter closure is built rather than re-looking-it-up on every conversion.
  • 💡 Testing — the new reference-argument test only asserts wrapper type identity; adding an element round-trip would harden it against activation/marshaling regressions.

Notes

  • CI status is currently pending on the head commit — not green yet. Please confirm the pipeline passes before merge.

Nice work — the suppressions are well-justified and the XML docs make the canonical-template reasoning easy to follow.

Generated by Android PR Reviewer for #12159 · 109.5 AIC · ⌖ 13.1 AIC · ⊞ 6.8K
Comment /review to run again

Comment thread src/Mono.Android/Java.Interop/SafeJavaCollectionFactory.cs Outdated
Resolve primitive value factories and dictionary argument shapes while selecting the converter. Conversion delegates now avoid repeated type checks and dictionary lookups while preserving unsupported-container fallback and null-handle behavior.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 3ae453dc-ec33-4fc5-8393-32ca368d993e
@jonathanpeppers
jonathanpeppers merged commit 1676f95 into main Jul 22, 2026
44 checks passed
@jonathanpeppers
jonathanpeppers deleted the dev/simonrozsival/trimmable-safe-container-converter-factory branch July 22, 2026 13:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready-to-review This PR is ready to review/merge, I think any CI failures are just flaky (ignorable). trimmable-type-map

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants