Add Unchecked.withNull for unconstrained C# nullable-generic interop - #20232
Add Unchecked.withNull for unconstrained C# nullable-generic interop#20232T-Gro wants to merge 6 commits into
Conversation
Adds an inline escape hatch re-typing 'T to 'T | null with no not null / not struct constraint, so unconstrained C# nullable-generic APIs (T? M<T>()) can be implemented and consumed from F#. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: acdc940e-b98f-4072-b143-a3e296e12fcb
❗ Release notes requiredYou can open this PR in browser to add release notes: open in github.dev
|
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: acdc940e-b98f-4072-b143-a3e296e12fcb
Faithfully reproduces jwosty's IEventContext.GetValue<'T> attempt: the explicit generic member with an inferred 'T | null return now compiles clean under warnaserror:FS3261 thanks to Unchecked.withNull, where bare Unchecked.defaultof reports FS3261. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: acdc940e-b98f-4072-b143-a3e296e12fcb
Mirror the sibling nonNull "Unsafely retypes ... This is an unsafe operation." wording, and spell out why withNull is unsafe (it bypasses the not null / not struct constraints, so 'T | null can be formed for a struct where null is not representable and degrades to defaultof) and that it exists for interop with unconstrained C# T? generics. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: acdc940e-b98f-4072-b143-a3e296e12fcb
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: acdc940e-b98f-4072-b143-a3e296e12fcb
|
@T-Gro I'm currently on vacation, can't check properly, but will this work for wrapped cases? Like for |
Yes. It works for the wrapped case. You make the nullable value with { new IAsyncEventContext with
member _.GetValueAsync<'T>(index) =
Task.FromResult(Unchecked.withNull (Unchecked.defaultof<'T>)) } // : Task<'T | null>Note: for a struct type, you cannot read the result back at a fixed type. F# reports FS3265 there. This is correct. A struct has no null value. I added two tests for this. |
|
This is great, I just wonder if there might be a better name out there? At the risk of bikeshedding: I have a feeling that future me is going to be prone to forgetting Thanks for the rapid action Tomas! |
Relates to #17734.
Adds
Unchecked.withNull : 'T -> 'T | null, an escape hatch for implementing and consuming unconstrained C# nullable-generic APIs (T? M<T>(),interface { T? GetValue<T>(int) }). UnlikeOperators.withNullit places nonot null/not structconstraint on'T, so such a slot can be satisfied without FS3261.It is
inlineand erases to a retype ((# "" value : 'T | null #)) — nothing lands in IL. Documented as unsafe/interop-only:'T | nullis meaningless for a struct'T. Assigningnullto a resulting mutable at a struct lowers todefault(T)(ILVerify-clean through multiple generic layers); the same on a concrete struct mutable stays a compile error (FS0043).cc @Lanayx @jwosty