Skip to content

Commit f44e2b2

Browse files
authored
Nullness - downcasting and typetests should understand nullness information (#17965)
1 parent 8a4c053 commit f44e2b2

37 files changed

+1189
-34
lines changed

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

+1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
* Fix concurrency issue in `ILPreTypeDefImpl` ([PR #17812](https://github.com/dotnet/fsharp/pull/17812))
1010
* Fix nullness inference for member val and other OO scenarios ([PR #17845](https://github.com/dotnet/fsharp/pull/17845))
1111
* Fix internal error when analyzing incomplete inherit member ([PR #17905](https://github.com/dotnet/fsharp/pull/17905))
12+
* Add warning when downcasting from nullable type to non-nullable ([PR #17965](https://github.com/dotnet/fsharp/pull/17965))
1213
* Fix missing nullness warning in case of method resolution multiple candidates ([PR #17917](https://github.com/dotnet/fsharp/pull/17918))
1314
* Fix failure to use bound values in `when` clauses of `try-with` in `seq` expressions ([# 17990](https://github.com/dotnet/fsharp/pull/17990))
1415

src/Compiler/AbstractIL/il.fs

+4-4
Original file line numberDiff line numberDiff line change
@@ -481,11 +481,11 @@ type ILAssemblyRef(data) =
481481
override x.GetHashCode() = uniqueStamp
482482

483483
override x.Equals yobj =
484-
((yobj :?> ILAssemblyRef).UniqueStamp = uniqueStamp)
484+
((!!yobj :?> ILAssemblyRef).UniqueStamp = uniqueStamp)
485485

486486
interface IComparable with
487487
override x.CompareTo yobj =
488-
compare (yobj :?> ILAssemblyRef).UniqueStamp uniqueStamp
488+
compare (!!yobj :?> ILAssemblyRef).UniqueStamp uniqueStamp
489489

490490
static member Create(name, hash, publicKey, retargetable, version, locale) =
491491
ILAssemblyRef
@@ -750,7 +750,7 @@ type ILTypeRef =
750750
override x.GetHashCode() = x.hashCode
751751

752752
override x.Equals yobj =
753-
let y = (yobj :?> ILTypeRef)
753+
let y = (!!yobj :?> ILTypeRef)
754754

755755
(x.ApproxId = y.ApproxId)
756756
&& (x.Scope = y.Scope)
@@ -793,7 +793,7 @@ type ILTypeRef =
793793
interface IComparable with
794794

795795
override x.CompareTo yobj =
796-
let y = (yobj :?> ILTypeRef)
796+
let y = (!!yobj :?> ILTypeRef)
797797
let c = compare x.ApproxId y.ApproxId
798798

799799
if c <> 0 then

src/Compiler/AbstractIL/ilreflect.fs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1834,7 +1834,7 @@ let rec buildMethodPass2 cenv tref (typB: TypeBuilder) emEnv (mdef: ILMethodDef)
18341834
let methB =
18351835
System.Diagnostics.Debug.Assert(not (isNull definePInvokeMethod), "Runtime does not have DefinePInvokeMethod") // Absolutely can't happen
18361836

1837-
(!!definePInvokeMethod)
1837+
!!(!!definePInvokeMethod)
18381838
.Invoke(
18391839
typB,
18401840
[|

src/Compiler/Checking/ConstraintSolver.fs

+2-4
Original file line numberDiff line numberDiff line change
@@ -2681,8 +2681,7 @@ and SolveTypeUseNotSupportsNull (csenv: ConstraintSolverEnv) ndeep m2 trace ty =
26812681
do! WarnD (ConstraintSolverNullnessWarning(FSComp.SR.csTypeHasNullAsTrueValue(NicePrint.minimalStringOfType denv ty), m, m2))
26822682
elif TypeNullIsExtraValueNew g m ty then
26832683
if g.checkNullness then
2684-
let denv = { denv with showNullnessAnnotations = Some true }
2685-
do! WarnD (ConstraintSolverNullnessWarning(FSComp.SR.csTypeHasNullAsExtraValue(NicePrint.minimalStringOfType denv ty), m, m2))
2684+
do! WarnD (ConstraintSolverNullnessWarning(FSComp.SR.csTypeHasNullAsExtraValue(NicePrint.minimalStringOfTypeWithNullness denv ty), m, m2))
26862685
else
26872686
match tryDestTyparTy g ty with
26882687
| ValueSome tp ->
@@ -2709,8 +2708,7 @@ and SolveNullnessNotSupportsNull (csenv: ConstraintSolverEnv) ndeep m2 (trace: O
27092708
| NullnessInfo.WithoutNull -> ()
27102709
| NullnessInfo.WithNull ->
27112710
if g.checkNullness && TypeNullIsExtraValueNew g m ty then
2712-
let denv = { denv with showNullnessAnnotations = Some true }
2713-
return! WarnD(ConstraintSolverNullnessWarning(FSComp.SR.csTypeHasNullAsExtraValue(NicePrint.minimalStringOfType denv ty), m, m2))
2711+
return! WarnD(ConstraintSolverNullnessWarning(FSComp.SR.csTypeHasNullAsExtraValue(NicePrint.minimalStringOfTypeWithNullness denv ty), m, m2))
27142712
}
27152713

27162714
and SolveTypeCanCarryNullness (csenv: ConstraintSolverEnv) ty nullness =

src/Compiler/Checking/Expressions/CheckExpressions.fs

+15-3
Original file line numberDiff line numberDiff line change
@@ -2984,11 +2984,20 @@ let TcRuntimeTypeTest isCast isOperator (cenv: cenv) denv m tgtTy srcTy =
29842984
else
29852985
error(Error(FSComp.SR.tcTypeTestErased(NicePrint.minimalStringOfType denv tgtTy, NicePrint.minimalStringOfType denv (stripTyEqnsWrtErasure EraseAll g tgtTy)), m))
29862986
else
2987-
for ety in getErasedTypes g tgtTy true do
2987+
let checkTrgtNullness =
2988+
match (srcTy,g),(tgtTy,g) with
2989+
| (NullableRefType|NullTrueValue|NullableTypar), WithoutNullRefType when g.checkNullness && isCast ->
2990+
let srcNice = NicePrint.minimalStringOfTypeWithNullness denv srcTy
2991+
let tgtNice = NicePrint.minimalStringOfTypeWithNullness denv tgtTy
2992+
warning(Error(FSComp.SR.tcDowncastFromNullableToWithoutNull(srcNice,tgtNice,tgtNice), m))
2993+
false
2994+
| (NullableRefType|NullTrueValue|NullableTypar), (NullableRefType|NullTrueValue|NullableTypar) -> not isCast //a type test (unlike type cast) will never return true for null in the source, therefore adding |null to target does not help => keep the erasure warning
2995+
| _ -> true
2996+
for ety in getErasedTypes g tgtTy checkTrgtNullness do
29882997
if isMeasureTy g ety then
29892998
warning(Error(FSComp.SR.tcTypeTestLosesMeasures(NicePrint.minimalStringOfType denv ety), m))
29902999
else
2991-
warning(Error(FSComp.SR.tcTypeTestLossy(NicePrint.minimalStringOfType denv ety, NicePrint.minimalStringOfType denv (stripTyEqnsWrtErasure EraseAll g ety)), m))
3000+
warning(Error(FSComp.SR.tcTypeTestLossy(NicePrint.minimalStringOfTypeWithNullness denv ety, NicePrint.minimalStringOfType denv (stripTyEqnsWrtErasure EraseAll g ety)), m))
29923001

29933002
/// Checks, warnings and constraint assertions for upcasts
29943003
let TcStaticUpcast (cenv: cenv) denv m tgtTy srcTy =
@@ -6118,7 +6127,10 @@ and TcExprDowncast (cenv: cenv) overallTy env tpenv (synExpr, synInnerExpr, m) =
61186127

61196128
// TcRuntimeTypeTest ensures tgtTy is a nominal type. Hence we can insert a check here
61206129
// based on the nullness semantics of the nominal type.
6121-
let expr = mkCallUnbox g m tgtTy innerExpr
6130+
let expr =
6131+
match (tgtTy,g) with
6132+
| NullTrueValue | NullableRefType | NullableTypar when g.checkNullness -> mkCallUnboxFast g m tgtTy innerExpr
6133+
| _ -> mkCallUnbox g m tgtTy innerExpr
61226134
expr, tpenv
61236135

61246136
and TcExprLazy (cenv: cenv) overallTy env tpenv (synInnerExpr, m) =

src/Compiler/Checking/MethodCalls.fs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1771,7 +1771,7 @@ module ProvidedMethodCalls =
17711771
| _ when typeEquiv g normTy g.float32_ty -> Const.Single(v :?> float32)
17721772
| _ when typeEquiv g normTy g.float_ty -> Const.Double(v :?> float)
17731773
| _ when typeEquiv g normTy g.char_ty -> Const.Char(v :?> char)
1774-
| _ when typeEquiv g normTy g.string_ty -> Const.String(v :?> string)
1774+
| _ when typeEquiv g normTy g.string_ty -> Const.String(!!v :?> string)
17751775
| _ when typeEquiv g normTy g.decimal_ty -> Const.Decimal(v :?> decimal)
17761776
| _ when typeEquiv g normTy g.unit_ty -> Const.Unit
17771777
| _ -> fail()

src/Compiler/Checking/NicePrint.fs

+3
Original file line numberDiff line numberDiff line change
@@ -2954,3 +2954,6 @@ let minimalStringOfType denv ty =
29542954
let denv = suppressNullnessAnnotations denv
29552955
let denvMin = { denv with showInferenceTyparAnnotations=false; showStaticallyResolvedTyparAnnotations=false }
29562956
showL (PrintTypes.layoutTypeWithInfoAndPrec denvMin SimplifyTypes.typeSimplificationInfo0 2 ty)
2957+
2958+
let minimalStringOfTypeWithNullness denv ty =
2959+
minimalStringOfType {denv with showNullnessAnnotations = Some true} ty

src/Compiler/Checking/NicePrint.fsi

+2
Original file line numberDiff line numberDiff line change
@@ -173,3 +173,5 @@ val minimalStringsOfTwoValues:
173173
denv: DisplayEnv -> infoReader: InfoReader -> vref1: ValRef -> vref2: ValRef -> string * string
174174

175175
val minimalStringOfType: denv: DisplayEnv -> ty: TType -> string
176+
177+
val minimalStringOfTypeWithNullness: denv: DisplayEnv -> ty: TType -> string

src/Compiler/DependencyManager/AssemblyResolveHandler.fs

+1-1
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ type AssemblyResolveHandlerCoreclr(assemblyProbingPaths: AssemblyResolutionProbe
4141

4242
member _.ResolveAssemblyNetStandard (ctxt: 'T) (assemblyName: AssemblyName) : Assembly =
4343
let loadAssembly path =
44-
loadFromAssemblyPathMethod.Invoke(ctxt, [| path |]) :?> Assembly
44+
!! loadFromAssemblyPathMethod.Invoke(ctxt, [| path |]) :?> Assembly
4545

4646
let assemblyPaths =
4747
match assemblyProbingPaths with

src/Compiler/DependencyManager/DependencyProvider.fs

+8-8
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ type ReflectionDependencyManagerProvider
168168
let keyProperty (x: objnull) = x |> keyProperty.GetValue |> string
169169

170170
let helpMessagesProperty (x: objnull) =
171-
let toStringArray (o: objnull) = o :?> string[]
171+
let toStringArray (o: objnull) = !!o :?> string[]
172172

173173
match helpMessagesProperty with
174174
| Some helpMessagesProperty -> x |> helpMessagesProperty.GetValue |> toStringArray
@@ -334,31 +334,31 @@ type ReflectionDependencyManagerProvider
334334
member _.StdOut =
335335
match getInstanceProperty<string[]> (result.GetType()) "StdOut" with
336336
| None -> [||]
337-
| Some p -> p.GetValue(result) :?> string[]
337+
| Some p -> !! p.GetValue(result) :?> string[]
338338

339339
/// The resolution error log (* process stderror *)
340340
member _.StdError =
341341
match getInstanceProperty<string[]> (result.GetType()) "StdError" with
342342
| None -> [||]
343-
| Some p -> p.GetValue(result) :?> string[]
343+
| Some p -> !! p.GetValue(result) :?> string[]
344344

345345
/// The resolution paths
346346
member _.Resolutions =
347347
match getInstanceProperty<seq<string>> (result.GetType()) "Resolutions" with
348348
| None -> Seq.empty<string>
349-
| Some p -> p.GetValue(result) :?> seq<string>
349+
| Some p -> !! p.GetValue(result) :?> seq<string>
350350

351351
/// The source code file paths
352352
member _.SourceFiles =
353353
match getInstanceProperty<seq<string>> (result.GetType()) "SourceFiles" with
354354
| None -> Seq.empty<string>
355-
| Some p -> p.GetValue(result) :?> seq<string>
355+
| Some p -> !! p.GetValue(result) :?> seq<string>
356356

357357
/// The roots to package directories
358358
member _.Roots =
359359
match getInstanceProperty<seq<string>> (result.GetType()) "Roots" with
360360
| None -> Seq.empty<string>
361-
| Some p -> p.GetValue(result) :?> seq<string>
361+
| Some p -> !! p.GetValue(result) :?> seq<string>
362362
}
363363

364364
static member MakeResultFromFields
@@ -473,8 +473,8 @@ type ReflectionDependencyManagerProvider
473473
match tupleFields |> Array.length with
474474
| 3 ->
475475
tupleFields[0] :?> bool,
476-
tupleFields[1] :?> string list |> List.toSeq,
477-
tupleFields[2] :?> string list |> List.distinct |> List.toSeq
476+
!!tupleFields[1] :?> string list |> List.toSeq,
477+
!!tupleFields[2] :?> string list |> List.distinct |> List.toSeq
478478
| _ -> false, seqEmpty, seqEmpty
479479

480480
ReflectionDependencyManagerProvider.MakeResultFromFields(success, [||], [||], Seq.empty, sourceFiles, packageRoots)

src/Compiler/FSComp.txt

+1
Original file line numberDiff line numberDiff line change
@@ -1537,6 +1537,7 @@ tcPassingWithoutNullToNonNullAP,"You can remove this |Null|NonNull| pattern usag
15371537
tcPassingWithoutNullToNonNullQuickAP,"You can remove this |NonNullQuick| pattern usage."
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."
1540+
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."
15401541
3268,csNullNotNullConstraintInconsistent,"The constraints 'null' and 'not null' are inconsistent"
15411542
3271,tcNullnessCheckingNotEnabled,"The 'nullness checking' language feature is not enabled. This use of a nullness checking construct will be ignored."
15421543
csTypeHasNullAsTrueValue,"The type '%s' uses 'null' as a representation value but a non-null type is expected"

src/Compiler/Interactive/fsi.fs

+2-2
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ module internal Utilities =
122122
}
123123
else
124124
let specialized = typedefof<AnyToLayoutSpecialization<_>>.MakeGenericType [| ty |]
125-
Activator.CreateInstance(specialized) :?> IAnyToLayoutCall
125+
!! Activator.CreateInstance(specialized) :?> IAnyToLayoutCall
126126

127127
let callStaticMethod (ty: Type) name args =
128128
ty.InvokeMember(
@@ -4762,7 +4762,7 @@ type FsiEvaluationSession
47624762
let makeNestedException (userExn: #Exception) =
47634763
// clone userExn -- make userExn the inner exception, to retain the stacktrace on raise
47644764
let arguments = [| userExn.Message :> obj; userExn :> obj |]
4765-
Activator.CreateInstance(userExn.GetType(), arguments) :?> Exception
4765+
!! Activator.CreateInstance(userExn.GetType(), arguments) :?> Exception
47664766

47674767
let commitResult res =
47684768
match res with

src/Compiler/TypedTree/TypeProviders.fs

+3-3
Original file line numberDiff line numberDiff line change
@@ -141,10 +141,10 @@ let CreateTypeProvider (
141141
IsHostedExecution= isInteractive,
142142
SystemRuntimeAssemblyVersion = systemRuntimeAssemblyVersion)
143143
#endif
144-
protect (fun () -> Activator.CreateInstance(typeProviderImplementationType, [| box e|]) :?> ITypeProvider )
144+
protect (fun () -> !!(Activator.CreateInstance(typeProviderImplementationType, [| box e|])) :?> ITypeProvider )
145145

146146
elif not(isNull(typeProviderImplementationType.GetConstructor [| |])) then
147-
protect (fun () -> Activator.CreateInstance typeProviderImplementationType :?> ITypeProvider )
147+
protect (fun () -> !!(Activator.CreateInstance typeProviderImplementationType) :?> ITypeProvider )
148148

149149
else
150150
// No appropriate constructor found
@@ -739,7 +739,7 @@ type ProvidedMethodBase (x: MethodBase, ctxt) =
739739
let paramsAsObj =
740740
try (!!meth).Invoke(provider, bindingFlags ||| BindingFlags.InvokeMethod, null, [| box x |], null)
741741
with err -> raise (StripException (StripException err))
742-
paramsAsObj :?> ParameterInfo[]
742+
!!paramsAsObj :?> ParameterInfo[]
743743

744744
staticParams |> ProvidedParameterInfo.CreateArrayNonNull ctxt
745745

src/Compiler/TypedTree/TypedTreeOps.fs

+26-3
Original file line numberDiff line numberDiff line change
@@ -9252,6 +9252,7 @@ let TypeNullNotLiked g m ty =
92529252
&& not (TypeNullIsTrueValue g ty)
92539253
&& not (TypeNullNever g ty)
92549254

9255+
92559256
let rec TypeHasDefaultValueAux isNew g m ty =
92569257
let ty = stripTyEqnsAndMeasureEqns g ty
92579258
(if isNew then TypeNullIsExtraValueNew g m ty else TypeNullIsExtraValue g m ty)
@@ -9318,15 +9319,37 @@ let (|SpecialEquatableHeadType|_|) g ty = (|SpecialComparableHeadType|_|) g ty
93189319
let (|SpecialNotEquatableHeadType|_|) g ty =
93199320
if isFunTy g ty then ValueSome() else ValueNone
93209321

9322+
let (|TyparTy|NullableTypar|StructTy|NullTrueValue|NullableRefType|WithoutNullRefType|UnresolvedRefType|) (ty,g) =
9323+
let sty = ty |> stripTyEqns g
9324+
if isTyparTy g sty then
9325+
if (nullnessOfTy g sty).TryEvaluate() = ValueSome NullnessInfo.WithNull then
9326+
NullableTypar
9327+
else
9328+
TyparTy
9329+
elif isStructTy g sty then
9330+
StructTy
9331+
elif TypeNullIsTrueValue g sty then
9332+
NullTrueValue
9333+
else
9334+
match (nullnessOfTy g sty).TryEvaluate() with
9335+
| ValueSome NullnessInfo.WithNull -> NullableRefType
9336+
| ValueSome NullnessInfo.WithoutNull -> WithoutNullRefType
9337+
| _ -> UnresolvedRefType
9338+
93219339
// Can we use the fast helper for the 'LanguagePrimitives.IntrinsicFunctions.TypeTestGeneric'?
93229340
let canUseTypeTestFast g ty =
93239341
not (isTyparTy g ty) &&
93249342
not (TypeNullIsTrueValue g ty)
93259343

93269344
// Can we use the fast helper for the 'LanguagePrimitives.IntrinsicFunctions.UnboxGeneric'?
9327-
let canUseUnboxFast g m ty =
9328-
not (isTyparTy g ty) &&
9329-
not (TypeNullNotLiked g m ty)
9345+
let canUseUnboxFast (g:TcGlobals) m ty =
9346+
if g.checkNullness then
9347+
match (ty,g) with
9348+
| TyparTy | WithoutNullRefType | UnresolvedRefType -> false
9349+
| StructTy | NullTrueValue | NullableRefType | NullableTypar -> true
9350+
else
9351+
not (isTyparTy g ty) &&
9352+
not (TypeNullNotLiked g m ty)
93309353

93319354
//--------------------------------------------------------------------------
93329355
// Nullness tests and pokes

src/Compiler/TypedTree/TypedTreeOps.fsi

+3
Original file line numberDiff line numberDiff line change
@@ -2608,6 +2608,9 @@ val (|SpecialEquatableHeadType|_|): TcGlobals -> TType -> TType list voption
26082608
[<return: Struct>]
26092609
val (|SpecialNotEquatableHeadType|_|): TcGlobals -> TType -> unit voption
26102610

2611+
val (|TyparTy|NullableTypar|StructTy|NullTrueValue|NullableRefType|WithoutNullRefType|UnresolvedRefType|):
2612+
TType * TcGlobals -> Choice<unit, unit, unit, unit, unit, unit, unit>
2613+
26112614
/// Matches if the given expression is an application
26122615
/// of the range or range-step operator on an integral type
26132616
/// and returns the type, start, step, and finish if so.

src/Compiler/Utilities/TaggedCollections.fs

+4-4
Original file line numberDiff line numberDiff line change
@@ -667,8 +667,8 @@ type internal Set<'T, 'ComparerTag> when 'ComparerTag :> IComparer<'T>(comparer:
667667
interface System.IComparable with
668668
// Cast s2 to the exact same type as s1, see 4884.
669669
// It is not OK to cast s2 to seq<'T>, since different compares could permute the elements.
670-
member s1.CompareTo(s2: obj) =
671-
SetTree.compare s1.Comparer s1.Tree (s2 :?> Set<'T, 'ComparerTag>).Tree
670+
member s1.CompareTo(s2: objnull) =
671+
SetTree.compare s1.Comparer s1.Tree (!!s2 :?> Set<'T, 'ComparerTag>).Tree
672672

673673
member this.ComputeHashCode() =
674674
let combineHash x y = (x <<< 1) + y + 631
@@ -1239,7 +1239,7 @@ type internal Map<'Key, 'T, 'ComparerTag> when 'ComparerTag :> IComparer<'Key>(c
12391239
| _ -> false
12401240

12411241
interface System.IComparable with
1242-
member m1.CompareTo(m2: obj) =
1242+
member m1.CompareTo(m2: objnull) =
12431243
Seq.compareWith
12441244
(fun (kvp1: KeyValuePair<_, _>) (kvp2: KeyValuePair<_, _>) ->
12451245
let c = m1.Comparer.Compare(kvp1.Key, kvp2.Key) in
@@ -1251,7 +1251,7 @@ type internal Map<'Key, 'T, 'ComparerTag> when 'ComparerTag :> IComparer<'Key>(c
12511251
// Cast m2 to the exact same type as m1, see 4884.
12521252
// It is not OK to cast m2 to seq<KeyValuePair<'Key,'T>>, since different compares could permute the KVPs.
12531253
m1
1254-
(m2 :?> Map<'Key, 'T, 'ComparerTag>)
1254+
(!!m2 :?> Map<'Key, 'T, 'ComparerTag>)
12551255

12561256
member this.ComputeHashCode() =
12571257
let combineHash x y = (x <<< 1) + y + 631

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.

0 commit comments

Comments
 (0)