From 6ef5aff652dfb790f62efbaeede598bbdc0aced2 Mon Sep 17 00:00:00 2001 From: Copilot Date: Wed, 26 Aug 2026 17:29:02 +0200 Subject: [PATCH 1/3] Collapse NullableOptionalInterop enforcement sites to always-on path The LanguageFeature.NullableOptionalInterop flag is permanently enabled since the minimum accepted --langversion is 8.0. Remove the dead SupportsFeature checks in the Checking layer and simplify the surrounding branches, deleting the now-unreachable match arms. No behavioural change. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Checking/Expressions/CheckExpressions.fs | 2 +- src/Compiler/Checking/MethodCalls.fs | 31 ++++++------------- .../Checking/OverloadResolutionRules.fs | 11 ++----- 3 files changed, 13 insertions(+), 31 deletions(-) diff --git a/src/Compiler/Checking/Expressions/CheckExpressions.fs b/src/Compiler/Checking/Expressions/CheckExpressions.fs index 02fe8f3bd29..47ee2f04e84 100644 --- a/src/Compiler/Checking/Expressions/CheckExpressions.fs +++ b/src/Compiler/Checking/Expressions/CheckExpressions.fs @@ -5883,7 +5883,7 @@ and TcAdjustExprForTypeDirectedConversions (cenv: cenv) (overallTy: OverallTy) a let g = cenv.g match overallTy with - | MustConvertTo (isMethodArg, reqdTy) when g.langVersion.SupportsFeature LanguageFeature.AdditionalTypeDirectedConversions || (g.langVersion.SupportsFeature LanguageFeature.NullableOptionalInterop && isMethodArg) -> + | MustConvertTo (isMethodArg, reqdTy) when g.langVersion.SupportsFeature LanguageFeature.AdditionalTypeDirectedConversions || isMethodArg -> let tcVal = LightweightTcValForUsingInBuildMethodCall g env.TraitContext AdjustExprForTypeDirectedConversions tcVal g cenv.amap cenv.infoReader env.AccessRights reqdTy actualTy m expr | _ -> diff --git a/src/Compiler/Checking/MethodCalls.fs b/src/Compiler/Checking/MethodCalls.fs index 828585e959d..3a8656a319d 100644 --- a/src/Compiler/Checking/MethodCalls.fs +++ b/src/Compiler/Checking/MethodCalls.fs @@ -296,7 +296,7 @@ let rec AdjustRequiredTypeForTypeDirectedConversions (infoReader: InfoReader) ad elif g.langVersion.SupportsFeature LanguageFeature.AdditionalTypeDirectedConversions && typeEquiv g g.float_ty reqdTy && typeEquiv g g.int32_ty actualTy then g.int32_ty, TypeDirectedConversionUsed.Yes(warn TypeDirectedConversion.BuiltIn, false, false), None - elif g.langVersion.SupportsFeature LanguageFeature.NullableOptionalInterop && isMethodArg && isNullableTy g reqdTy && not (isNullableTy g actualTy) then + elif isMethodArg && isNullableTy g reqdTy && not (isNullableTy g actualTy) then let underlyingTy = destNullableTy g reqdTy // shortcut if typeEquiv g underlyingTy actualTy then @@ -365,17 +365,13 @@ let AdjustCalledArgTypeForOptionals (infoReader: InfoReader) ad enforceNullableO match calledArg.OptArgInfo with // CSharpMethod(?x = arg), optional C#-style argument, may have nullable type | CallerSide _ -> - if g.langVersion.SupportsFeature LanguageFeature.NullableOptionalInterop then - - let calledArgTy = - if isNullableTy g calledArgTy then - destNullableTy g calledArgTy - else - calledArgTy + let calledArgTy = + if isNullableTy g calledArgTy then + destNullableTy g calledArgTy + else + calledArgTy - mkOptionalTy g calledArgTy, TypeDirectedConversionUsed.No, None - else - calledArgTy, TypeDirectedConversionUsed.No, None + mkOptionalTy g calledArgTy, TypeDirectedConversionUsed.No, None // FSharpMethod(?x = arg), optional F#-style argument | CalleeSide -> @@ -387,15 +383,11 @@ let AdjustCalledArgTypeForOptionals (infoReader: InfoReader) ad enforceNullableO AdjustCalledArgTypeForTypeDirectedConversionsAndAutoQuote infoReader ad callerArgTy calledArgTy calledArg m else match calledArg.OptArgInfo with - // CSharpMethod(x = arg), non-optional C#-style argument, may have type Nullable. - | NotOptional when not (g.langVersion.SupportsFeature LanguageFeature.NullableOptionalInterop) -> - AdjustCalledArgTypeForTypeDirectedConversionsAndAutoQuote infoReader ad callerArgTy calledArgTy calledArg m - // The arg should have type ty. However for backwards compat, we also allow arg to have type Nullable | NotOptional // CSharpMethod(x = arg), optional C#-style argument, may have type Nullable. | CallerSide _ -> - if isNullableTy g calledArgTy && g.langVersion.SupportsFeature LanguageFeature.NullableOptionalInterop then + if isNullableTy g calledArgTy then // If inference has worked out it's a nullable then use this if isNullableTy g callerArgTy then calledArgTy, TypeDirectedConversionUsed.No, None @@ -1470,8 +1462,7 @@ let rec AdjustExprForTypeDirectedConversions tcVal (g: TcGlobals) amap infoReade mkCallToDoubleOperator g m actualTy expr - elif g.langVersion.SupportsFeature LanguageFeature.NullableOptionalInterop && - isNullableTy g reqdTy && not (isNullableTy g actualTy) then + elif isNullableTy g reqdTy && not (isNullableTy g actualTy) then let underlyingTy = destNullableTy g reqdTy let adjustedExpr = AdjustExprForTypeDirectedConversions tcVal g amap infoReader ad underlyingTy actualTy m expr @@ -1627,10 +1618,6 @@ let AdjustCallerArgForOptional tcVal tcFieldInit eCallerMemberName (infoReader: let reflArgInfo = calledArg.ReflArgInfo let calledArgTy = calledArg.CalledArgumentType match calledArg.OptArgInfo with - | NotOptional when not (g.langVersion.SupportsFeature LanguageFeature.NullableOptionalInterop) -> - if isOptCallerArg then errorR(Error(FSComp.SR.tcFormalArgumentIsNotOptional(), m)) - assignedArg - // For non-nullable, non-optional arguments no conversion is needed. // We return precisely the assignedArg. This also covers the case where there // can be a lingering permitted type mismatch between caller argument and called argument, diff --git a/src/Compiler/Checking/OverloadResolutionRules.fs b/src/Compiler/Checking/OverloadResolutionRules.fs index 62a08c57ec8..19e4a5ad06c 100644 --- a/src/Compiler/Checking/OverloadResolutionRules.fs +++ b/src/Compiler/Checking/OverloadResolutionRules.fs @@ -319,13 +319,8 @@ let private compareArg (ctx: OverloadResolutionContext) (calledArg1: CalledArg) // T is always better than inref | _ when isInByrefTy g ty2 && typeEquiv g ty1 (destByrefTy g ty2) -> true - // T is always better than Nullable from F# 5.0 onwards - | _ when - g.langVersion.SupportsFeature(LanguageFeature.NullableOptionalInterop) - && isNullableTy g ty2 - && typeEquiv g ty1 (destNullableTy g ty2) - -> - true + // T is always better than Nullable + | _ when isNullableTy g ty2 && typeEquiv g ty1 (destNullableTy g ty2) -> true | _ -> false) @@ -490,7 +485,7 @@ let private moreConcreteRule: TiebreakRule = let private nullableOptionalInteropRule: TiebreakRule = { Id = TiebreakRuleId.NullableOptionalInterop - RequiredFeature = Some LanguageFeature.NullableOptionalInterop + RequiredFeature = None Compare = fun ctx (struct (candidate, _, _)) (struct (other, _, _)) -> let args1 = candidate.AllCalledArgs |> List.concat From 227afb71b38f84392455fbae2f28fa7ca45686a2 Mon Sep 17 00:00:00 2001 From: Copilot Date: Wed, 26 Aug 2026 18:05:14 +0200 Subject: [PATCH 2/3] Remove dead LanguageFeature.NullableOptionalInterop flag definition The NullableOptionalInterop language feature shipped in F# 5.0 and is permanently enabled because the minimum accepted --langversion is 8.0. Remove the now-unused union case, features-map entry, GetFeatureString arm, and the featureNullableOptionalInterop resource string, then regenerate the xlf localization files via /t:UpdateXlf. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Compiler/FSComp.txt | 1 - src/Compiler/Facilities/LanguageFeatures.fs | 3 --- src/Compiler/Facilities/LanguageFeatures.fsi | 1 - src/Compiler/xlf/FSComp.txt.cs.xlf | 5 ----- src/Compiler/xlf/FSComp.txt.de.xlf | 5 ----- src/Compiler/xlf/FSComp.txt.es.xlf | 5 ----- src/Compiler/xlf/FSComp.txt.fr.xlf | 5 ----- src/Compiler/xlf/FSComp.txt.it.xlf | 5 ----- src/Compiler/xlf/FSComp.txt.ja.xlf | 5 ----- src/Compiler/xlf/FSComp.txt.ko.xlf | 5 ----- src/Compiler/xlf/FSComp.txt.pl.xlf | 5 ----- src/Compiler/xlf/FSComp.txt.pt-BR.xlf | 5 ----- src/Compiler/xlf/FSComp.txt.ru.xlf | 5 ----- src/Compiler/xlf/FSComp.txt.tr.xlf | 5 ----- src/Compiler/xlf/FSComp.txt.zh-Hans.xlf | 5 ----- src/Compiler/xlf/FSComp.txt.zh-Hant.xlf | 5 ----- 16 files changed, 70 deletions(-) diff --git a/src/Compiler/FSComp.txt b/src/Compiler/FSComp.txt index 531ae517fd0..a3ead67ac11 100644 --- a/src/Compiler/FSComp.txt +++ b/src/Compiler/FSComp.txt @@ -1568,7 +1568,6 @@ featurePackageManagement,"package management" featureFromEndSlicing,"from-end slicing" featureNullnessChecking,"nullness checking" featureResumableStateMachines,"resumable state machines" -featureNullableOptionalInterop,"nullable optional interop" featureDefaultInterfaceMemberConsumption,"default interface member consumption" featureStringInterpolation,"string interpolation" featureWitnessPassing,"witness passing for trait constraints in F# quotations" diff --git a/src/Compiler/Facilities/LanguageFeatures.fs b/src/Compiler/Facilities/LanguageFeatures.fs index df7cc294aa4..543031813ae 100644 --- a/src/Compiler/Facilities/LanguageFeatures.fs +++ b/src/Compiler/Facilities/LanguageFeatures.fs @@ -21,7 +21,6 @@ type LanguageFeature = | PackageManagement | FromEndSlicing | ResumableStateMachines - | NullableOptionalInterop | DefaultInterfaceMemberConsumption | WitnessPassing | AdditionalTypeDirectedConversions @@ -139,7 +138,6 @@ type LanguageVersion(versionText, ?disabledFeaturesArray: LanguageFeature array) [ // F# 5.0 LanguageFeature.DotlessFloat32Literal, languageVersion50 - LanguageFeature.NullableOptionalInterop, languageVersion50 LanguageFeature.DefaultInterfaceMemberConsumption, languageVersion50 LanguageFeature.PackageManagement, languageVersion50 LanguageFeature.WitnessPassing, languageVersion50 @@ -341,7 +339,6 @@ type LanguageVersion(versionText, ?disabledFeaturesArray: LanguageFeature array) | LanguageFeature.FromEndSlicing -> FSComp.SR.featureFromEndSlicing () | LanguageFeature.NullnessChecking -> FSComp.SR.featureNullnessChecking () | LanguageFeature.ResumableStateMachines -> FSComp.SR.featureResumableStateMachines () - | LanguageFeature.NullableOptionalInterop -> FSComp.SR.featureNullableOptionalInterop () | LanguageFeature.DefaultInterfaceMemberConsumption -> FSComp.SR.featureDefaultInterfaceMemberConsumption () | LanguageFeature.WitnessPassing -> FSComp.SR.featureWitnessPassing () | LanguageFeature.AdditionalTypeDirectedConversions -> FSComp.SR.featureAdditionalImplicitConversions () diff --git a/src/Compiler/Facilities/LanguageFeatures.fsi b/src/Compiler/Facilities/LanguageFeatures.fsi index 9db6bee529e..a1ebbd6b884 100644 --- a/src/Compiler/Facilities/LanguageFeatures.fsi +++ b/src/Compiler/Facilities/LanguageFeatures.fsi @@ -11,7 +11,6 @@ type LanguageFeature = | PackageManagement | FromEndSlicing | ResumableStateMachines - | NullableOptionalInterop | DefaultInterfaceMemberConsumption | WitnessPassing | AdditionalTypeDirectedConversions diff --git a/src/Compiler/xlf/FSComp.txt.cs.xlf b/src/Compiler/xlf/FSComp.txt.cs.xlf index 61211b419b4..24cd65861ca 100644 --- a/src/Compiler/xlf/FSComp.txt.cs.xlf +++ b/src/Compiler/xlf/FSComp.txt.cs.xlf @@ -532,11 +532,6 @@ honor the 'NotNullIfNotNull' attribute on a method's return value - - nullable optional interop - nepovinný zprostředkovatel komunikace s možnou hodnotou null - - nullness checking nullness checking diff --git a/src/Compiler/xlf/FSComp.txt.de.xlf b/src/Compiler/xlf/FSComp.txt.de.xlf index 28019bbdaf3..d14a8a34a5e 100644 --- a/src/Compiler/xlf/FSComp.txt.de.xlf +++ b/src/Compiler/xlf/FSComp.txt.de.xlf @@ -532,11 +532,6 @@ honor the 'NotNullIfNotNull' attribute on a method's return value - - nullable optional interop - Interop, NULL-Werte zulassend, optional - - nullness checking nullness checking diff --git a/src/Compiler/xlf/FSComp.txt.es.xlf b/src/Compiler/xlf/FSComp.txt.es.xlf index 0d5158ff312..5edf6a0ff26 100644 --- a/src/Compiler/xlf/FSComp.txt.es.xlf +++ b/src/Compiler/xlf/FSComp.txt.es.xlf @@ -532,11 +532,6 @@ honor the 'NotNullIfNotNull' attribute on a method's return value - - nullable optional interop - interoperabilidad opcional que admite valores NULL - - nullness checking nullness checking diff --git a/src/Compiler/xlf/FSComp.txt.fr.xlf b/src/Compiler/xlf/FSComp.txt.fr.xlf index b1222d679d8..55dab6c1571 100644 --- a/src/Compiler/xlf/FSComp.txt.fr.xlf +++ b/src/Compiler/xlf/FSComp.txt.fr.xlf @@ -532,11 +532,6 @@ honor the 'NotNullIfNotNull' attribute on a method's return value - - nullable optional interop - interopérabilité facultative pouvant accepter une valeur null - - nullness checking nullness checking diff --git a/src/Compiler/xlf/FSComp.txt.it.xlf b/src/Compiler/xlf/FSComp.txt.it.xlf index 748229772c3..c9d15a5acfa 100644 --- a/src/Compiler/xlf/FSComp.txt.it.xlf +++ b/src/Compiler/xlf/FSComp.txt.it.xlf @@ -532,11 +532,6 @@ honor the 'NotNullIfNotNull' attribute on a method's return value - - nullable optional interop - Interop facoltativo nullable - - nullness checking nullness checking diff --git a/src/Compiler/xlf/FSComp.txt.ja.xlf b/src/Compiler/xlf/FSComp.txt.ja.xlf index e413d95f6d4..2f8dc2d6140 100644 --- a/src/Compiler/xlf/FSComp.txt.ja.xlf +++ b/src/Compiler/xlf/FSComp.txt.ja.xlf @@ -532,11 +532,6 @@ honor the 'NotNullIfNotNull' attribute on a method's return value - - nullable optional interop - Null 許容のオプションの相互運用 - - nullness checking nullness checking diff --git a/src/Compiler/xlf/FSComp.txt.ko.xlf b/src/Compiler/xlf/FSComp.txt.ko.xlf index 26f6ef4e6ed..3df02258e7f 100644 --- a/src/Compiler/xlf/FSComp.txt.ko.xlf +++ b/src/Compiler/xlf/FSComp.txt.ko.xlf @@ -532,11 +532,6 @@ honor the 'NotNullIfNotNull' attribute on a method's return value - - nullable optional interop - nullable 선택적 interop - - nullness checking nullness checking diff --git a/src/Compiler/xlf/FSComp.txt.pl.xlf b/src/Compiler/xlf/FSComp.txt.pl.xlf index 623fb303116..5870ab03ecb 100644 --- a/src/Compiler/xlf/FSComp.txt.pl.xlf +++ b/src/Compiler/xlf/FSComp.txt.pl.xlf @@ -532,11 +532,6 @@ honor the 'NotNullIfNotNull' attribute on a method's return value - - nullable optional interop - opcjonalna międzyoperacyjność dopuszczająca wartość null - - nullness checking nullness checking diff --git a/src/Compiler/xlf/FSComp.txt.pt-BR.xlf b/src/Compiler/xlf/FSComp.txt.pt-BR.xlf index c11f600ad14..25ebcc73ee0 100644 --- a/src/Compiler/xlf/FSComp.txt.pt-BR.xlf +++ b/src/Compiler/xlf/FSComp.txt.pt-BR.xlf @@ -532,11 +532,6 @@ honor the 'NotNullIfNotNull' attribute on a method's return value - - nullable optional interop - interoperabilidade opcional anulável - - nullness checking nullness checking diff --git a/src/Compiler/xlf/FSComp.txt.ru.xlf b/src/Compiler/xlf/FSComp.txt.ru.xlf index a877af1b88c..eec84b4dfab 100644 --- a/src/Compiler/xlf/FSComp.txt.ru.xlf +++ b/src/Compiler/xlf/FSComp.txt.ru.xlf @@ -532,11 +532,6 @@ honor the 'NotNullIfNotNull' attribute on a method's return value - - nullable optional interop - необязательное взаимодействие, допускающее значение NULL - - nullness checking nullness checking diff --git a/src/Compiler/xlf/FSComp.txt.tr.xlf b/src/Compiler/xlf/FSComp.txt.tr.xlf index bced047f177..68ffe7d11f6 100644 --- a/src/Compiler/xlf/FSComp.txt.tr.xlf +++ b/src/Compiler/xlf/FSComp.txt.tr.xlf @@ -532,11 +532,6 @@ honor the 'NotNullIfNotNull' attribute on a method's return value - - nullable optional interop - null atanabilir isteğe bağlı birlikte çalışma - - nullness checking nullness checking diff --git a/src/Compiler/xlf/FSComp.txt.zh-Hans.xlf b/src/Compiler/xlf/FSComp.txt.zh-Hans.xlf index 1193d6bfcfe..039f4454fbe 100644 --- a/src/Compiler/xlf/FSComp.txt.zh-Hans.xlf +++ b/src/Compiler/xlf/FSComp.txt.zh-Hans.xlf @@ -532,11 +532,6 @@ honor the 'NotNullIfNotNull' attribute on a method's return value - - nullable optional interop - 可以为 null 的可选互操作 - - nullness checking nullness checking diff --git a/src/Compiler/xlf/FSComp.txt.zh-Hant.xlf b/src/Compiler/xlf/FSComp.txt.zh-Hant.xlf index f1a7a9b02aa..bc0db5d1dee 100644 --- a/src/Compiler/xlf/FSComp.txt.zh-Hant.xlf +++ b/src/Compiler/xlf/FSComp.txt.zh-Hant.xlf @@ -532,11 +532,6 @@ honor the 'NotNullIfNotNull' attribute on a method's return value - - nullable optional interop - 可為 Null 的選擇性 Interop - - nullness checking nullness checking From 745d9842744edd6cf6acd163bd8fe1b3d8172af5 Mon Sep 17 00:00:00 2001 From: Copilot Date: Wed, 26 Aug 2026 19:17:47 +0200 Subject: [PATCH 3/3] Add release note for NullableOptionalInterop flag removal Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- docs/release-notes/.FSharp.Compiler.Service/11.0.100.md | 1 + 1 file changed, 1 insertion(+) 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 562632e460d..442363145ba 100644 --- a/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md +++ b/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md @@ -200,6 +200,7 @@ * Interpolated string holes (e.g. `$"{x}"`) are now formatted with invariant culture (via the `string` operator) instead of the current thread culture. ([PR #19971](https://github.com/dotnet/fsharp/pull/19971)) * Lines starting with `#:` are now ignored ([Language suggestion 1440](https://github.com/fsharp/fslang-suggestions/issues/1440), [RFC FS-1337](https://github.com/fsharp/fslang-design/pull/830), [PR #20212](https://github.com/dotnet/fsharp/pull/20212)) * Calculate Entity.PublicPath instead of storing ([PR #20285](https://github.com/dotnet/fsharp/pull/20285)) +* Remove the always-on `NullableOptionalInterop` language feature flag and collapse its guarded code to the enabled path; `--disableLanguageFeature:NullableOptionalInterop` is no longer a recognized feature name. ([Issue #20154](https://github.com/dotnet/fsharp/issues/20154), [PR #20378](https://github.com/dotnet/fsharp/pull/20378)) ### Breaking Changes * Add `ExtendedLayoutAttribute` support for future .NET runtime interop. `ILTypeDefLayout` has a new `Extended` case. ([Issue #19190](https://github.com/dotnet/fsharp/issues/19190), [PR #19194](https://github.com/dotnet/fsharp/pull/19194))