Join GitHub today
GitHub is home to over 28 million developers working together to host and review code, manage projects, and build software together.Sign up
Nullable Reference Types #317
This is a draft of F# nullable reference types.
It is based primarily on the C# proposal, with additional considerations for F# based on existing behavior we have.
I expect a lot of things that are written down to change over time (i.e., a good lot of what I've written may not actually work out well, but we'll see
changed the title from
[DRAFT] Nullable Reference Types
Nullable Reference Types
Jun 29, 2018
referenced this pull request
Jul 3, 2018
Some offline discussion with @cartermp:
Notes from a discussion with @TIHan on possible implementation approaches, and also some discussion about FSHarp.Core.
General implementation approach:
Null checking is different to byref checking because AFAICS it also involves inference, and in particular inference that changes ("enhances" I suppose) the types reported across assembly boundaries.
presumably infers type
presumably infers type
The way we currently manage this is in type checking, so I don't think we can do it as a separate phase. Well, we could perhaps do it as a separate phase but we'd have to mutate a lot of the objects we've already created, and propagate the results around.
TAST TType adjustment?
So I think the way to approach it would be to put nullness info (and variables) into the TAST for types. This is, however, quite invasive on the codebase. For example we might start with this:
One basic problem is that
Another problem is the pickled info format in TastPickle.fs. We have to keep that format stable (keep reading old formats, don't produce incompatible new formats for DLLs that aren't compiled with the new switch) but still encode the information. We'll find some way to do that I suppose.
Filling in the NullnessInfo on import from .NET isn't that hard I suppose: if it's coming from a new-switch .NET DLL then fill in NotNull by default etc.
With all that in place, I suppose we could start to implement rules that actually interpret this information, e.g. in ConstraintSolver.fs and TypeRelations.fs. Basically if NotNull meets Null then emit a warning, and if Unknown meets either then mutate it to whichever. This has to be done with Undo in ConstraintSolver.fs, and there will be some other subtleties.
So you want to extend each case of TType with
Separately, I'll mention that there is another alternative to
which is to instead use a single new case
That may well work out more cleanly but we'd have to establish exactly where these can occur and carefully look for recursive descent cases where nullness is relevant.
FSharp.Core and F# metadata format
There is this also a problem about FSharp.Core - will it "use the new features" or not? That's really a major issue. If it doesnt, it won't be consumable by downlevel compilers. If it doesn't, does that effect things badly? e.g. will strings returned by String.replicate be assumed to be nullable? That would be so wrong.... Or will we have to manually annotate the whole API of FSharp.Core (I guess we will)
We can treat FSharp.Core as a separate design point. There are several meanings of "use the new features" and we need to clarify that. And there are several ground rules, such as FSharp.Core remains binary compatible, and preferably continues to have F# metadata consumable by downlevel compilers (part of what it means to be binary compatible in my eyes).
I think it will be more subtle than that. FSharp.Core may well be compiled with the feature enabled and get a null-checked interpretation when consumed by a new compiler. But it must get the existing interpretation when consumed by an old compiler. Exactly how we do that is TBD.
Yes, whatever it means to "annotate the metadata" can't intrude with the functioning of an old compiler.
At least, that's my position for now.
Do we need to store it in metadata? Well, it feels like we do. I mean this is type information and flows across assembly boundaries. But yes, simply emitting identical metadata for FSharp.Core would be ok, and perhaps with an associated extra metadata bloc with the nullness values. It's hard to rely on attributes as the code to correlate F# TAST stuff to .NET stuff would be really gnarly.
Yes. F#-to-F# is always mediated via the F#-specific metadata today
Perhaps I'm missing something, but isn't this how the annotations will work? That is, if a compiler does not understand what these are, it should just ignore them and see reference types as the same reference types it knows.
We'd need to verify this of course, but I would think that selective annotation accomplishes what we want.
Notes from meeting with C# team: