Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Nullness - downcasting and typetests should understand nullness information #17965

Merged
merged 15 commits into from
Nov 11, 2024
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 2 additions & 4 deletions src/Compiler/Checking/ConstraintSolver.fs
Original file line number Diff line number Diff line change
Expand Up @@ -2682,8 +2682,7 @@ and SolveTypeUseNotSupportsNull (csenv: ConstraintSolverEnv) ndeep m2 trace ty =
do! WarnD (ConstraintSolverNullnessWarning(FSComp.SR.csTypeHasNullAsTrueValue(NicePrint.minimalStringOfType denv ty), m, m2))
elif TypeNullIsExtraValueNew g m ty then
if g.checkNullness then
let denv = { denv with showNullnessAnnotations = Some true }
do! WarnD (ConstraintSolverNullnessWarning(FSComp.SR.csTypeHasNullAsExtraValue(NicePrint.minimalStringOfType denv ty), m, m2))
do! WarnD (ConstraintSolverNullnessWarning(FSComp.SR.csTypeHasNullAsExtraValue(NicePrint.minimalStringOfTypeWithNullness denv ty), m, m2))
else
match tryDestTyparTy g ty with
| ValueSome tp ->
Expand All @@ -2710,8 +2709,7 @@ and SolveNullnessNotSupportsNull (csenv: ConstraintSolverEnv) ndeep m2 (trace: O
| NullnessInfo.WithoutNull -> ()
| NullnessInfo.WithNull ->
if g.checkNullness && TypeNullIsExtraValueNew g m ty then
let denv = { denv with showNullnessAnnotations = Some true }
return! WarnD(ConstraintSolverNullnessWarning(FSComp.SR.csTypeHasNullAsExtraValue(NicePrint.minimalStringOfType denv ty), m, m2))
return! WarnD(ConstraintSolverNullnessWarning(FSComp.SR.csTypeHasNullAsExtraValue(NicePrint.minimalStringOfTypeWithNullness denv ty), m, m2))
}

