Skip to content

Commit 935b796

Browse files
T-Grovzarytovskii
andauthored
Bugfix :: Nullness :: Add warnings for instantiations of (T|null) which are not allowed in F# (frequent at Deserialize from S.T.Json) (#18057)
* basic check * handle units of measure * fantomas * test expectation adjusted * Update src/Compiler/FSComp.txt Co-authored-by: Vlad Zarytovskii <[email protected]> * change wording --------- Co-authored-by: Vlad Zarytovskii <[email protected]>
1 parent df7ccb7 commit 935b796

20 files changed

+204
-7
lines changed

FSharp.sln

+9-6
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "TestTP", "tests\service\dat
4848
EndProject
4949
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Docs", "Docs", "{4E4F41D9-86A7-4F5D-B735-1A0744AB68AC}"
5050
ProjectSection(SolutionItems) = preProject
51+
docs\builder-caches.md = docs\builder-caches.md
52+
docs\changing-the-ast.md = docs\changing-the-ast.md
5153
docs\coding-standards.md = docs\coding-standards.md
5254
docs\compiler-startup-performance.md = docs\compiler-startup-performance.md
5355
docs\debug-emit.md = docs\debug-emit.md
@@ -57,12 +59,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Docs", "Docs", "{4E4F41D9-8
5759
docs\index.md = docs\index.md
5860
docs\large-inputs-and-stack-overflows.md = docs\large-inputs-and-stack-overflows.md
5961
docs\memory-usage.md = docs\memory-usage.md
60-
docs\optimizations.md = docs\optimizations.md
62+
docs\names.md = docs\names.md
6163
docs\optimizations-equality.md = docs\optimizations-equality.md
64+
docs\optimizations.md = docs\optimizations.md
6265
docs\overview.md = docs\overview.md
63-
docs\builder-caches.md = docs\builder-caches.md
64-
docs\changing-the-ast.md = docs\changing-the-ast.md
65-
docs\names.md = docs\names.md
6666
docs\perf-discussions-archive.md = docs\perf-discussions-archive.md
6767
docs\project-builds.md = docs\project-builds.md
6868
docs\representations.md = docs\representations.md
@@ -84,8 +84,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "fcs", "fcs", "{B86EBFF1-E03
8484
docs\fcs\symbols.fsx = docs\fcs\symbols.fsx
8585
docs\fcs\tokenizer.fsx = docs\fcs\tokenizer.fsx
8686
docs\fcs\typedtree.fsx = docs\fcs\typedtree.fsx
87-
docs\fcs\untypedtree.fsx = docs\fcs\untypedtree.fsx
8887
docs\fcs\untypedtree-apis.fsx = docs\fcs\untypedtree-apis.fsx
88+
docs\fcs\untypedtree.fsx = docs\fcs\untypedtree.fsx
8989
EndProjectSection
9090
EndProject
9191
Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "fsc", "src\fsc\fscProject\fsc.fsproj", "{10D15DBB-EFF0-428C-BA83-41600A93EEC4}"
@@ -142,6 +142,9 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".FSharp.Compiler.Service",
142142
docs\release-notes\.FSharp.Compiler.Service\8.0.200.md = docs\release-notes\.FSharp.Compiler.Service\8.0.200.md
143143
docs\release-notes\.FSharp.Compiler.Service\8.0.202.md = docs\release-notes\.FSharp.Compiler.Service\8.0.202.md
144144
docs\release-notes\.FSharp.Compiler.Service\8.0.300.md = docs\release-notes\.FSharp.Compiler.Service\8.0.300.md
145+
docs\release-notes\.FSharp.Compiler.Service\8.0.400.md = docs\release-notes\.FSharp.Compiler.Service\8.0.400.md
146+
docs\release-notes\.FSharp.Compiler.Service\9.0.100.md = docs\release-notes\.FSharp.Compiler.Service\9.0.100.md
147+
docs\release-notes\.FSharp.Compiler.Service\9.0.200.md = docs\release-notes\.FSharp.Compiler.Service\9.0.200.md
145148
EndProjectSection
146149
EndProject
147150
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".FSharp.Core", ".FSharp.Core", "{23798638-A1E9-4DAE-9C9C-F5D87499ADD6}"
@@ -158,8 +161,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".Language", ".Language", "{
158161
EndProject
159162
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".VisualStudio", ".VisualStudio", "{AF70EC5A-8E7C-4FDA-857D-AF08082CFC64}"
160163
ProjectSection(SolutionItems) = preProject
161-
docs\release-notes\.VisualStudio\17.9.md = docs\release-notes\.VisualStudio\17.9.md
162164
docs\release-notes\.VisualStudio\17.10.md = docs\release-notes\.VisualStudio\17.10.md
165+
docs\release-notes\.VisualStudio\17.9.md = docs\release-notes\.VisualStudio\17.9.md
163166
EndProjectSection
164167
EndProject
165168
Global

docs/release-notes/.FSharp.Compiler.Service/9.0.200.md

+2
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,15 @@
1515
* Fix locals allocating for the special `copyOfStruct` defensive copy ([PR #18025](https://github.com/dotnet/fsharp/pull/18025))
1616
* Fix lowering of computed array expressions when the expression consists of a simple mapping from a `uint64` or `unativeint` array. [PR #18081](https://github.com/dotnet/fsharp/pull/18081)
1717

18+
1819
### Added
1920

2021
* Let `dotnet fsi --help` print a link to the documentation website. ([PR #18006](https://github.com/dotnet/fsharp/pull/18006))
2122
* Deprecate places where `seq` can be omitted. ([Language suggestion #1033](https://github.com/fsharp/fslang-suggestions/issues/1033), [PR #17772](https://github.com/dotnet/fsharp/pull/17772))
2223
* Support literal attribute on decimals ([PR #17769](https://github.com/dotnet/fsharp/pull/17769))
2324
* Added type conversions cache, only enabled for compiler runs, guarded by language version preview ([PR #17668](https://github.com/dotnet/fsharp/pull/17668))
2425
* Added project property ParallelCompilation which turns on graph based type checking, parallel ILXGen and parallel optimization. By default on for users of langversion=preview ([PR #17948](https://github.com/dotnet/fsharp/pull/17948))
26+
* Adding warning when consuming generic method returning T|null for types not supporting nullness (structs,anons,tuples) ([PR #18057](https://github.com/dotnet/fsharp/pull/18057))
2527

2628
### Changed
2729

src/Compiler/Checking/Expressions/CheckExpressions.fs

+4-1
Original file line numberDiff line numberDiff line change
@@ -9728,10 +9728,13 @@ and TcMethodApplicationThen
97289728
let (CallerNamedArg(id, _)) = List.head attributeAssignedNamedItems
97299729
errorR(Error(FSComp.SR.tcNamedArgumentDidNotMatch(id.idText), id.idRange))
97309730

9731-
97329731
// Resolve the "delayed" lookups
97339732
let exprTy = (tyOfExpr g expr)
97349733

9734+
for problematicTy in GetDisallowedNullness g exprTy do
9735+
let denv = env.DisplayEnv
9736+
warning(Error(FSComp.SR.tcDisallowedNullableApplication(methodName,NicePrint.minimalStringOfType denv problematicTy), m))
9737+
97359738
PropagateThenTcDelayed cenv overallTy env tpenv mWholeExpr (MakeApplicableExprNoFlex cenv expr) exprTy atomicFlag delayed
97369739

97379740
/// Infer initial type information at the callsite from the syntax of an argument, prior to overload resolution.

src/Compiler/FSComp.txt

+1
Original file line numberDiff line numberDiff line change
@@ -1538,6 +1538,7 @@ tcPassingWithoutNullToNonNullQuickAP,"You can remove this |NonNullQuick| pattern
15381538
tcPassingWithoutNullTononNullFunction,"You can remove this `nonNull` assertion."
15391539
3263,tcNullableToStringOverride,"With nullness checking enabled, overrides of .ToString() method must return a non-nullable string. You can handle potential nulls via the built-in string function."
15401540
3264,tcDowncastFromNullableToWithoutNull,"Nullness warning: Downcasting from '%s' into '%s' can introduce unexpected null values. Cast to '%s|null' instead or handle the null before downcasting."
1541+
3265,tcDisallowedNullableApplication,"Application of method '%s' attempted to create a nullable type ('T | null) for '%s'. Nullness warnings won't be reported correctly for such types."
15411542
3268,csNullNotNullConstraintInconsistent,"The constraints 'null' and 'not null' are inconsistent"
15421543
3271,tcNullnessCheckingNotEnabled,"The 'nullness checking' language feature is not enabled. This use of a nullness checking construct will be ignored."
15431544
csTypeHasNullAsTrueValue,"The type '%s' uses 'null' as a representation value but a non-null type is expected"

src/Compiler/TypedTree/TypedTreeOps.fs

+51
Original file line numberDiff line numberDiff line change
@@ -9215,6 +9215,57 @@ let reqTyForArgumentNullnessInference g actualTy reqTy =
92159215
changeWithNullReqTyToVariable g reqTy
92169216
| _ -> reqTy
92179217

9218+
9219+
let GetDisallowedNullness (g:TcGlobals) (ty:TType) =
9220+
if g.checkNullness then
9221+
let rec hasWithNullAnyWhere ty alreadyWrappedInOuterWithNull =
9222+
match ty with
9223+
| TType_var (tp, n) ->
9224+
let withNull = alreadyWrappedInOuterWithNull || n.TryEvaluate() = (ValueSome NullnessInfo.WithNull)
9225+
match tp.Solution with
9226+
| None -> []
9227+
| Some t -> hasWithNullAnyWhere t withNull
9228+
9229+
| TType_app (tcr, tinst, nullnessOrig) ->
9230+
let tyArgs = tinst |> List.collect (fun t -> hasWithNullAnyWhere t false)
9231+
9232+
match alreadyWrappedInOuterWithNull, tcr.TypeAbbrev with
9233+
| true, _ when isStructTyconRef tcr -> ty :: tyArgs
9234+
| true, _ when tcr.IsMeasureableReprTycon ->
9235+
match tcr.TypeReprInfo with
9236+
| TMeasureableRepr realType ->
9237+
if hasWithNullAnyWhere realType true |> List.isEmpty then
9238+
[]
9239+
else [ty]
9240+
| _ -> []
9241+
| true, Some tAbbrev -> (hasWithNullAnyWhere tAbbrev true) @ tyArgs
9242+
| _ -> tyArgs
9243+
9244+
| TType_tuple (_,tupTypes) ->
9245+
let inner = tupTypes |> List.collect (fun t -> hasWithNullAnyWhere t false)
9246+
if alreadyWrappedInOuterWithNull then ty :: inner else inner
9247+
9248+
| TType_anon (anon,tys) ->
9249+
let inner = tys |> List.collect (fun t -> hasWithNullAnyWhere t false)
9250+
if alreadyWrappedInOuterWithNull then ty :: inner else inner
9251+
| TType_fun (d, r, _) ->
9252+
(hasWithNullAnyWhere d false) @ (hasWithNullAnyWhere r false)
9253+
9254+
| TType_forall _ -> []
9255+
| TType_ucase _ -> []
9256+
| TType_measure m ->
9257+
if alreadyWrappedInOuterWithNull then
9258+
let measuresInside =
9259+
ListMeasureVarOccs m
9260+
|> List.choose (fun x -> x.Solution)
9261+
|> List.collect (fun x -> hasWithNullAnyWhere x true)
9262+
ty :: measuresInside
9263+
else []
9264+
9265+
hasWithNullAnyWhere ty false
9266+
else
9267+
[]
9268+
92189269
let TypeHasAllowNull (tcref:TyconRef) g m =
92199270
not tcref.IsStructOrEnumTycon &&
92209271
not (isByrefLikeTyconRef g m tcref) &&

src/Compiler/TypedTree/TypedTreeOps.fsi

+5
Original file line numberDiff line numberDiff line change
@@ -1815,6 +1815,11 @@ val TypeNullIsTrueValue: TcGlobals -> TType -> bool
18151815

18161816
val TypeNullIsExtraValue: TcGlobals -> range -> TType -> bool
18171817

1818+
/// A type coming via interop from C# can be holding a nullness combination not supported in F#.
1819+
/// Prime example are APIs marked as T|null applied to structs, tuples and anons.
1820+
/// Unsupported values can also be nested within generic type arguments, e.g. a List<Tuple<string,T|null>> applied to an anon.
1821+
val GetDisallowedNullness: TcGlobals -> TType -> TType list
1822+
18181823
val TypeHasAllowNull: TyconRef -> TcGlobals -> range -> bool
18191824

18201825
val TypeNullIsExtraValueNew: TcGlobals -> range -> TType -> bool

src/Compiler/xlf/FSComp.txt.cs.xlf

+5
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Compiler/xlf/FSComp.txt.de.xlf

+5
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Compiler/xlf/FSComp.txt.es.xlf

+5
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Compiler/xlf/FSComp.txt.fr.xlf

+5
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Compiler/xlf/FSComp.txt.it.xlf

+5
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Compiler/xlf/FSComp.txt.ja.xlf

+5
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Compiler/xlf/FSComp.txt.ko.xlf

+5
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Compiler/xlf/FSComp.txt.pl.xlf

+5
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)