diff --git a/docs/release-notes/.FSharp.Compiler.Service/10.0.100.md b/docs/release-notes/.FSharp.Compiler.Service/10.0.100.md index ee225e94bbf..b69621fc796 100644 --- a/docs/release-notes/.FSharp.Compiler.Service/10.0.100.md +++ b/docs/release-notes/.FSharp.Compiler.Service/10.0.100.md @@ -18,6 +18,8 @@ * Fix active pattern typechecking regression. ([Issue #18638](https://github.com/dotnet/fsharp/issues/18638), [PR #18642](https://github.com/dotnet/fsharp/pull/18642)) * Fix nullness warnings when casting non-nullable values to `IEquatable` to match C# behavior. ([Issue #18759](https://github.com/dotnet/fsharp/issues/18759)) * Fix IsByRefLikeAttribute types being incorrectly suppressed in completion lists. Types like `Span` and `ReadOnlySpan` now appear correctly in IntelliSense. + +* Fix SRTP nullness constraint resolution for types imported from older assemblies. AmbivalentToNull types now use legacy F# nullness rules instead of always satisfying `'T : null` constraints. ([Issue #18390](https://github.com/dotnet/fsharp/issues/18390), [Issue #18344](https://github.com/dotnet/fsharp/issues/18344)) * Fix Show XML doc for enum fields in external metadata ([Issue #17939](https://github.com/dotnet/fsharp/issues/17939#issuecomment-3137410105), [PR #18800](https://github.com/dotnet/fsharp/pull/18800)) ### Changed diff --git a/src/Compiler/Checking/ConstraintSolver.fs b/src/Compiler/Checking/ConstraintSolver.fs index 1132eefa448..a8389e6ad05 100644 --- a/src/Compiler/Checking/ConstraintSolver.fs +++ b/src/Compiler/Checking/ConstraintSolver.fs @@ -1024,7 +1024,8 @@ and SolveTypMeetsTyparConstraints (csenv: ConstraintSolverEnv) ndeep m2 trace ty | TyparConstraint.SimpleChoice(tys, m2) -> SolveTypeChoice csenv ndeep m2 trace ty tys | TyparConstraint.CoercesTo(ty2, m2) -> SolveTypeSubsumesTypeKeepAbbrevs csenv ndeep m2 trace None ty2 ty | TyparConstraint.MayResolveMember(traitInfo, m2) -> - SolveMemberConstraint csenv false PermitWeakResolution.No ndeep m2 trace traitInfo |> OperationResult.ignore + SolveMemberConstraint csenv false PermitWeakResolution.No ndeep m2 trace traitInfo + |> OperationResult.ignore } and shouldWarnUselessNullCheck (csenv:ConstraintSolverEnv) = @@ -2659,18 +2660,28 @@ and SolveTypeUseSupportsNull (csenv: ConstraintSolverEnv) ndeep m2 trace ty = if not g.checkNullness && not (TypeNullIsExtraValue g m ty) then return! ErrorD (ConstraintSolverError(FSComp.SR.csTypeDoesNotHaveNull(NicePrint.minimalStringOfType denv ty), m, m2)) else - if TypeNullIsExtraValue g m ty then - () - elif isNullableTy g ty then - return! ErrorD (ConstraintSolverError(FSComp.SR.csNullableTypeDoesNotHaveNull(NicePrint.minimalStringOfType denv ty), m, m2)) - else - match tryDestTyparTy g ty with - | ValueSome tp -> - do! AddConstraint csenv ndeep m2 trace tp (TyparConstraint.SupportsNull m) - | ValueNone -> - return! ErrorD (ConstraintSolverError(FSComp.SR.csTypeDoesNotHaveNull(NicePrint.minimalStringOfType denv ty), m, m2)) + // Use legacy F# nullness rules when langFeatureNullness is disabled + do! SolveLegacyTypeUseSupportsNullLiteral csenv ndeep m2 trace ty } +// Common logic for legacy F# nullness rules - used for both non-langFeatureNullness path and AmbivalentToNull types +and SolveLegacyTypeUseSupportsNullLiteral (csenv: ConstraintSolverEnv) ndeep m2 (trace: OptionalTrace) ty = + trackErrors { + let g = csenv.g + let m = csenv.m + let denv = csenv.DisplayEnv + if TypeNullIsExtraValue g m ty then + () + elif isNullableTy g ty then + return! ErrorD (ConstraintSolverError(FSComp.SR.csNullableTypeDoesNotHaveNull(NicePrint.minimalStringOfType denv ty), m, m2)) + else + match tryDestTyparTy g ty with + | ValueSome tp -> + do! AddConstraint csenv ndeep m2 trace tp (TyparConstraint.SupportsNull m) + | ValueNone -> + return! ErrorD (ConstraintSolverError(FSComp.SR.csTypeDoesNotHaveNull(NicePrint.minimalStringOfType denv ty), m, m2)) + } + and SolveNullnessSupportsNull (csenv: ConstraintSolverEnv) ndeep m2 (trace: OptionalTrace) ty nullness = trackErrors { let g = csenv.g @@ -2684,7 +2695,9 @@ and SolveNullnessSupportsNull (csenv: ConstraintSolverEnv) ndeep m2 (trace: Opti trace.Exec (fun () -> nv.Set KnownWithNull) (fun () -> nv.Unset()) | Nullness.Known n1 -> match n1 with - | NullnessInfo.AmbivalentToNull -> () + | NullnessInfo.AmbivalentToNull -> + // For AmbivalentToNull types (imported from older assemblies), use legacy F# nullness rules + do! SolveLegacyTypeUseSupportsNullLiteral csenv ndeep m2 trace ty | NullnessInfo.WithNull -> () | NullnessInfo.WithoutNull -> if g.checkNullness then diff --git a/src/Compiler/Symbols/SymbolHelpers.fs b/src/Compiler/Symbols/SymbolHelpers.fs index c516f0c377b..c641790d02c 100644 --- a/src/Compiler/Symbols/SymbolHelpers.fs +++ b/src/Compiler/Symbols/SymbolHelpers.fs @@ -407,9 +407,9 @@ module internal SymbolHelpers = member x.Equals(item1, item2) = match item1,item2 with - | null,null -> true - | null,_ | _,null -> false - | item1,item2 -> + | Null,Null -> true + | Null,_ | _,Null -> false + | NonNull item1,NonNull item2 -> // This may explore assemblies that are not in the reference set. // In this case just bail out and assume items are not equal protectAssemblyExploration false (fun () -> diff --git a/src/Compiler/TypedTree/TypedTree.fs b/src/Compiler/TypedTree/TypedTree.fs index 47815bed680..3efd889f57b 100644 --- a/src/Compiler/TypedTree/TypedTree.fs +++ b/src/Compiler/TypedTree/TypedTree.fs @@ -4453,9 +4453,9 @@ type TType = scope.QualifiedName [] - member x.DebugText = x.ToString() + member x.DebugText = x.LimitedToString(4) - override x.ToString() = + member x.LimitedToString(maxDepth:int) = match x with | TType_forall (_tps, ty) -> "forall ... " + ty.ToString() | TType_app (tcref, tinst, nullness) -> tcref.DisplayName + (match tinst with [] -> "" | tys -> "<" + String.concat "," (List.map string tys) + ">") + nullness.ToString() @@ -4474,9 +4474,12 @@ type TType = | TType_var (tp, _) -> match tp.Solution with | None -> tp.DisplayName - | Some _ -> tp.DisplayName + " (solved)" + | Some t -> tp.DisplayName + $" (solved: {if maxDepth < 0 then Boolean.TrueString else t.LimitedToString(maxDepth-1)})" | TType_measure ms -> ms.ToString() + override x.ToString() = x.LimitedToString(4) + + type TypeInst = TType list type TTypes = TType list diff --git a/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj b/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj index 9618f9cbcee..da13333f878 100644 --- a/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj +++ b/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj @@ -349,7 +349,7 @@ - + diff --git a/tests/FSharp.Compiler.Private.Scripting.UnitTests/FSharpScriptTests.fs b/tests/FSharp.Compiler.Private.Scripting.UnitTests/FSharpScriptTests.fs index 207cde1074f..e53dc45f877 100644 --- a/tests/FSharp.Compiler.Private.Scripting.UnitTests/FSharpScriptTests.fs +++ b/tests/FSharp.Compiler.Private.Scripting.UnitTests/FSharpScriptTests.fs @@ -330,6 +330,42 @@ printfn "{@"%A"}" result Assert.Equal(1, (errors |> Seq.filter (fun error -> error.Message.Contains("FSharp.Really.Not.A.Package")) |> Seq.length)) Assert.Equal(1, (errors |> Seq.filter (fun error -> error.Message.Contains("FSharp.Really.Not.Another.Package")) |> Seq.length)) + [] + member _.``FsharpPlus - report errors``() = + let code = """ +#i "nuget:https://api.nuget.org/v3/index.json" +#r "nuget: FSharpPlus, 1.6.1" + +open FSharpPlus +open FSharpPlus.Data + +let printTable x = + let lines (lst: 'Record list) = + let fields = Reflection.FSharpType.GetRecordFields typeof<'Record> + let headers = fields |> Seq.map _.Name + let asList (x:'record) = fields |> Seq.map (fun field -> string (Reflection.FSharpValue.GetRecordField(x, field))) + let rows = Seq.map asList lst + let table = seq { yield headers; yield! rows } + let maxs = table |> (Seq.traverse ZipList >> ZipList.run) |>> Seq.map length |>> maxBy id + let rowSep = String.replicate (sum maxs + length maxs - 1) "-" + let fill (i, s) = s + String.replicate (i - length s) " " + let printRow r = "|" + (r |> zip maxs |>> fill |> intercalate "|") + "|" + seq { + yield "." + rowSep + "." + yield printRow headers + yield "|" + rowSep + "|" + yield! (rows |>> printRow) + yield "'" + rowSep + "'" } + x |> lines |> iter (printfn "%s") + x |> List.length + +printTable [{|Age = 15; Weight = 88; Name = "Blahboolahboogaloo"|}] +""" + use script = new FSharpScript(additionalArgs=[| |]) + let opt = script.Eval(code) |> getValue + let value = opt.Value + Assert.Equal(1, downcast value.ReflectionValue) + [] member _.``ML - use assembly with ref dependencies``() = let code = """ diff --git a/tests/FSharp.Compiler.Service.Tests/ProjectAnalysisTests.fs b/tests/FSharp.Compiler.Service.Tests/ProjectAnalysisTests.fs index 7bae259ac2c..5c4aba97324 100644 --- a/tests/FSharp.Compiler.Service.Tests/ProjectAnalysisTests.fs +++ b/tests/FSharp.Compiler.Service.Tests/ProjectAnalysisTests.fs @@ -5805,7 +5805,8 @@ let checkContentAsScript content = | FSharpCheckFileAnswer.Succeeded r -> r [] -module ScriptClosureCacheUse = +module ScriptClosureCacheUse = + [] let ``References from #r nuget are included in script project options`` () = let checkResults = checkContentAsScript """