and SolveTypeCanCarryNullness (csenv: ConstraintSolverEnv) ty nullness =
Expand Down
18 changes: 15 additions & 3 deletions src/Compiler/Checking/Expressions/CheckExpressions.fs
Original file line number Diff line number Diff line change
Expand Up @@ -2974,11 +2974,20 @@ let TcRuntimeTypeTest isCast isOperator (cenv: cenv) denv m tgtTy srcTy =
else
error(Error(FSComp.SR.tcTypeTestErased(NicePrint.minimalStringOfType denv tgtTy, NicePrint.minimalStringOfType denv (stripTyEqnsWrtErasure EraseAll g tgtTy)), m))
else
for ety in getErasedTypes g tgtTy true do
let checkTrgtNullness =
match (srcTy,g),(tgtTy,g) with
| (NullableRefType|NullTrueValue|NullableTypar), WithoutNullRefType when g.checkNullness && isCast ->
let srcNice = NicePrint.minimalStringOfTypeWithNullness denv srcTy
let tgtNice = NicePrint.minimalStringOfTypeWithNullness denv tgtTy
warning(Error(FSComp.SR.tcDowncastFromNullableToWithoutNull(srcNice,tgtNice,tgtNice), m))
false
| (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
| _ -> true
for ety in getErasedTypes g tgtTy checkTrgtNullness do
if isMeasureTy g ety then
warning(Error(FSComp.SR.tcTypeTestLosesMeasures(NicePrint.minimalStringOfType denv ety), m))
else
warning(Error(FSComp.SR.tcTypeTestLossy(NicePrint.minimalStringOfType denv ety, NicePrint.minimalStringOfType denv (stripTyEqnsWrtErasure EraseAll g ety)), m))
warning(Error(FSComp.SR.tcTypeTestLossy(NicePrint.minimalStringOfTypeWithNullness denv ety, NicePrint.minimalStringOfType denv (stripTyEqnsWrtErasure EraseAll g ety)), m))

/// Checks, warnings and constraint assertions for upcasts
let TcStaticUpcast (cenv: cenv) denv m tgtTy srcTy =
Expand Down Expand Up @@ -6108,7 +6117,10 @@ and TcExprDowncast (cenv: cenv) overallTy env tpenv (synExpr, synInnerExpr, m) =

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

and TcExprLazy (cenv: cenv) overallTy env tpenv (synInnerExpr, m) =
Expand Down
3 changes: 3 additions & 0 deletions src/Compiler/Checking/NicePrint.fs
Original file line number Diff line number Diff line change
Expand Up @@ -2954,3 +2954,6 @@ let minimalStringOfType denv ty =
let denv = suppressNullnessAnnotations denv
let denvMin = { denv with showInferenceTyparAnnotations=false; showStaticallyResolvedTyparAnnotations=false }
showL (PrintTypes.layoutTypeWithInfoAndPrec denvMin SimplifyTypes.typeSimplificationInfo0 2 ty)

let minimalStringOfTypeWithNullness denv ty =
minimalStringOfType {denv with showNullnessAnnotations = Some true} ty
2 changes: 2 additions & 0 deletions src/Compiler/Checking/NicePrint.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -173,3 +173,5 @@ val minimalStringsOfTwoValues:
denv: DisplayEnv -> infoReader: InfoReader -> vref1: ValRef -> vref2: ValRef -> string * string

val minimalStringOfType: denv: DisplayEnv -> ty: TType -> string

val minimalStringOfTypeWithNullness: denv: DisplayEnv -> ty: TType -> string
1 change: 1 addition & 0 deletions src/Compiler/FSComp.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1537,6 +1537,7 @@ tcPassingWithoutNullToNonNullAP,"You can remove this |Null|NonNull| pattern usag
tcPassingWithoutNullToNonNullQuickAP,"You can remove this |NonNullQuick| pattern usage."
tcPassingWithoutNullTononNullFunction,"You can remove this `nonNull` assertion."
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."
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."
3268,csNullNotNullConstraintInconsistent,"The constraints 'null' and 'not null' are inconsistent"
3271,tcNullnessCheckingNotEnabled,"The 'nullness checking' language feature is not enabled. This use of a nullness checking construct will be ignored."
csTypeHasNullAsTrueValue,"The type '%s' uses 'null' as a representation value but a non-null type is expected"
Expand Down
30 changes: 27 additions & 3 deletions src/Compiler/TypedTree/TypedTreeOps.fs
Original file line number Diff line number Diff line change
Expand Up @@ -9252,6 +9252,8 @@ let TypeNullNotLiked g m ty =
&& not (TypeNullIsTrueValue g ty)
&& not (TypeNullNever g ty)

/// a set of residual types that must also satisfy the constraint
T-Gro marked this conversation as resolved.
Show resolved Hide resolved
T-Gro marked this conversation as resolved.
Show resolved Hide resolved

let rec TypeHasDefaultValueAux isNew g m ty =
let ty = stripTyEqnsAndMeasureEqns g ty
(if isNew then TypeNullIsExtraValueNew g m ty else TypeNullIsExtraValue g m ty)
Expand Down Expand Up @@ -9318,15 +9320,37 @@ let (|SpecialEquatableHeadType|_|) g ty = (|SpecialComparableHeadType|_|) g ty
let (|SpecialNotEquatableHeadType|_|) g ty =
if isFunTy g ty then ValueSome() else ValueNone

let (|TyparTy|NullableTypar|StructTy|NullTrueValue|NullableRefType|WithoutNullRefType|UnresolvedRefType|) (ty,g) =
let sty = ty |> stripTyEqns g
if isTyparTy g sty then
if (nullnessOfTy g sty).TryEvaluate() = ValueSome NullnessInfo.WithNull then
NullableTypar
else
TyparTy
elif isStructTy g sty then
StructTy
elif TypeNullIsTrueValue g sty then
NullTrueValue
else
match (nullnessOfTy g sty).TryEvaluate() with
| ValueSome NullnessInfo.WithNull -> NullableRefType
| ValueSome NullnessInfo.WithoutNull -> WithoutNullRefType
| _ -> UnresolvedRefType

// Can we use the fast helper for the 'LanguagePrimitives.IntrinsicFunctions.TypeTestGeneric'?
let canUseTypeTestFast g ty =
not (isTyparTy g ty) &&
not (TypeNullIsTrueValue g ty)

// Can we use the fast helper for the 'LanguagePrimitives.IntrinsicFunctions.UnboxGeneric'?
let canUseUnboxFast g m ty =
not (isTyparTy g ty) &&
not (TypeNullNotLiked g m ty)
let canUseUnboxFast (g:TcGlobals) m ty =
if g.checkNullness then
match (ty,g) with
| TyparTy | WithoutNullRefType | UnresolvedRefType -> false
| StructTy | NullTrueValue | NullableRefType | NullableTypar -> true
else
not (isTyparTy g ty) &&
not (TypeNullNotLiked g m ty)

//--------------------------------------------------------------------------
// Nullness tests and pokes
Expand Down
3 changes: 3 additions & 0 deletions src/Compiler/TypedTree/TypedTreeOps.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -2608,6 +2608,9 @@ val (|SpecialEquatableHeadType|_|): TcGlobals -> TType -> TType list voption
[<return: Struct>]
val (|SpecialNotEquatableHeadType|_|): TcGlobals -> TType -> unit voption

val (|TyparTy|NullableTypar|StructTy|NullTrueValue|NullableRefType|WithoutNullRefType|UnresolvedRefType|):
TType * TcGlobals -> Choice<unit, unit, unit, unit, unit, unit, unit>

/// Matches if the given expression is an application
/// of the range or range-step operator on an integral type
/// and returns the type, start, step, and finish if so.
Expand Down
5 changes: 5 additions & 0 deletions src/Compiler/xlf/FSComp.txt.cs.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Compiler/xlf/FSComp.txt.de.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Compiler/xlf/FSComp.txt.es.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Compiler/xlf/FSComp.txt.fr.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Compiler/xlf/FSComp.txt.it.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Compiler/xlf/FSComp.txt.ja.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Compiler/xlf/FSComp.txt.ko.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Compiler/xlf/FSComp.txt.pl.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Compiler/xlf/FSComp.txt.pt-BR.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Compiler/xlf/FSComp.txt.ru.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading