Skip to content

Commit

Permalink
Nullness :: Format string %s should allow nullable string (#16656)
Browse files Browse the repository at this point in the history
  • Loading branch information
T-Gro authored Feb 6, 2024
1 parent cd662eb commit bca1c53
Show file tree
Hide file tree
Showing 4 changed files with 82 additions and 4 deletions.
3 changes: 2 additions & 1 deletion src/Compiler/Checking/CheckFormatStrings.fs
Original file line number Diff line number Diff line change
Expand Up @@ -449,7 +449,8 @@ let parseFormatStringInternal
checkOtherFlags ch
collectSpecifierLocation fragLine fragCol 1
let i = skipPossibleInterpolationHole (i+1)
parseLoop ((posi, g.string_ty) :: acc) (i, fragLine, fragCol+1) fragments
let stringTy = if g.checkNullness && g.langFeatureNullness then g.string_ty_withNull else g.string_ty
parseLoop ((posi, stringTy) :: acc) (i, fragLine, fragCol+1) fragments

| 'O' ->
checkOtherFlags ch
Expand Down
2 changes: 2 additions & 0 deletions src/Compiler/TypedTree/TcGlobals.fs
Original file line number Diff line number Diff line change
Expand Up @@ -447,6 +447,7 @@ type TcGlobals(
let v_FormattableStringFactory_tcref = findSysTyconRef sysCompilerServices "FormattableStringFactory"
let v_FormattableStringFactory_ty = mkNonGenericTy v_FormattableStringFactory_tcref
let v_string_ty = mkNonGenericTy v_string_tcr
let v_string_ty_withNull = mkNonGenericTyWithNullness v_string_tcr KnownWithNull
let v_decimal_ty = mkSysNonGenericTy sys "Decimal"
let v_unit_ty = mkNonGenericTy v_unit_tcr_nice
let v_system_Type_ty = mkSysNonGenericTy sys "Type"
Expand Down Expand Up @@ -1336,6 +1337,7 @@ type TcGlobals(
member _.bool_ty = v_bool_ty
member _.int_ty = v_int_ty
member _.string_ty = v_string_ty
member _.string_ty_withNull = v_string_ty_withNull
member _.system_IFormattable_tcref = v_IFormattable_tcref
member _.system_FormattableString_tcref = v_FormattableString_tcref
member _.system_FormattableStringFactory_tcref = v_FormattableStringFactory_tcref
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -221,11 +221,12 @@
<Compile Include="Language\SequenceExpressionTests.fs" />
<Compile Include="Language\StaticClassTests.fs" />
<Compile Include="Language\PrintfFormatTests.fs" />
<Compile Include="Language\InterfaceTests.fs" />
<Compile Include="Language\CopyAndUpdateTests.fs" />
<Compile Include="Language\ConstraintIntersectionTests.fs" />
<Compile Include="Language\InterfaceTests.fs" />
<Compile Include="Language\CopyAndUpdateTests.fs" />
<Compile Include="Language\ConstraintIntersectionTests.fs" />
<Compile Include="Language\BooleanReturningAndReturnTypeDirectedPartialActivePatternTests.fs" />
<Compile Include="Language\NullableCsharpImportTests.fs" />
<Compile Include="Language\NullableReferenceTypesTests.fs" />
<Compile Include="ConstraintSolver\PrimitiveConstraints.fs" />
<Compile Include="ConstraintSolver\MemberConstraints.fs" />
<Compile Include="ConstraintSolver\ObjInference.fs" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
module Language.NullableReferenceTypes

open Xunit
open FSharp.Test.Compiler

let typeCheckWithStrictNullness cu =
cu
|> withLangVersionPreview
|> withCheckNulls
|> withWarnOn 3261
|> withOptions ["--warnaserror+"]
|> typecheck

[<Fact>]
let ``Printing a nullable string should pass`` () =
FSharp """module MyLibrary
let maybeNull : string | null = null
let nonNullString = "abc"
let printedValueNotNull = sprintf "This is not null: %s" nonNullString
let printedValueNull = sprintf "This is null: %s" maybeNull
let interpolated = $"This is fine {maybeNull}"
let interpolatedAnnotatedNotNull = $"This is fine %s{nonNullString}"
let interpolatedAnnotatedNullable = $"This is not null %s{maybeNull}"
let interpolateNullLiteral = $"This is not null %s{null}"
let sprintfnNullLiteral = sprintf "This is null: %s" null
"""
|> asLibrary
|> typeCheckWithStrictNullness
|> shouldSucceed


[<Fact>]
let ``Printing a nullable object should pass`` () =
FSharp """module MyLibrary
let maybeNull : string | null = null
let maybeUri : System.Uri | null = null
let okString = "abc"
let printViaO = sprintf "This is null: %O and this is null %O and this is not null %O" maybeNull maybeUri okString
"""
|> asLibrary
|> typeCheckWithStrictNullness
|> shouldSucceed


[<Fact>]
let ``Printing a nullable array via percent A should pass`` () =
FSharp """module MyLibrary
let maybeArray : ((string array) | null) = null
let arrayOfMaybes : ((string | null) array ) = [|null|]
let printViaA = sprintf "This is null: %A and this has null inside %A" maybeArray arrayOfMaybes
"""
|> asLibrary
|> typeCheckWithStrictNullness
|> shouldSucceed

[<Fact>]
let ``WhatIf the format itself is null`` () =
FSharp """module MyLibrary
[<Literal>]
let thisCannotBeAFormat : string | null = null
[<Literal>]
let maybeLiteral : string | null = "abc"
[<Literal>]
let maybeLiteralWithHole : string | null = "Look at me %s"
[<Literal>]
let notNullLiteral : string = "abc"
let doStuff() =
printfn notNullLiteral
printfn maybeLiteral
printfn maybeLiteralWithHole thisCannotBeAFormat
"""
|> asLibrary
|> typeCheckWithStrictNullness
|> shouldSucceed

0 comments on commit bca1c53

Please sign in to comment.