diff --git a/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md b/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md index 3e2c18a6ef0..763645a4133 100644 --- a/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md +++ b/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md @@ -53,6 +53,7 @@ * Fix parallel compilation of scripts ([PR #19649](https://github.com/dotnet/fsharp/pull/19649)) * Fix parser recovery, name resolution, and code completion for unfinished enum patterns ([PR #19708](https://github.com/dotnet/fsharp/pull/19708)) * Parser: fix unexpected diagnostics in debug builds, improve error messages ([PR #19730](https://github.com/dotnet/fsharp/pull/19730)) +* Allow `| null` nullable annotation on a `[]` over a reference type (e.g. the FSharp.UMX `type string<[] 'm> = string` pattern). ([Issue #19657](https://github.com/dotnet/fsharp/issues/19657)) ### Added diff --git a/src/Compiler/Checking/ConstraintSolver.fs b/src/Compiler/Checking/ConstraintSolver.fs index 8a567e0ecef..b739cede2de 100644 --- a/src/Compiler/Checking/ConstraintSolver.fs +++ b/src/Compiler/Checking/ConstraintSolver.fs @@ -2990,7 +2990,9 @@ and SolveTypeIsReferenceType (csenv: ConstraintSolverEnv) ndeep m2 trace ty = | ValueSome destTypar -> AddConstraint csenv ndeep m2 trace destTypar (TyparConstraint.IsReferenceType m) | _ -> - if isRefTy g ty then CompleteD + // Strip measure equations so we test the underlying erased representation — see dotnet/fsharp#19657. + let underlyingTy = stripTyEqnsAndMeasureEqns g ty + if isRefTy g underlyingTy then CompleteD else ErrorD (ConstraintSolverError(FSComp.SR.csGenericConstructRequiresReferenceSemantics(NicePrint.minimalStringOfType denv ty), m, m)) and SolveTypeRequiresDefaultConstructor (csenv: ConstraintSolverEnv) ndeep m2 trace origTy = diff --git a/tests/FSharp.Compiler.ComponentTests/Language/Nullness/NullableReferenceTypesTests.fs b/tests/FSharp.Compiler.ComponentTests/Language/Nullness/NullableReferenceTypesTests.fs index c36c51b3a15..c45d73553e2 100644 --- a/tests/FSharp.Compiler.ComponentTests/Language/Nullness/NullableReferenceTypesTests.fs +++ b/tests/FSharp.Compiler.ComponentTests/Language/Nullness/NullableReferenceTypesTests.fs @@ -2065,6 +2065,146 @@ let processMyStr (x:mystring) = Error 3261, Line 12, Col 27, Line 12, Col 39, "Nullness warning: A non-nullable 'string' was expected but this expression is nullable. Consider either changing the target to also be nullable, or use pattern matching to safely handle the null case of this expression." ] +[] +let ``MeasureAnnotatedAbbreviation over string allows nullable annotation in all positions`` () = + FSharp """module MyLibrary + +[] +type string<[] 'm> = string + +[] type test +type TestType = string + +let x: (TestType | null) = Unchecked.defaultof + +let consume (s: TestType | null) = () + +let produce () : TestType | null = null + +type NullableTestType = TestType | null + +let y: (string | null) = null +""" + |> asLibrary + |> typeCheckWithStrictNullness + |> shouldSucceed + +[] +let ``MeasureAnnotatedAbbreviation over user reference type allows nullable annotation`` () = + FSharp """module MyLibrary + +type MyRef() = member _.Hello = "hi" + +[] +type MyRef<[] 'm> = MyRef + +[] type tag +type Tagged = MyRef + +let f (x: Tagged | null) : Tagged | null = x +let g : Tagged | null = null +""" + |> asLibrary + |> typeCheckWithStrictNullness + |> shouldSucceed + +[] +let ``MeasureAnnotatedAbbreviation over value type rejects nullable annotation`` () = + FSharp """module MyLibrary + +[] +type int<[] 'm> = int + +[] +type DateTime<[] 'm> = System.DateTime + +[] type kg +[] type s + +let bad1 : (int | null) = null +let bad2 : (DateTime | null) = null +""" + |> asLibrary + |> typeCheckWithStrictNullness + |> shouldFail + |> withDiagnostics [ + Error 3260, Line 12, Col 13, Line 12, Col 27, "The type 'int' does not support a nullness qualification." + Error 43, Line 12, Col 13, Line 12, Col 27, "A generic construct requires that the type 'int' have reference semantics, but it does not, i.e. it is a struct" + Error 3260, Line 13, Col 13, Line 13, Col 31, "The type 'DateTime' does not support a nullness qualification." + Error 43, Line 13, Col 13, Line 13, Col 31, "A generic construct requires that the type 'DateTime' have reference semantics, but it does not, i.e. it is a struct" + ] + +[] +let ``Nullness flow and not-null constraints work with MeasureAnnotatedAbbreviation over string`` () = + FSharp """module MyLibrary + +[] +type string<[] 'm> = string + +[] type tag +type S = string + +let onlyNotNull (s: S) = () + +let widen (x: S) : S | null = x + +let narrowBad (x: S | null) : S = x + +let matched (x: S | null) = + match x with + | null -> () + | nn -> onlyNotNull nn +""" + |> asLibrary + |> typeCheckWithStrictNullness + |> shouldFail + |> withDiagnostics [ + Error 3261, Line 13, Col 35, Line 13, Col 36, "Nullness warning: A non-nullable 'S' was expected but this expression is nullable. Consider either changing the target to also be nullable, or use pattern matching to safely handle the null case of this expression." + ] + +[] +let ``MeasureAnnotatedAbbreviation satisfies not-struct constraint`` () = + FSharp """module MyLibrary + +[] +type string<[] 'm> = string + +[] type tag +type S = string + +let needsRef<'T when 'T : not struct> (x: 'T) = () +let callIt (s: S) = needsRef s +let callNullable (s: S | null) = needsRef s +""" + |> asLibrary + |> typeCheckWithStrictNullness + |> shouldSucceed + +[] +let ``MeasureAnnotatedAbbreviation over obj, interface, and chained abbreviation allows nullable`` () = + FSharp """module MyLibrary + +[] +type obj<[] 'm> = obj + +[] +type IDisposable<[] 'm> = System.IDisposable + +[] +type string<[] 'm> = string + +type chainedString<[] 'm> = string<'m> + +[] type tag + +let a : obj | null = null +let b : IDisposable | null = null +let c : chainedString | null = null +""" + |> asLibrary + |> typeCheckWithStrictNullness + |> shouldSucceed + [] let ``ToString on reference type still returns nullable string`` () = FSharp """module MyLibrary