From f26d790d7e0f245332e264cb2f3debda6e385f59 Mon Sep 17 00:00:00 2001 From: Tomas Grosup Date: Fri, 15 May 2026 14:50:41 +0200 Subject: [PATCH 01/18] Add failing tests + strings + ContextInfo for dot-access nullness warning (#19658, sprint 1/3) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Compiler/Checking/ConstraintSolver.fs | 8 ++++ src/Compiler/Checking/ConstraintSolver.fsi | 14 ++++++ .../Checking/Expressions/CheckExpressions.fs | 3 +- src/Compiler/FSStrings.resx | 6 +++ src/Compiler/Symbols/FSharpDiagnostic.fs | 1 + src/Compiler/xlf/FSStrings.cs.xlf | 10 ++++ src/Compiler/xlf/FSStrings.de.xlf | 10 ++++ src/Compiler/xlf/FSStrings.es.xlf | 10 ++++ src/Compiler/xlf/FSStrings.fr.xlf | 10 ++++ src/Compiler/xlf/FSStrings.it.xlf | 10 ++++ src/Compiler/xlf/FSStrings.ja.xlf | 10 ++++ src/Compiler/xlf/FSStrings.ko.xlf | 10 ++++ src/Compiler/xlf/FSStrings.pl.xlf | 10 ++++ src/Compiler/xlf/FSStrings.pt-BR.xlf | 10 ++++ src/Compiler/xlf/FSStrings.ru.xlf | 10 ++++ src/Compiler/xlf/FSStrings.tr.xlf | 10 ++++ src/Compiler/xlf/FSStrings.zh-Hans.xlf | 10 ++++ src/Compiler/xlf/FSStrings.zh-Hant.xlf | 10 ++++ .../Nullness/NullableReferenceTypesTests.fs | 48 +++++++++++++++++++ 19 files changed, 209 insertions(+), 1 deletion(-) diff --git a/src/Compiler/Checking/ConstraintSolver.fs b/src/Compiler/Checking/ConstraintSolver.fs index 8a567e0ecef..478508b3d3c 100644 --- a/src/Compiler/Checking/ConstraintSolver.fs +++ b/src/Compiler/Checking/ConstraintSolver.fs @@ -186,6 +186,12 @@ type ContextInfo = /// The range points to the original argument location. | NullnessCheckOfCapturedArg of range + /// The type equation comes from a member access on a nullable receiver expression. + /// The first range is the receiver/object-expression range (e.g. the `x` in `x.PadLeft`). + /// The string is the resolved member/method name. + /// The optional string is the binding name when the receiver is a simple value reference (e.g. "x"). + | MemberAccessOnNullable of objExprRange: range * memberName: string * bindingName: string option + /// Captures relevant information for a particular failed overload resolution. type OverloadInformation = { @@ -236,6 +242,8 @@ exception ConstraintSolverNullnessWarningWithType of DisplayEnv * TType * Nullne exception ConstraintSolverNullnessWarning of string * range * range +exception ConstraintSolverNullnessWarningOnDotAccess of DisplayEnv * objTy: TType * memberName: string * bindingName: string option * objExprRange: range * mMethod: range + exception ConstraintSolverError of string * range * range exception ErrorFromApplyingDefault of tcGlobals: TcGlobals * displayEnv: DisplayEnv * Typar * TType * error: exn * range: range diff --git a/src/Compiler/Checking/ConstraintSolver.fsi b/src/Compiler/Checking/ConstraintSolver.fsi index 8d21270f901..d3a8c0fb89a 100644 --- a/src/Compiler/Checking/ConstraintSolver.fsi +++ b/src/Compiler/Checking/ConstraintSolver.fsi @@ -66,6 +66,12 @@ type ContextInfo = /// The range points to the original argument location. | NullnessCheckOfCapturedArg of range + /// The type equation comes from a member access on a nullable receiver expression. + /// The first range is the receiver/object-expression range (e.g. the `x` in `x.PadLeft`). + /// The string is the resolved member/method name. + /// The optional string is the binding name when the receiver is a simple value reference (e.g. "x"). + | MemberAccessOnNullable of objExprRange: range * memberName: string * bindingName: string option + /// Captures relevant information for a particular failed overload resolution. type OverloadInformation = { methodSlot: CalledMeth @@ -146,6 +152,14 @@ exception ConstraintSolverNullnessWarningWithType of DisplayEnv * TType * Nullne exception ConstraintSolverNullnessWarning of string * range * range +exception ConstraintSolverNullnessWarningOnDotAccess of + DisplayEnv * + objTy: TType * + memberName: string * + bindingName: string option * + objExprRange: range * + mMethod: range + exception ConstraintSolverError of string * range * range exception ErrorFromApplyingDefault of diff --git a/src/Compiler/Checking/Expressions/CheckExpressions.fs b/src/Compiler/Checking/Expressions/CheckExpressions.fs index 30d49e776c8..04ef1b8d6f9 100644 --- a/src/Compiler/Checking/Expressions/CheckExpressions.fs +++ b/src/Compiler/Checking/Expressions/CheckExpressions.fs @@ -604,7 +604,8 @@ let ShrinkContext env oldRange newRange = | ContextInfo.RuntimeTypeTest _ | ContextInfo.DowncastUsedInsteadOfUpcast _ | ContextInfo.SequenceExpression _ - | ContextInfo.NullnessCheckOfCapturedArg _ -> + | ContextInfo.NullnessCheckOfCapturedArg _ + | ContextInfo.MemberAccessOnNullable _ -> env | ContextInfo.CollectionElement (b,m) -> if not (equals m oldRange) then env else diff --git a/src/Compiler/FSStrings.resx b/src/Compiler/FSStrings.resx index 920cc48a52a..18482e71c9e 100644 --- a/src/Compiler/FSStrings.resx +++ b/src/Compiler/FSStrings.resx @@ -135,6 +135,12 @@ Nullness warning: The types '{0}' and '{1}' do not have compatible nullability. + + Nullness warning: Possible dereference of a null value when accessing member '{0}' on a nullable expression of type '{1}'. + + + Nullness warning: Possible dereference of a null value when accessing member '{0}' on the nullable value '{1}' of type '{2}'. + Nullness warning: The type '{0}' does not support 'null'. diff --git a/src/Compiler/Symbols/FSharpDiagnostic.fs b/src/Compiler/Symbols/FSharpDiagnostic.fs index 0d48e550f70..06097f35ad0 100644 --- a/src/Compiler/Symbols/FSharpDiagnostic.fs +++ b/src/Compiler/Symbols/FSharpDiagnostic.fs @@ -63,6 +63,7 @@ module ExtendedData = | ContextInfo.PatternMatchGuard _ -> PatternMatchGuard | ContextInfo.SequenceExpression _ -> SequenceExpression | ContextInfo.NullnessCheckOfCapturedArg _ -> NoContext + | ContextInfo.MemberAccessOnNullable _ -> NoContext type IFSharpDiagnosticExtendedData = interface end diff --git a/src/Compiler/xlf/FSStrings.cs.xlf b/src/Compiler/xlf/FSStrings.cs.xlf index a28784716d6..d8e813bb38c 100644 --- a/src/Compiler/xlf/FSStrings.cs.xlf +++ b/src/Compiler/xlf/FSStrings.cs.xlf @@ -32,6 +32,16 @@ Nullness warning: The types '{0}' and '{1}' do not have compatible nullability. + + Nullness warning: Possible dereference of a null value when accessing member '{0}' on a nullable expression of type '{1}'. + Nullness warning: Possible dereference of a null value when accessing member '{0}' on a nullable expression of type '{1}'. + + + + Nullness warning: Possible dereference of a null value when accessing member '{0}' on the nullable value '{1}' of type '{2}'. + Nullness warning: Possible dereference of a null value when accessing member '{0}' on the nullable value '{1}' of type '{2}'. + + Type mismatch. Expecting a tuple of length {0} of type\n {1} \nbut given a tuple of length {2} of type\n {3} {4}\n Neshoda typů Očekává se řazená kolekce členů o délce {0} typu\n {1} \nale odevzdala se řazená kolekce členů o délce {2} typu\n {3}{4}\n diff --git a/src/Compiler/xlf/FSStrings.de.xlf b/src/Compiler/xlf/FSStrings.de.xlf index 73299495a4b..2a6c43b4e6e 100644 --- a/src/Compiler/xlf/FSStrings.de.xlf +++ b/src/Compiler/xlf/FSStrings.de.xlf @@ -32,6 +32,16 @@ Nullness warning: The types '{0}' and '{1}' do not have compatible nullability. + + Nullness warning: Possible dereference of a null value when accessing member '{0}' on a nullable expression of type '{1}'. + Nullness warning: Possible dereference of a null value when accessing member '{0}' on a nullable expression of type '{1}'. + + + + Nullness warning: Possible dereference of a null value when accessing member '{0}' on the nullable value '{1}' of type '{2}'. + Nullness warning: Possible dereference of a null value when accessing member '{0}' on the nullable value '{1}' of type '{2}'. + + Type mismatch. Expecting a tuple of length {0} of type\n {1} \nbut given a tuple of length {2} of type\n {3} {4}\n Typenkonflikt. Es wurde ein Tupel der Länge {0} des Typs\n {1} \nerwartet, aber ein Tupel der Länge {2} des Typs\n {3}{4}\n angegeben. diff --git a/src/Compiler/xlf/FSStrings.es.xlf b/src/Compiler/xlf/FSStrings.es.xlf index e73ce8e4291..09958694ca7 100644 --- a/src/Compiler/xlf/FSStrings.es.xlf +++ b/src/Compiler/xlf/FSStrings.es.xlf @@ -32,6 +32,16 @@ Nullness warning: The types '{0}' and '{1}' do not have compatible nullability. + + Nullness warning: Possible dereference of a null value when accessing member '{0}' on a nullable expression of type '{1}'. + Nullness warning: Possible dereference of a null value when accessing member '{0}' on a nullable expression of type '{1}'. + + + + Nullness warning: Possible dereference of a null value when accessing member '{0}' on the nullable value '{1}' of type '{2}'. + Nullness warning: Possible dereference of a null value when accessing member '{0}' on the nullable value '{1}' of type '{2}'. + + Type mismatch. Expecting a tuple of length {0} of type\n {1} \nbut given a tuple of length {2} of type\n {3} {4}\n Error de coincidencia de tipos. Se espera una tupla de longitud {0} de tipo\n {1} \nperero se ha proporcionado una tupla de longitud {2} de tipo\n {3}{4}\n diff --git a/src/Compiler/xlf/FSStrings.fr.xlf b/src/Compiler/xlf/FSStrings.fr.xlf index cb784fdab67..ba65366cece 100644 --- a/src/Compiler/xlf/FSStrings.fr.xlf +++ b/src/Compiler/xlf/FSStrings.fr.xlf @@ -32,6 +32,16 @@ Nullness warning: The types '{0}' and '{1}' do not have compatible nullability. + + Nullness warning: Possible dereference of a null value when accessing member '{0}' on a nullable expression of type '{1}'. + Nullness warning: Possible dereference of a null value when accessing member '{0}' on a nullable expression of type '{1}'. + + + + Nullness warning: Possible dereference of a null value when accessing member '{0}' on the nullable value '{1}' of type '{2}'. + Nullness warning: Possible dereference of a null value when accessing member '{0}' on the nullable value '{1}' of type '{2}'. + + Type mismatch. Expecting a tuple of length {0} of type\n {1} \nbut given a tuple of length {2} of type\n {3} {4}\n Incompatibilité de type. Tuple de longueur attendu {0} de type\n {1} \nmais tuple de longueur {2} de type\n {3}{4}\n diff --git a/src/Compiler/xlf/FSStrings.it.xlf b/src/Compiler/xlf/FSStrings.it.xlf index de6c619667a..e78c11f3f21 100644 --- a/src/Compiler/xlf/FSStrings.it.xlf +++ b/src/Compiler/xlf/FSStrings.it.xlf @@ -32,6 +32,16 @@ Nullness warning: The types '{0}' and '{1}' do not have compatible nullability. + + Nullness warning: Possible dereference of a null value when accessing member '{0}' on a nullable expression of type '{1}'. + Nullness warning: Possible dereference of a null value when accessing member '{0}' on a nullable expression of type '{1}'. + + + + Nullness warning: Possible dereference of a null value when accessing member '{0}' on the nullable value '{1}' of type '{2}'. + Nullness warning: Possible dereference of a null value when accessing member '{0}' on the nullable value '{1}' of type '{2}'. + + Type mismatch. Expecting a tuple of length {0} of type\n {1} \nbut given a tuple of length {2} of type\n {3} {4}\n Tipo non corrispondente. È prevista una tupla di lunghezza {0} di tipo\n {1} \n, ma è stata specificata una tupla di lunghezza {2} di tipo\n {3}{4}\n diff --git a/src/Compiler/xlf/FSStrings.ja.xlf b/src/Compiler/xlf/FSStrings.ja.xlf index 23154e99892..4907e12557b 100644 --- a/src/Compiler/xlf/FSStrings.ja.xlf +++ b/src/Compiler/xlf/FSStrings.ja.xlf @@ -32,6 +32,16 @@ Nullness warning: The types '{0}' and '{1}' do not have compatible nullability. + + Nullness warning: Possible dereference of a null value when accessing member '{0}' on a nullable expression of type '{1}'. + Nullness warning: Possible dereference of a null value when accessing member '{0}' on a nullable expression of type '{1}'. + + + + Nullness warning: Possible dereference of a null value when accessing member '{0}' on the nullable value '{1}' of type '{2}'. + Nullness warning: Possible dereference of a null value when accessing member '{0}' on the nullable value '{1}' of type '{2}'. + + Type mismatch. Expecting a tuple of length {0} of type\n {1} \nbut given a tuple of length {2} of type\n {3} {4}\n 型が一致しません。型の長さ {0} のタプルが必要です\n {1} \nただし、型の長さ {2} のタプルが指定された場合\n {3}{4}\n diff --git a/src/Compiler/xlf/FSStrings.ko.xlf b/src/Compiler/xlf/FSStrings.ko.xlf index 8b2e15d5d5f..a08987198bf 100644 --- a/src/Compiler/xlf/FSStrings.ko.xlf +++ b/src/Compiler/xlf/FSStrings.ko.xlf @@ -32,6 +32,16 @@ Nullness warning: The types '{0}' and '{1}' do not have compatible nullability. + + Nullness warning: Possible dereference of a null value when accessing member '{0}' on a nullable expression of type '{1}'. + Nullness warning: Possible dereference of a null value when accessing member '{0}' on a nullable expression of type '{1}'. + + + + Nullness warning: Possible dereference of a null value when accessing member '{0}' on the nullable value '{1}' of type '{2}'. + Nullness warning: Possible dereference of a null value when accessing member '{0}' on the nullable value '{1}' of type '{2}'. + + Type mismatch. Expecting a tuple of length {0} of type\n {1} \nbut given a tuple of length {2} of type\n {3} {4}\n 유형 불일치. 형식이 \n {1}이고 길이가 {0}인 튜플이 필요합니다. \n그러나 형식이 \n {3}이고 길이가 {2}인 튜플이 제공되었습니다.{4}\n diff --git a/src/Compiler/xlf/FSStrings.pl.xlf b/src/Compiler/xlf/FSStrings.pl.xlf index 7ab2fe2d494..160e6f04980 100644 --- a/src/Compiler/xlf/FSStrings.pl.xlf +++ b/src/Compiler/xlf/FSStrings.pl.xlf @@ -32,6 +32,16 @@ Nullness warning: The types '{0}' and '{1}' do not have compatible nullability. + + Nullness warning: Possible dereference of a null value when accessing member '{0}' on a nullable expression of type '{1}'. + Nullness warning: Possible dereference of a null value when accessing member '{0}' on a nullable expression of type '{1}'. + + + + Nullness warning: Possible dereference of a null value when accessing member '{0}' on the nullable value '{1}' of type '{2}'. + Nullness warning: Possible dereference of a null value when accessing member '{0}' on the nullable value '{1}' of type '{2}'. + + Type mismatch. Expecting a tuple of length {0} of type\n {1} \nbut given a tuple of length {2} of type\n {3} {4}\n Niezgodność. Oczekiwano krotki o długości {0} typu\n {1} \nale otrzymano krotkę o długości {2} typu\n {3}{4}\n diff --git a/src/Compiler/xlf/FSStrings.pt-BR.xlf b/src/Compiler/xlf/FSStrings.pt-BR.xlf index f5e8bc53ca4..b0f043eecfd 100644 --- a/src/Compiler/xlf/FSStrings.pt-BR.xlf +++ b/src/Compiler/xlf/FSStrings.pt-BR.xlf @@ -32,6 +32,16 @@ Nullness warning: The types '{0}' and '{1}' do not have compatible nullability. + + Nullness warning: Possible dereference of a null value when accessing member '{0}' on a nullable expression of type '{1}'. + Nullness warning: Possible dereference of a null value when accessing member '{0}' on a nullable expression of type '{1}'. + + + + Nullness warning: Possible dereference of a null value when accessing member '{0}' on the nullable value '{1}' of type '{2}'. + Nullness warning: Possible dereference of a null value when accessing member '{0}' on the nullable value '{1}' of type '{2}'. + + Type mismatch. Expecting a tuple of length {0} of type\n {1} \nbut given a tuple of length {2} of type\n {3} {4}\n Tipo incompatível. Esperando uma tupla de comprimento {0} do tipo\n {1} \nmas recebeu uma tupla de comprimento {2} do tipo\n {3}{4}\n diff --git a/src/Compiler/xlf/FSStrings.ru.xlf b/src/Compiler/xlf/FSStrings.ru.xlf index 117c522a187..91f4886ef2d 100644 --- a/src/Compiler/xlf/FSStrings.ru.xlf +++ b/src/Compiler/xlf/FSStrings.ru.xlf @@ -32,6 +32,16 @@ Nullness warning: The types '{0}' and '{1}' do not have compatible nullability. + + Nullness warning: Possible dereference of a null value when accessing member '{0}' on a nullable expression of type '{1}'. + Nullness warning: Possible dereference of a null value when accessing member '{0}' on a nullable expression of type '{1}'. + + + + Nullness warning: Possible dereference of a null value when accessing member '{0}' on the nullable value '{1}' of type '{2}'. + Nullness warning: Possible dereference of a null value when accessing member '{0}' on the nullable value '{1}' of type '{2}'. + + Type mismatch. Expecting a tuple of length {0} of type\n {1} \nbut given a tuple of length {2} of type\n {3} {4}\n Несоответствие типов. Ожидается кортеж длиной {0} типа\n {1}, \nно предоставлен кортеж длиной {2} типа\n {3}{4}\n diff --git a/src/Compiler/xlf/FSStrings.tr.xlf b/src/Compiler/xlf/FSStrings.tr.xlf index 6483eba2da0..5a22beb2131 100644 --- a/src/Compiler/xlf/FSStrings.tr.xlf +++ b/src/Compiler/xlf/FSStrings.tr.xlf @@ -32,6 +32,16 @@ Nullness warning: The types '{0}' and '{1}' do not have compatible nullability. + + Nullness warning: Possible dereference of a null value when accessing member '{0}' on a nullable expression of type '{1}'. + Nullness warning: Possible dereference of a null value when accessing member '{0}' on a nullable expression of type '{1}'. + + + + Nullness warning: Possible dereference of a null value when accessing member '{0}' on the nullable value '{1}' of type '{2}'. + Nullness warning: Possible dereference of a null value when accessing member '{0}' on the nullable value '{1}' of type '{2}'. + + Type mismatch. Expecting a tuple of length {0} of type\n {1} \nbut given a tuple of length {2} of type\n {3} {4}\n Tür uyuşmazlığı. {0} uzunluğunda türü\n {1} \nolan bir demet bekleniyordu ancak {2} uzunluğunda türü\n {3}{4}\nolan bir demet verildi diff --git a/src/Compiler/xlf/FSStrings.zh-Hans.xlf b/src/Compiler/xlf/FSStrings.zh-Hans.xlf index be48009ccde..d620699ec07 100644 --- a/src/Compiler/xlf/FSStrings.zh-Hans.xlf +++ b/src/Compiler/xlf/FSStrings.zh-Hans.xlf @@ -32,6 +32,16 @@ Nullness warning: The types '{0}' and '{1}' do not have compatible nullability. + + Nullness warning: Possible dereference of a null value when accessing member '{0}' on a nullable expression of type '{1}'. + Nullness warning: Possible dereference of a null value when accessing member '{0}' on a nullable expression of type '{1}'. + + + + Nullness warning: Possible dereference of a null value when accessing member '{0}' on the nullable value '{1}' of type '{2}'. + Nullness warning: Possible dereference of a null value when accessing member '{0}' on the nullable value '{1}' of type '{2}'. + + Type mismatch. Expecting a tuple of length {0} of type\n {1} \nbut given a tuple of length {2} of type\n {3} {4}\n 类型不匹配。应为长度为 {0} 的类型的元组\n {1} \n但提供了长度为 {2} 的类型的元组\n {3}{4}\n diff --git a/src/Compiler/xlf/FSStrings.zh-Hant.xlf b/src/Compiler/xlf/FSStrings.zh-Hant.xlf index 45cae27f445..272ec1802ba 100644 --- a/src/Compiler/xlf/FSStrings.zh-Hant.xlf +++ b/src/Compiler/xlf/FSStrings.zh-Hant.xlf @@ -32,6 +32,16 @@ Nullness warning: The types '{0}' and '{1}' do not have compatible nullability. + + Nullness warning: Possible dereference of a null value when accessing member '{0}' on a nullable expression of type '{1}'. + Nullness warning: Possible dereference of a null value when accessing member '{0}' on a nullable expression of type '{1}'. + + + + Nullness warning: Possible dereference of a null value when accessing member '{0}' on the nullable value '{1}' of type '{2}'. + Nullness warning: Possible dereference of a null value when accessing member '{0}' on the nullable value '{1}' of type '{2}'. + + Type mismatch. Expecting a tuple of length {0} of type\n {1} \nbut given a tuple of length {2} of type\n {3} {4}\n 類型不符。必須是類型為\n {1} \n 的元組長度 {0},但提供的是類型為\n {3}{4}\n 的元組長度 {2} diff --git a/tests/FSharp.Compiler.ComponentTests/Language/Nullness/NullableReferenceTypesTests.fs b/tests/FSharp.Compiler.ComponentTests/Language/Nullness/NullableReferenceTypesTests.fs index c36c51b3a15..a8b4b953d6b 100644 --- a/tests/FSharp.Compiler.ComponentTests/Language/Nullness/NullableReferenceTypesTests.fs +++ b/tests/FSharp.Compiler.ComponentTests/Language/Nullness/NullableReferenceTypesTests.fs @@ -2373,3 +2373,51 @@ let main _ = 0 |> compile |> run |> verifyOutputContains [|"-1"|] + +[] +let ``Issue 19658 - nullness warning on dotted method access underlines the receiver and mentions the member`` () = + FSharp """module Program +[] +let main _ = + let x: string | null = "" + let y = x.PadLeft(1) + 0""" + |> asLibrary + |> typeCheckWithStrictNullness + |> shouldFail + |> withDiagnostics [ + Error 3261, Line 5, Col 13, Line 5, Col 14, + "Nullness warning: Possible dereference of a null value when accessing member 'PadLeft' on the nullable value 'x' of type 'string'." + ] + +[] +let ``Issue 19658 - nullness warning on dotted property access underlines the receiver`` () = + FSharp """module Program +[] +let main _ = + let x: string | null = "" + let n = x.Length + 0""" + |> asLibrary + |> typeCheckWithStrictNullness + |> shouldFail + |> withDiagnostics [ + Error 3261, Line 5, Col 13, Line 5, Col 14, + "Nullness warning: Possible dereference of a null value when accessing member 'Length' on the nullable value 'x' of type 'string'." + ] + +[] +let ``Issue 19658 - nullness warning on dotted access of complex receiver omits binding name`` () = + FSharp """module Program +let getStr () : string | null = "" +[] +let main _ = + let n = (getStr()).Length + 0""" + |> asLibrary + |> typeCheckWithStrictNullness + |> shouldFail + |> withDiagnostics [ + Error 3261, Line 5, Col 13, Line 5, Col 23, + "Nullness warning: Possible dereference of a null value when accessing member 'Length' on a nullable expression of type 'string'." + ] From 2a60e44c70ef9f88c70ad95989cb22d72d5aeecc Mon Sep 17 00:00:00 2001 From: Tomas Grosup Date: Fri, 15 May 2026 15:41:36 +0200 Subject: [PATCH 02/18] Emit dot-access-specific nullness warning with receiver range (#19658, sprint 2/3) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Compiler/Checking/ConstraintSolver.fs | 48 +++++++++++++++---- src/Compiler/Checking/ConstraintSolver.fsi | 2 + .../Checking/Expressions/CheckExpressions.fs | 17 +++++-- src/Compiler/Driver/CompilerDiagnostics.fs | 14 ++++++ .../Nullness/NullableReferenceTypesTests.fs | 2 +- 5 files changed, 69 insertions(+), 14 deletions(-) diff --git a/src/Compiler/Checking/ConstraintSolver.fs b/src/Compiler/Checking/ConstraintSolver.fs index 478508b3d3c..439084f1d9e 100644 --- a/src/Compiler/Checking/ConstraintSolver.fs +++ b/src/Compiler/Checking/ConstraintSolver.fs @@ -1049,6 +1049,7 @@ and shouldWarnUselessNullCheck (csenv:ConstraintSolverEnv) = and getNullnessWarningRange (csenv: ConstraintSolverEnv) = match csenv.eContextInfo with | ContextInfo.NullnessCheckOfCapturedArg capturedArgRange -> capturedArgRange + | ContextInfo.MemberAccessOnNullable (objExprRange, _, _) -> objExprRange | _ -> csenv.m // nullness1: actual @@ -1124,7 +1125,12 @@ and SolveNullnessSubsumesNullness (csenv: ConstraintSolverEnv) m2 (trace: Option CompleteD | NullnessInfo.WithoutNull, NullnessInfo.WithNull -> if csenv.g.checkNullness then - WarnD(ConstraintSolverNullnessWarningWithTypes(csenv.DisplayEnv, ty1, ty2, n1, n2, getNullnessWarningRange csenv, m2)) + match csenv.eContextInfo with + | ContextInfo.MemberAccessOnNullable (objExprRange, memberName, bindingName) -> + let displayTy = replaceNullnessOfTy csenv.g.knownWithoutNull ty2 + WarnD(ConstraintSolverNullnessWarningOnDotAccess(csenv.DisplayEnv, displayTy, memberName, bindingName, objExprRange, m2)) + | _ -> + WarnD(ConstraintSolverNullnessWarningWithTypes(csenv.DisplayEnv, ty1, ty2, n1, n2, getNullnessWarningRange csenv, m2)) else CompleteD | Nullness.KnownFromConstructor, _ | _, Nullness.KnownFromConstructor -> CompleteD // Unreachable after Normalize() @@ -2075,7 +2081,7 @@ and SolveMemberConstraint (csenv: ConstraintSolverEnv) ignoreUnresolvedOverload let methOverloadResult, errors = trace.CollectThenUndoOrCommit (fun (a, _) -> Option.isSome a) - (fun trace -> ResolveOverloading csenv (WithTrace trace) nm ndeep (Some traitInfo) CallerArgs.Empty AccessibleFromEverywhere calledMethGroup false (Some (MustEqual retTy))) + (fun trace -> ResolveOverloading csenv (WithTrace trace) nm ndeep (Some traitInfo) None CallerArgs.Empty AccessibleFromEverywhere calledMethGroup false (Some (MustEqual retTy))) match anonRecdPropSearch, recdPropSearch, methOverloadResult with | Some (anonInfo, tinst, i), None, None -> @@ -3487,6 +3493,7 @@ and ResolveOverloadingCore methodName ndeep cx + objArgInfo (callerArgs: CallerArgs) ad (calledMethGroup: CalledMeth list) @@ -3503,6 +3510,12 @@ and ResolveOverloadingCore let infoReader = csenv.InfoReader let m = csenv.m + let withObjArgContext (env: ConstraintSolverEnv) = + match objArgInfo with + | Some (mObj, memberName, bindingName) -> + { env with eContextInfo = ContextInfo.MemberAccessOnNullable(mObj, memberName, bindingName) } + | None -> env + // Always take the return type into account for // -- op_Explicit, op_Implicit // -- candidate method sets that potentially use tupling of unfilled out args @@ -3516,13 +3529,14 @@ and ResolveOverloadingCore let exactMatchCandidates = candidates |> FilterEachThenUndo (fun newTrace calledMeth -> let csenv = { csenv with IsSpeculativeForMethodOverloading = true } + let objCsenv = withObjArgContext csenv let cxsln = AssumeMethodSolvesTrait csenv cx m (WithTrace newTrace) calledMeth CanMemberSigsMatchUpToCheck csenv permitOptArgs alwaysCheckReturn (TypesEquiv csenv ndeep (WithTrace newTrace) cxsln) // instantiations equivalent - (TypesMustSubsume csenv ndeep (WithTrace newTrace) cxsln m) // obj can subsume + (TypesMustSubsume objCsenv ndeep (WithTrace newTrace) cxsln m) // obj can subsume (ReturnTypesMustSubsumeOrConvert csenv ad ndeep (WithTrace newTrace) cxsln cx.IsSome m) // return can subsume or convert (ArgsEquivOrConvert csenv ad ndeep (WithTrace newTrace) cxsln cx.IsSome) // args exact reqdRetTyOpt @@ -3539,13 +3553,14 @@ and ResolveOverloadingCore let applicable = candidates |> FilterEachThenUndo (fun newTrace candidate -> let csenv = { csenv with IsSpeculativeForMethodOverloading = true } + let objCsenv = withObjArgContext csenv let cxsln = AssumeMethodSolvesTrait csenv cx m (WithTrace newTrace) candidate CanMemberSigsMatchUpToCheck csenv permitOptArgs alwaysCheckReturn (TypesEquiv csenv ndeep (WithTrace newTrace) cxsln) // instantiations equivalent - (TypesMustSubsume csenv ndeep (WithTrace newTrace) cxsln m) // obj can subsume + (TypesMustSubsume objCsenv ndeep (WithTrace newTrace) cxsln m) // obj can subsume (ReturnTypesMustSubsumeOrConvert csenv ad ndeep (WithTrace newTrace) cxsln cx.IsSome m) // return can subsume or convert (ArgsMustSubsumeOrConvertWithContextualReport csenv ad ndeep (WithTrace newTrace) cxsln cx.IsSome candidate) // args can subsume reqdRetTyOpt @@ -3561,13 +3576,14 @@ and ResolveOverloadingCore |> List.choose (fun calledMeth -> match CollectThenUndo (fun newTrace -> let csenv = { csenv with IsSpeculativeForMethodOverloading = true } + let objCsenv = withObjArgContext csenv let cxsln = AssumeMethodSolvesTrait csenv cx m (WithTrace newTrace) calledMeth CanMemberSigsMatchUpToCheck csenv permitOptArgs alwaysCheckReturn (TypesEquiv csenv ndeep (WithTrace newTrace) cxsln) - (TypesMustSubsume csenv ndeep (WithTrace newTrace) cxsln m) + (TypesMustSubsume objCsenv ndeep (WithTrace newTrace) cxsln m) (ReturnTypesMustSubsumeOrConvert csenv ad ndeep (WithTrace newTrace) cxsln cx.IsSome m) (ArgsMustSubsumeOrConvertWithContextualReport csenv ad ndeep (WithTrace newTrace) cxsln cx.IsSome calledMeth) reqdRetTyOpt @@ -3600,6 +3616,7 @@ and ResolveOverloading methodName // The name of the method being called, for error reporting ndeep // Depth of inference cx // We're doing overload resolution as part of constraint solving, where special rules apply for op_Explicit and op_Implicit constraints. + objArgInfo // Information about the receiver of a dotted call, used to refine nullness warnings. (callerArgs: CallerArgs) ad // The access domain of the caller, e.g. a module, type etc. calledMethGroup // The set of methods being called @@ -3679,7 +3696,7 @@ and ResolveOverloading match cachedHit with | Some result -> result | None -> - ResolveOverloadingCore csenv methodName ndeep cx callerArgs ad calledMethGroup candidates permitOptArgs reqdRetTyOpt isOpConversion retTyOpt anyHasOutArgs cacheKeyOpt cache + ResolveOverloadingCore csenv methodName ndeep cx objArgInfo callerArgs ad calledMethGroup candidates permitOptArgs reqdRetTyOpt isOpConversion retTyOpt anyHasOutArgs cacheKeyOpt cache // If we've got a candidate solution: make the final checks - no undo here! // Allow subsumption on arguments. Include the return type. @@ -3696,6 +3713,11 @@ and ResolveOverloading trackErrors { do! errors let cxsln = AssumeMethodSolvesTrait csenv cx m trace calledMeth + let objCsenv = + match objArgInfo with + | Some (mObj, memberName, bindingName) -> + { csenv with eContextInfo = ContextInfo.MemberAccessOnNullable(mObj, memberName, bindingName) } + | None -> csenv match calledMethTrace with | NoTrace -> let! _usesTDC = @@ -3704,7 +3726,7 @@ and ResolveOverloading permitOptArgs true (TypesEquiv csenv ndeep trace cxsln) // instantiations equal - (TypesMustSubsume csenv ndeep trace cxsln m) // obj can subsume + (TypesMustSubsume objCsenv ndeep trace cxsln m) // obj can subsume (ReturnTypesMustSubsumeOrConvert csenv ad ndeep trace cxsln cx.IsSome m) // return can subsume or convert (ArgsMustSubsumeOrConvert csenv ad ndeep trace cxsln cx.IsSome true) // args can subsume or convert reqdRetTyOpt @@ -3967,9 +3989,9 @@ and GetMostApplicableOverload csenv ndeep candidates applicableMeths calledMethG let err = FailOverloading csenv calledMethGroup reqdRetTyOpt isOpConversion callerArgs (PossibleCandidates(methodName, methods,cx)) m None, ErrorD err, NoTrace -let ResolveOverloadingForCall denv css m methodName callerArgs ad calledMethGroup permitOptArgs reqdRetTy = +let ResolveOverloadingForCall denv css m objArgInfo methodName callerArgs ad calledMethGroup permitOptArgs reqdRetTy = let csenv = MakeConstraintSolverEnv ContextInfo.NoContext css m denv - ResolveOverloading csenv NoTrace methodName 0 None callerArgs ad calledMethGroup permitOptArgs (Some reqdRetTy) + ResolveOverloading csenv NoTrace methodName 0 None objArgInfo callerArgs ad calledMethGroup permitOptArgs (Some reqdRetTy) /// This is used before analyzing the types of arguments in a single overload resolution let UnifyUniqueOverloading @@ -3977,12 +3999,18 @@ let UnifyUniqueOverloading css m callerArgCounts + objArgInfo methodName ad (calledMethGroup: CalledMeth list) reqdRetTy // The expected return type, if known = let csenv = MakeConstraintSolverEnv ContextInfo.NoContext css m denv + let objCsenv = + match objArgInfo with + | Some (mObj, memberName, bindingName) -> + { csenv with eContextInfo = ContextInfo.MemberAccessOnNullable(mObj, memberName, bindingName) } + | None -> csenv let m = csenv.m // See what candidates we have based on name and arity let candidates = calledMethGroup |> List.filter (fun cmeth -> cmeth.IsCandidate(m, ad)) @@ -3996,7 +4024,7 @@ let UnifyUniqueOverloading true // permitOptArgs true // always check return type (TypesEquiv csenv ndeep NoTrace None) - (TypesMustSubsume csenv ndeep NoTrace None m) + (TypesMustSubsume objCsenv ndeep NoTrace None m) (ReturnTypesMustSubsumeOrConvert csenv ad ndeep NoTrace None false m) (ArgsMustSubsumeOrConvert csenv ad ndeep NoTrace None false false) (Some reqdRetTy) diff --git a/src/Compiler/Checking/ConstraintSolver.fsi b/src/Compiler/Checking/ConstraintSolver.fsi index d3a8c0fb89a..feeb671e3b9 100644 --- a/src/Compiler/Checking/ConstraintSolver.fsi +++ b/src/Compiler/Checking/ConstraintSolver.fsi @@ -260,6 +260,7 @@ val ResolveOverloadingForCall: DisplayEnv -> ConstraintSolverState -> range -> + objArgInfo: (range * string * string option) option -> methodName: string -> callerArgs: CallerArgs -> AccessorDomain -> @@ -273,6 +274,7 @@ val UnifyUniqueOverloading: ConstraintSolverState -> range -> int * int -> + objArgInfo: (range * string * string option) option -> string -> AccessorDomain -> CalledMeth list -> diff --git a/src/Compiler/Checking/Expressions/CheckExpressions.fs b/src/Compiler/Checking/Expressions/CheckExpressions.fs index 04ef1b8d6f9..20c3edae3f8 100644 --- a/src/Compiler/Checking/Expressions/CheckExpressions.fs +++ b/src/Compiler/Checking/Expressions/CheckExpressions.fs @@ -10159,6 +10159,7 @@ and TcMethodApplication_UniqueOverloadInference objTyOpt isCheckingAttributeCall callerObjArgTys + objArgInfo methodName curriedCallerArgsOpt candidateMethsAndProps @@ -10235,7 +10236,7 @@ and TcMethodApplication_UniqueOverloadInference yield makeOneCalledMeth (minfo, pinfoOpt, false) ] let uniquelyResolved = - UnifyUniqueOverloading denv cenv.css mMethExpr callerArgCounts methodName ad preArgumentTypeCheckingCalledMethGroup returnTy + UnifyUniqueOverloading denv cenv.css mMethExpr callerArgCounts objArgInfo methodName ad preArgumentTypeCheckingCalledMethGroup returnTy uniquelyResolved, preArgumentTypeCheckingCalledMethGroup @@ -10393,6 +10394,16 @@ and TcMethodApplication let callerObjArgTys = objArgs |> List.map (tyOfExpr g) let calledMeths = calledMethsAndProps |> List.map fst + let objArgInfo = + match objArgs with + | [objExpr] -> + let bindingName = + match stripDebugPoints objExpr with + | Expr.Val (vref, _, _) -> Some vref.DisplayName + | _ -> None + Some (objExpr.Range, methodName, bindingName) + | _ -> None + // Uses of curried members are ALWAYS treated as if they are first class uses of members. // Curried members may not be overloaded (checked at use-site for curried members brought into scope through extension members) let curriedCallerArgs, exprTy, delayed = @@ -10423,7 +10434,7 @@ and TcMethodApplication // Extract what we know about the caller arguments, either type-directed if // no arguments are given or else based on the syntax of the arguments. let uniquelyResolved, preArgumentTypeCheckingCalledMethGroup = - TcMethodApplication_UniqueOverloadInference cenv env exprTy tyArgsOpt ad objTyOpt isCheckingAttributeCall callerObjArgTys methodName curriedCallerArgsOpt candidateMethsAndProps candidates mMethExpr mItem staticTyOpt + TcMethodApplication_UniqueOverloadInference cenv env exprTy tyArgsOpt ad objTyOpt isCheckingAttributeCall callerObjArgTys objArgInfo methodName curriedCallerArgsOpt candidateMethsAndProps candidates mMethExpr mItem staticTyOpt // STEP 2. Check arguments let unnamedCurriedCallerArgs, namedCurriedCallerArgs, lambdaVars, returnTy, tpenv = @@ -10462,7 +10473,7 @@ and TcMethodApplication CanonicalizePartialInferenceProblem cenv.css denv mItem (unnamedCurriedCallerArgs |> List.collectSquared (fun callerArg -> freeInTypeLeftToRight g false callerArg.CallerArgumentType)) - let result, errors = ResolveOverloadingForCall denv cenv.css mMethExpr methodName callerArgs ad postArgumentTypeCheckingCalledMethGroup true returnTy + let result, errors = ResolveOverloadingForCall denv cenv.css mMethExpr objArgInfo methodName callerArgs ad postArgumentTypeCheckingCalledMethGroup true returnTy match afterResolution, result with | AfterResolution.DoNothing, _ -> () diff --git a/src/Compiler/Driver/CompilerDiagnostics.fs b/src/Compiler/Driver/CompilerDiagnostics.fs index 4ecbfc081ef..8d764e93781 100644 --- a/src/Compiler/Driver/CompilerDiagnostics.fs +++ b/src/Compiler/Driver/CompilerDiagnostics.fs @@ -170,6 +170,7 @@ type Exception with | ConstraintSolverNullnessWarningEquivWithTypes(_, _, _, _, _, m, _) | ConstraintSolverNullnessWarningWithTypes(_, _, _, _, _, m, _) | ConstraintSolverNullnessWarningWithType(_, _, _, m, _) + | ConstraintSolverNullnessWarningOnDotAccess(_, _, _, _, m, _) | ConstraintSolverNullnessWarning(_, m, _) | ConstraintSolverTypesNotInEqualityRelation(_, _, _, m, _, _) | ConstraintSolverError(_, m, _) @@ -348,6 +349,7 @@ type Exception with | ConstraintSolverNullnessWarningEquivWithTypes _ -> 3261 | ConstraintSolverNullnessWarningWithTypes _ -> 3261 | ConstraintSolverNullnessWarningWithType _ -> 3261 + | ConstraintSolverNullnessWarningOnDotAccess _ -> 3261 | ConstraintSolverNullnessWarning _ -> 3261 | InvalidAttributeTargetForLanguageElement _ -> 842 | _ -> 193 @@ -446,6 +448,8 @@ module OldStyleMessages = let ConstraintSolverNullnessWarningEquivWithTypesE () = Message("ConstraintSolverNullnessWarningEquivWithTypes", "%s") let ConstraintSolverNullnessWarningWithTypesE () = Message("ConstraintSolverNullnessWarningWithTypes", "%s%s") let ConstraintSolverNullnessWarningWithTypeE () = Message("ConstraintSolverNullnessWarningWithType", "%s") + let ConstraintSolverNullnessWarningOnDotAccessE () = Message("ConstraintSolverNullnessWarningOnDotAccess", "%s%s") + let ConstraintSolverNullnessWarningOnDotAccessWithBindingE () = Message("ConstraintSolverNullnessWarningOnDotAccessWithBinding", "%s%s%s") let ConstraintSolverNullnessWarningE () = Message("ConstraintSolverNullnessWarning", "%s") let ConstraintSolverTypesNotInEqualityRelation1E () = Message("ConstraintSolverTypesNotInEqualityRelation1", "%s%s") let ConstraintSolverTypesNotInEqualityRelation2E () = Message("ConstraintSolverTypesNotInEqualityRelation2", "%s%s") @@ -731,6 +735,15 @@ type Exception with if m.StartLine <> m2.StartLine then os.Append(SeeAlsoE().Format(stringOfRange m)) |> ignore + | ConstraintSolverNullnessWarningOnDotAccess(denv, objTy, memberName, bindingName, _, _) -> + let denv = { denv with showNullnessAnnotations = Some false } + let tyStr = NicePrint.minimalStringOfType denv objTy + match bindingName with + | Some name -> + os.Append(ConstraintSolverNullnessWarningOnDotAccessWithBindingE().Format memberName name tyStr) |> ignore + | None -> + os.Append(ConstraintSolverNullnessWarningOnDotAccessE().Format memberName tyStr) |> ignore + | ConstraintSolverNullnessWarning(msg, m, m2) -> os.Append(ConstraintSolverNullnessWarningE().Format(msg)) |> ignore @@ -792,6 +805,7 @@ type Exception with (match contextInfo with | ContextInfo.NoContext -> false | ContextInfo.NullnessCheckOfCapturedArg _ -> false + | ContextInfo.MemberAccessOnNullable _ -> false | _ -> true) -> e.Output(os, suggestNames) diff --git a/tests/FSharp.Compiler.ComponentTests/Language/Nullness/NullableReferenceTypesTests.fs b/tests/FSharp.Compiler.ComponentTests/Language/Nullness/NullableReferenceTypesTests.fs index a8b4b953d6b..2b0f9877db9 100644 --- a/tests/FSharp.Compiler.ComponentTests/Language/Nullness/NullableReferenceTypesTests.fs +++ b/tests/FSharp.Compiler.ComponentTests/Language/Nullness/NullableReferenceTypesTests.fs @@ -2418,6 +2418,6 @@ let main _ = |> typeCheckWithStrictNullness |> shouldFail |> withDiagnostics [ - Error 3261, Line 5, Col 13, Line 5, Col 23, + Error 3261, Line 5, Col 14, Line 5, Col 22, "Nullness warning: Possible dereference of a null value when accessing member 'Length' on a nullable expression of type 'string'." ] From 280918b5317e4600f051620397d0a4767b66123c Mon Sep 17 00:00:00 2001 From: Tomas Grosup Date: Fri, 15 May 2026 16:08:07 +0200 Subject: [PATCH 03/18] Update baselines, surface area, and release notes for dot-access nullness warning (#19658, sprint 3/3) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../.FSharp.Compiler.Service/11.0.100.md | 4 +++ src/Compiler/Driver/CompilerDiagnostics.fs | 18 +++++++--- .../Nullness/NullableCsharpImportTests.fs | 4 +-- .../Nullness/NullableReferenceTypesTests.fs | 2 +- ...s-syntax-positive.fs.checknulls_on.err.bsl | 36 +++++++++---------- 5 files changed, 39 insertions(+), 25 deletions(-) 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..cc08aa98aa3 100644 --- a/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md +++ b/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md @@ -59,6 +59,10 @@ * Added warning FS3884 when a function or delegate value is used as an interpolated string argument. ([PR #19289](https://github.com/dotnet/fsharp/pull/19289)) * Add `#version;;` directive to F# Interactive to display version and environment information. ([Issue #13307](https://github.com/dotnet/fsharp/issues/13307), [PR #19332](https://github.com/dotnet/fsharp/pull/19332)) +### Improved + +* Nullness warning FS3261 on dotted access (e.g. `x.Member`) now underlines the receiver expression and includes the member name and (when known) the binding name in the message. ([Issue #19658](https://github.com/dotnet/fsharp/issues/19658)) + ### Changed * Improvements in error and warning messages: new error FS3885 when `let!`/`use!` is the final expression in a computation expression; new warning FS3886 when a list literal contains a single tuple element (likely missing `;` separator); improved wording for FS0003, FS0025, FS0039, FS0072, FS0247, FS0597, FS0670, FS3082, and SRTP operator-not-in-scope hints. ([PR #19398](https://github.com/dotnet/fsharp/pull/19398)) diff --git a/src/Compiler/Driver/CompilerDiagnostics.fs b/src/Compiler/Driver/CompilerDiagnostics.fs index 8d764e93781..458e53a4f57 100644 --- a/src/Compiler/Driver/CompilerDiagnostics.fs +++ b/src/Compiler/Driver/CompilerDiagnostics.fs @@ -449,7 +449,10 @@ module OldStyleMessages = let ConstraintSolverNullnessWarningWithTypesE () = Message("ConstraintSolverNullnessWarningWithTypes", "%s%s") let ConstraintSolverNullnessWarningWithTypeE () = Message("ConstraintSolverNullnessWarningWithType", "%s") let ConstraintSolverNullnessWarningOnDotAccessE () = Message("ConstraintSolverNullnessWarningOnDotAccess", "%s%s") - let ConstraintSolverNullnessWarningOnDotAccessWithBindingE () = Message("ConstraintSolverNullnessWarningOnDotAccessWithBinding", "%s%s%s") + + let ConstraintSolverNullnessWarningOnDotAccessWithBindingE () = + Message("ConstraintSolverNullnessWarningOnDotAccessWithBinding", "%s%s%s") + let ConstraintSolverNullnessWarningE () = Message("ConstraintSolverNullnessWarning", "%s") let ConstraintSolverTypesNotInEqualityRelation1E () = Message("ConstraintSolverTypesNotInEqualityRelation1", "%s%s") let ConstraintSolverTypesNotInEqualityRelation2E () = Message("ConstraintSolverTypesNotInEqualityRelation2", "%s%s") @@ -736,13 +739,20 @@ type Exception with os.Append(SeeAlsoE().Format(stringOfRange m)) |> ignore | ConstraintSolverNullnessWarningOnDotAccess(denv, objTy, memberName, bindingName, _, _) -> - let denv = { denv with showNullnessAnnotations = Some false } + let denv = + { denv with + showNullnessAnnotations = Some false + } + let tyStr = NicePrint.minimalStringOfType denv objTy + match bindingName with | Some name -> - os.Append(ConstraintSolverNullnessWarningOnDotAccessWithBindingE().Format memberName name tyStr) |> ignore + os.Append(ConstraintSolverNullnessWarningOnDotAccessWithBindingE().Format memberName name tyStr) + |> ignore | None -> - os.Append(ConstraintSolverNullnessWarningOnDotAccessE().Format memberName tyStr) |> ignore + os.Append(ConstraintSolverNullnessWarningOnDotAccessE().Format memberName tyStr) + |> ignore | ConstraintSolverNullnessWarning(msg, m, m2) -> os.Append(ConstraintSolverNullnessWarningE().Format(msg)) |> ignore diff --git a/tests/FSharp.Compiler.ComponentTests/Language/Nullness/NullableCsharpImportTests.fs b/tests/FSharp.Compiler.ComponentTests/Language/Nullness/NullableCsharpImportTests.fs index d969afc538f..51df61f2e89 100644 --- a/tests/FSharp.Compiler.ComponentTests/Language/Nullness/NullableCsharpImportTests.fs +++ b/tests/FSharp.Compiler.ComponentTests/Language/Nullness/NullableCsharpImportTests.fs @@ -123,7 +123,7 @@ let s : string = d.Name // should warn here!! |> asLibrary |> typeCheckWithStrictNullness |> shouldFail - |> withDiagnostics [Error 3261, Line 6, Col 18, Line 6, Col 24, "Nullness warning: The types 'DirectoryInfo' and 'DirectoryInfo | null' do not have compatible nullability."] + |> withDiagnostics [Error 3261, Line 6, Col 18, Line 6, Col 19, "Nullness warning: Possible dereference of a null value when accessing member 'Name' on the nullable value 'd' of type 'DirectoryInfo'."] [] let ``Consumption of netstandard2 BCL api which is not annotated`` () = @@ -258,6 +258,6 @@ let theOtherOne = NullableClass.nullableImmArrayOfNotNullStrings |> compile |> shouldFail |> withDiagnostics - [Error 3261, Line 7, Col 18, Line 7, Col 36, "Nullness warning: The types 'string' and 'string | null' do not have compatible nullability."] + [Error 3261, Line 7, Col 18, Line 7, Col 29, "Nullness warning: Possible dereference of a null value when accessing member 'Length' on the nullable value 'firstString' of type 'string'."] diff --git a/tests/FSharp.Compiler.ComponentTests/Language/Nullness/NullableReferenceTypesTests.fs b/tests/FSharp.Compiler.ComponentTests/Language/Nullness/NullableReferenceTypesTests.fs index 2b0f9877db9..f74ff1bd683 100644 --- a/tests/FSharp.Compiler.ComponentTests/Language/Nullness/NullableReferenceTypesTests.fs +++ b/tests/FSharp.Compiler.ComponentTests/Language/Nullness/NullableReferenceTypesTests.fs @@ -295,7 +295,7 @@ let getLength (x: string | null) = x.Length |> asLibrary |> typeCheckWithStrictNullness |> shouldFail - |> withDiagnostics [Error 3261, Line 3, Col 36, Line 3, Col 44, "Nullness warning: The types 'string' and 'string | null' do not have compatible nullability."] + |> withDiagnostics [Error 3261, Line 3, Col 36, Line 3, Col 37, "Nullness warning: Possible dereference of a null value when accessing member 'Length' on the nullable value 'x' of type 'string'."] [] let ``Does report warning on obj to static member`` () = diff --git a/tests/FSharp.Compiler.ComponentTests/Language/Nullness/using-nullness-syntax-positive.fs.checknulls_on.err.bsl b/tests/FSharp.Compiler.ComponentTests/Language/Nullness/using-nullness-syntax-positive.fs.checknulls_on.err.bsl index aa8325df86b..5ad627e5131 100644 --- a/tests/FSharp.Compiler.ComponentTests/Language/Nullness/using-nullness-syntax-positive.fs.checknulls_on.err.bsl +++ b/tests/FSharp.Compiler.ComponentTests/Language/Nullness/using-nullness-syntax-positive.fs.checknulls_on.err.bsl @@ -4,25 +4,25 @@ using-nullness-syntax-positive.fs (13,18)-(13,24) typecheck error Nullness warni using-nullness-syntax-positive.fs (17,15)-(17,19) typecheck error Nullness warning: The type 'obj' does not support 'null'. using-nullness-syntax-positive.fs (18,15)-(18,19) typecheck error Nullness warning: The type 'String | null' supports 'null' but a non-null type is expected. using-nullness-syntax-positive.fs (19,15)-(19,21) typecheck error Nullness warning: The type 'int option' uses 'null' as a representation value but a non-null type is expected. -using-nullness-syntax-positive.fs (27,14)-(27,17) typecheck error Nullness warning: The types 'C' and 'C | null' do not have compatible nullability. -using-nullness-syntax-positive.fs (27,14)-(27,17) typecheck error Nullness warning: The types 'C' and 'C | null' do not have compatible nullability. -using-nullness-syntax-positive.fs (28,14)-(28,19) typecheck error Nullness warning: The types 'C' and 'C | null' do not have compatible nullability. -using-nullness-syntax-positive.fs (28,14)-(28,19) typecheck error Nullness warning: The types 'C' and 'C | null' do not have compatible nullability. -using-nullness-syntax-positive.fs (29,14)-(29,17) typecheck error Nullness warning: The types 'C' and 'C | null' do not have compatible nullability. -using-nullness-syntax-positive.fs (29,14)-(29,17) typecheck error Nullness warning: The types 'C' and 'C | null' do not have compatible nullability. +using-nullness-syntax-positive.fs (27,14)-(27,15) typecheck error Nullness warning: Possible dereference of a null value when accessing member 'P' on the nullable value 'c' of type 'C'. +using-nullness-syntax-positive.fs (27,14)-(27,15) typecheck error Nullness warning: Possible dereference of a null value when accessing member 'P' on the nullable value 'c' of type 'C'. +using-nullness-syntax-positive.fs (28,14)-(28,15) typecheck error Nullness warning: Possible dereference of a null value when accessing member 'M' on the nullable value 'c' of type 'C'. +using-nullness-syntax-positive.fs (28,14)-(28,15) typecheck error Nullness warning: Possible dereference of a null value when accessing member 'M' on the nullable value 'c' of type 'C'. +using-nullness-syntax-positive.fs (29,14)-(29,15) typecheck error Nullness warning: Possible dereference of a null value when accessing member 'M' on the nullable value 'c' of type 'C'. +using-nullness-syntax-positive.fs (29,14)-(29,15) typecheck error Nullness warning: Possible dereference of a null value when accessing member 'M' on the nullable value 'c' of type 'C'. using-nullness-syntax-positive.fs (43,26)-(43,30) typecheck error Nullness warning: The type 'String' does not support 'null'. -using-nullness-syntax-positive.fs (85,63)-(85,70) typecheck error Nullness warning: The types 'C' and 'C | null' do not have compatible nullability. -using-nullness-syntax-positive.fs (85,63)-(85,70) typecheck error Nullness warning: The types 'C' and 'C | null' do not have compatible nullability. -using-nullness-syntax-positive.fs (86,81)-(86,90) typecheck error Nullness warning: The types 'C' and 'C | null' do not have compatible nullability. -using-nullness-syntax-positive.fs (86,81)-(86,90) typecheck error Nullness warning: The types 'C' and 'C | null' do not have compatible nullability. -using-nullness-syntax-positive.fs (86,92)-(86,102) typecheck error Nullness warning: The types 'C' and 'C | null' do not have compatible nullability. -using-nullness-syntax-positive.fs (86,92)-(86,102) typecheck error Nullness warning: The types 'C' and 'C | null' do not have compatible nullability. -using-nullness-syntax-positive.fs (91,53)-(91,60) typecheck error Nullness warning: The types 'C' and 'C | null' do not have compatible nullability. -using-nullness-syntax-positive.fs (91,53)-(91,60) typecheck error Nullness warning: The types 'C' and 'C | null' do not have compatible nullability. -using-nullness-syntax-positive.fs (92,72)-(92,81) typecheck error Nullness warning: The types 'C' and 'C | null' do not have compatible nullability. -using-nullness-syntax-positive.fs (92,72)-(92,81) typecheck error Nullness warning: The types 'C' and 'C | null' do not have compatible nullability. -using-nullness-syntax-positive.fs (92,83)-(92,93) typecheck error Nullness warning: The types 'C' and 'C | null' do not have compatible nullability. -using-nullness-syntax-positive.fs (92,83)-(92,93) typecheck error Nullness warning: The types 'C' and 'C | null' do not have compatible nullability. +using-nullness-syntax-positive.fs (85,63)-(85,64) typecheck error Nullness warning: Possible dereference of a null value when accessing member 'Value' on the nullable value 's' of type 'C'. +using-nullness-syntax-positive.fs (85,63)-(85,64) typecheck error Nullness warning: Possible dereference of a null value when accessing member 'Value' on the nullable value 's' of type 'C'. +using-nullness-syntax-positive.fs (86,81)-(86,84) typecheck error Nullness warning: Possible dereference of a null value when accessing member 'Value' on the nullable value 'fmt' of type 'C'. +using-nullness-syntax-positive.fs (86,81)-(86,84) typecheck error Nullness warning: Possible dereference of a null value when accessing member 'Value' on the nullable value 'fmt' of type 'C'. +using-nullness-syntax-positive.fs (86,92)-(86,96) typecheck error Nullness warning: Possible dereference of a null value when accessing member 'Value' on the nullable value 'arg1' of type 'C'. +using-nullness-syntax-positive.fs (86,92)-(86,96) typecheck error Nullness warning: Possible dereference of a null value when accessing member 'Value' on the nullable value 'arg1' of type 'C'. +using-nullness-syntax-positive.fs (91,53)-(91,54) typecheck error Nullness warning: Possible dereference of a null value when accessing member 'Value' on the nullable value 's' of type 'C'. +using-nullness-syntax-positive.fs (91,53)-(91,54) typecheck error Nullness warning: Possible dereference of a null value when accessing member 'Value' on the nullable value 's' of type 'C'. +using-nullness-syntax-positive.fs (92,72)-(92,75) typecheck error Nullness warning: Possible dereference of a null value when accessing member 'Value' on the nullable value 'fmt' of type 'C'. +using-nullness-syntax-positive.fs (92,72)-(92,75) typecheck error Nullness warning: Possible dereference of a null value when accessing member 'Value' on the nullable value 'fmt' of type 'C'. +using-nullness-syntax-positive.fs (92,83)-(92,87) typecheck error Nullness warning: Possible dereference of a null value when accessing member 'Value' on the nullable value 'arg1' of type 'C'. +using-nullness-syntax-positive.fs (92,83)-(92,87) typecheck error Nullness warning: Possible dereference of a null value when accessing member 'Value' on the nullable value 'arg1' of type 'C'. using-nullness-syntax-positive.fs (120,32)-(120,36) typecheck error Nullness warning: The type 'obj array' does not support 'null'. using-nullness-syntax-positive.fs (129,4)-(129,34) typecheck error Nullness warning: The type 'String' does not support 'null'. using-nullness-syntax-positive.fs (134,5)-(134,44) typecheck error Nullness warning: The type 'String' does not support 'null'. From 8257d527203cc59061cf18da0227313861bd6256 Mon Sep 17 00:00:00 2001 From: Tomas Grosup Date: Tue, 19 May 2026 11:58:30 +0200 Subject: [PATCH 04/18] Address review findings: dedupe helper, trim comments, add edge-case tests - Extract withObjArgContext helper in ResolveOverloading and UnifyUniqueOverloading, replacing 3 inline match/rewrap duplicates - Trim MemberAccessOnNullable doc comment to 1 line (named labels self-document) - Remove redundant replaceNullnessOfTy (showNullnessAnnotations=false handles display) - Tighten release notes: 'dotted method or property access' (not indexer/records) - Add 5 new edge-case tests: chained access, mutable receiver, overloaded method, extension method, static call negative test - Clean up existing 3 tests: remove EntryPoint boilerplate, minimal source code Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../.FSharp.Compiler.Service/11.0.100.md | 2 +- src/Compiler/Checking/ConstraintSolver.fs | 28 +++--- src/Compiler/Checking/ConstraintSolver.fsi | 5 +- .../Nullness/NullableReferenceTypesTests.fs | 99 ++++++++++++++----- 4 files changed, 92 insertions(+), 42 deletions(-) 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 cc08aa98aa3..8c5409babd2 100644 --- a/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md +++ b/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md @@ -61,7 +61,7 @@ ### Improved -* Nullness warning FS3261 on dotted access (e.g. `x.Member`) now underlines the receiver expression and includes the member name and (when known) the binding name in the message. ([Issue #19658](https://github.com/dotnet/fsharp/issues/19658)) +* Nullness warning FS3261 on dotted method or property access (e.g. `x.Member`) now underlines the receiver expression and includes the member name and (when known) the binding name in the message. ([Issue #19658](https://github.com/dotnet/fsharp/issues/19658)) ### Changed diff --git a/src/Compiler/Checking/ConstraintSolver.fs b/src/Compiler/Checking/ConstraintSolver.fs index 439084f1d9e..f392bb6f339 100644 --- a/src/Compiler/Checking/ConstraintSolver.fs +++ b/src/Compiler/Checking/ConstraintSolver.fs @@ -186,10 +186,7 @@ type ContextInfo = /// The range points to the original argument location. | NullnessCheckOfCapturedArg of range - /// The type equation comes from a member access on a nullable receiver expression. - /// The first range is the receiver/object-expression range (e.g. the `x` in `x.PadLeft`). - /// The string is the resolved member/method name. - /// The optional string is the binding name when the receiver is a simple value reference (e.g. "x"). + /// Obj-argument type check in a dotted member access on a nullable receiver. | MemberAccessOnNullable of objExprRange: range * memberName: string * bindingName: string option /// Captures relevant information for a particular failed overload resolution. @@ -1127,8 +1124,7 @@ and SolveNullnessSubsumesNullness (csenv: ConstraintSolverEnv) m2 (trace: Option if csenv.g.checkNullness then match csenv.eContextInfo with | ContextInfo.MemberAccessOnNullable (objExprRange, memberName, bindingName) -> - let displayTy = replaceNullnessOfTy csenv.g.knownWithoutNull ty2 - WarnD(ConstraintSolverNullnessWarningOnDotAccess(csenv.DisplayEnv, displayTy, memberName, bindingName, objExprRange, m2)) + WarnD(ConstraintSolverNullnessWarningOnDotAccess(csenv.DisplayEnv, ty2, memberName, bindingName, objExprRange, m2)) | _ -> WarnD(ConstraintSolverNullnessWarningWithTypes(csenv.DisplayEnv, ty1, ty2, n1, n2, getNullnessWarningRange csenv, m2)) else @@ -3627,6 +3623,12 @@ and ResolveOverloading let g = csenv.g let m = csenv.m + let withObjArgContext (env: ConstraintSolverEnv) = + match objArgInfo with + | Some (mObj, memberName, bindingName) -> + { env with eContextInfo = ContextInfo.MemberAccessOnNullable(mObj, memberName, bindingName) } + | None -> env + let isOpConversion = (methodName = "op_Explicit") || (methodName = "op_Implicit") @@ -3713,11 +3715,7 @@ and ResolveOverloading trackErrors { do! errors let cxsln = AssumeMethodSolvesTrait csenv cx m trace calledMeth - let objCsenv = - match objArgInfo with - | Some (mObj, memberName, bindingName) -> - { csenv with eContextInfo = ContextInfo.MemberAccessOnNullable(mObj, memberName, bindingName) } - | None -> csenv + let objCsenv = withObjArgContext csenv match calledMethTrace with | NoTrace -> let! _usesTDC = @@ -4006,11 +4004,11 @@ let UnifyUniqueOverloading reqdRetTy // The expected return type, if known = let csenv = MakeConstraintSolverEnv ContextInfo.NoContext css m denv - let objCsenv = + let withObjArgContext (env: ConstraintSolverEnv) = match objArgInfo with | Some (mObj, memberName, bindingName) -> - { csenv with eContextInfo = ContextInfo.MemberAccessOnNullable(mObj, memberName, bindingName) } - | None -> csenv + { env with eContextInfo = ContextInfo.MemberAccessOnNullable(mObj, memberName, bindingName) } + | None -> env let m = csenv.m // See what candidates we have based on name and arity let candidates = calledMethGroup |> List.filter (fun cmeth -> cmeth.IsCandidate(m, ad)) @@ -4024,7 +4022,7 @@ let UnifyUniqueOverloading true // permitOptArgs true // always check return type (TypesEquiv csenv ndeep NoTrace None) - (TypesMustSubsume objCsenv ndeep NoTrace None m) + (TypesMustSubsume (withObjArgContext csenv) ndeep NoTrace None m) (ReturnTypesMustSubsumeOrConvert csenv ad ndeep NoTrace None false m) (ArgsMustSubsumeOrConvert csenv ad ndeep NoTrace None false false) (Some reqdRetTy) diff --git a/src/Compiler/Checking/ConstraintSolver.fsi b/src/Compiler/Checking/ConstraintSolver.fsi index feeb671e3b9..b446e3246f2 100644 --- a/src/Compiler/Checking/ConstraintSolver.fsi +++ b/src/Compiler/Checking/ConstraintSolver.fsi @@ -66,10 +66,7 @@ type ContextInfo = /// The range points to the original argument location. | NullnessCheckOfCapturedArg of range - /// The type equation comes from a member access on a nullable receiver expression. - /// The first range is the receiver/object-expression range (e.g. the `x` in `x.PadLeft`). - /// The string is the resolved member/method name. - /// The optional string is the binding name when the receiver is a simple value reference (e.g. "x"). + /// Obj-argument type check in a dotted member access on a nullable receiver. | MemberAccessOnNullable of objExprRange: range * memberName: string * bindingName: string option /// Captures relevant information for a particular failed overload resolution. diff --git a/tests/FSharp.Compiler.ComponentTests/Language/Nullness/NullableReferenceTypesTests.fs b/tests/FSharp.Compiler.ComponentTests/Language/Nullness/NullableReferenceTypesTests.fs index f74ff1bd683..bea4925d928 100644 --- a/tests/FSharp.Compiler.ComponentTests/Language/Nullness/NullableReferenceTypesTests.fs +++ b/tests/FSharp.Compiler.ComponentTests/Language/Nullness/NullableReferenceTypesTests.fs @@ -2375,49 +2375,104 @@ let main _ = 0 |> verifyOutputContains [|"-1"|] [] -let ``Issue 19658 - nullness warning on dotted method access underlines the receiver and mentions the member`` () = - FSharp """module Program -[] -let main _ = - let x: string | null = "" - let y = x.PadLeft(1) - 0""" +let ``Issue 19658 - nullness warning on dotted method access underlines the receiver`` () = + FSharp """module MyLib +let f (x: string | null) = x.PadLeft(1)""" |> asLibrary |> typeCheckWithStrictNullness |> shouldFail |> withDiagnostics [ - Error 3261, Line 5, Col 13, Line 5, Col 14, + Error 3261, Line 2, Col 28, Line 2, Col 29, "Nullness warning: Possible dereference of a null value when accessing member 'PadLeft' on the nullable value 'x' of type 'string'." ] [] let ``Issue 19658 - nullness warning on dotted property access underlines the receiver`` () = - FSharp """module Program -[] -let main _ = - let x: string | null = "" - let n = x.Length - 0""" + FSharp """module MyLib +let f (x: string | null) = x.Length""" |> asLibrary |> typeCheckWithStrictNullness |> shouldFail |> withDiagnostics [ - Error 3261, Line 5, Col 13, Line 5, Col 14, + Error 3261, Line 2, Col 28, Line 2, Col 29, "Nullness warning: Possible dereference of a null value when accessing member 'Length' on the nullable value 'x' of type 'string'." ] [] -let ``Issue 19658 - nullness warning on dotted access of complex receiver omits binding name`` () = - FSharp """module Program +let ``Issue 19658 - nullness warning on complex receiver omits binding name`` () = + FSharp """module MyLib let getStr () : string | null = "" -[] -let main _ = - let n = (getStr()).Length - 0""" +let f () = (getStr()).Length""" |> asLibrary |> typeCheckWithStrictNullness |> shouldFail |> withDiagnostics [ - Error 3261, Line 5, Col 14, Line 5, Col 22, + Error 3261, Line 3, Col 13, Line 3, Col 21, "Nullness warning: Possible dereference of a null value when accessing member 'Length' on a nullable expression of type 'string'." ] + +[] +let ``Issue 19658 - chained access warns only on the nullable receiver`` () = + FSharp """module MyLib +let f (x: string | null) = x.Trim().Length""" + |> asLibrary + |> typeCheckWithStrictNullness + |> shouldFail + |> withDiagnostics [ + Error 3261, Line 2, Col 28, Line 2, Col 29, + "Nullness warning: Possible dereference of a null value when accessing member 'Trim' on the nullable value 'x' of type 'string'." + ] + +[] +let ``Issue 19658 - mutable receiver shows binding name`` () = + FSharp """module MyLib +let f () = + let mutable s: string | null = null + s.Length""" + |> asLibrary + |> typeCheckWithStrictNullness + |> shouldFail + |> withDiagnostics [ + Error 3261, Line 4, Col 5, Line 4, Col 6, + "Nullness warning: Possible dereference of a null value when accessing member 'Length' on the nullable value 's' of type 'string'." + ] + +[] +let ``Issue 19658 - overloaded method does not double-fire`` () = + FSharp """module MyLib +let f (x: string | null) = x.Split(',')""" + |> asLibrary + |> typeCheckWithStrictNullness + |> shouldFail + |> withDiagnostics [ + Error 3261, Line 2, Col 28, Line 2, Col 29, + "Nullness warning: Possible dereference of a null value when accessing member 'Split' on the nullable value 'x' of type 'string'." + ] + +[] +let ``Issue 19658 - extension method on nullable receiver`` () = + FSharp """module MyLib +open System.Linq +let f (xs: System.Collections.Generic.List | null) = xs.First()""" + |> asLibrary + |> typeCheckWithStrictNullness + |> shouldFail + |> withDiagnostics [ + Error 3261, Line 3, Col 59, Line 3, Col 61, + "Nullness warning: Possible dereference of a null value when accessing member 'First' on the nullable value 'xs' of type 'int seq'." + ] + +[] +let ``Issue 19658 - static call still uses generic nullness warning`` () = + FSharp """module MyLib +type C() = + static member Do(x: string) = () +let x: string | null = "" +C.Do(x)""" + |> asLibrary + |> typeCheckWithStrictNullness + |> shouldFail + |> withDiagnostics [ + Error 3261, Line 5, Col 6, Line 5, Col 7, + "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." + ] From 5e73972917e8a08b0e3848bc08b7b7fc82252d6c Mon Sep 17 00:00:00 2001 From: Tomas Grosup Date: Wed, 20 May 2026 15:40:25 +0200 Subject: [PATCH 05/18] Strip MemberAccessOnNullable context before recursing in SolveTypeSubsumesType (#19658) The MemberAccessOnNullable context is meant only for the receiver's outer nullness check. It was leaking into recursive subsumption/equality checks on tuple components, fun-type domains, and especially generic type arguments, producing a dot-access warning with the wrong type (the inner type-arg) and pinning unrelated deep nullness warnings to the receiver range. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Compiler/Checking/ConstraintSolver.fs | 57 +++++++++++-------- .../Nullness/NullableReferenceTypesTests.fs | 13 +++++ 2 files changed, 46 insertions(+), 24 deletions(-) diff --git a/src/Compiler/Checking/ConstraintSolver.fs b/src/Compiler/Checking/ConstraintSolver.fs index f392bb6f339..b84fb85d102 100644 --- a/src/Compiler/Checking/ConstraintSolver.fs +++ b/src/Compiler/Checking/ConstraintSolver.fs @@ -1499,6 +1499,15 @@ and SolveTypeSubsumesType (csenv: ConstraintSolverEnv) ndeep m2 (trace: Optional let g = csenv.g let canShortcut = not trace.HasTrace + // MemberAccessOnNullable describes the OUTER receiver's nullness only. + // Strip it before recursing into type-args/tuple/fun components so deep + // nullness mismatches don't get reported as if they were the receiver's + // (wrong message AND wrong range). See issue #19658. + let csenvInner = + match csenv.eContextInfo with + | ContextInfo.MemberAccessOnNullable _ -> { csenv with eContextInfo = ContextInfo.NoContext } + | _ -> csenv + // 'a :> objnull ---> if isObjNullTy g ty1 then CompleteD @@ -1515,85 +1524,85 @@ and SolveTypeSubsumesType (csenv: ConstraintSolverEnv) ndeep m2 (trace: Optional match sty1, sty2 with | TType_var (tp1, nullness1) , _ -> match aenv.EquivTypars.TryFind tp1 with - | Some tpTy1 -> SolveTypeSubsumesType csenv ndeep m2 trace cxsln tpTy1 ty2 + | Some tpTy1 -> SolveTypeSubsumesType csenvInner ndeep m2 trace cxsln tpTy1 ty2 | _ -> match sty2 with | TType_var (r2, nullness2) when typarEq tp1 r2 -> - SolveNullnessEquiv csenv m2 trace ty1 ty2 nullness1 nullness2 + SolveNullnessEquiv csenvInner m2 trace ty1 ty2 nullness1 nullness2 | TType_var (r2, nullness2) when not csenv.MatchingOnly -> trackErrors { - do! SolveTyparSubtypeOfType csenv ndeep m2 trace r2 ty1 + do! SolveTyparSubtypeOfType csenvInner ndeep m2 trace r2 ty1 let nullnessAfterSolution2 = combineNullness (nullnessOfTy g sty2) nullness2 - do! SolveNullnessSubsumesNullness csenv m2 trace ty1 ty2 nullness1 nullnessAfterSolution2 + do! SolveNullnessSubsumesNullness csenvInner m2 trace ty1 ty2 nullness1 nullnessAfterSolution2 } - | _ -> SolveTypeEqualsTypeKeepAbbrevsWithCxsln csenv ndeep m2 trace cxsln ty1 ty2 + | _ -> SolveTypeEqualsTypeKeepAbbrevsWithCxsln csenvInner ndeep m2 trace cxsln ty1 ty2 | _, TType_var (r2, nullness2) when not csenv.MatchingOnly -> trackErrors { - do! SolveTyparSubtypeOfType csenv ndeep m2 trace r2 ty1 + do! SolveTyparSubtypeOfType csenvInner ndeep m2 trace r2 ty1 let nullnessAfterSolution2 = combineNullness (nullnessOfTy g sty2) nullness2 - do! SolveNullnessSubsumesNullness csenv m2 trace ty1 ty2 (nullnessOfTy g sty1) nullnessAfterSolution2 + do! SolveNullnessSubsumesNullness csenvInner m2 trace ty1 ty2 (nullnessOfTy g sty1) nullnessAfterSolution2 } | TType_tuple (tupInfo1, l1), TType_tuple (tupInfo2, l2) -> if evalTupInfoIsStruct tupInfo1 <> evalTupInfoIsStruct tupInfo2 then ErrorD (ConstraintSolverError(FSComp.SR.tcTupleStructMismatch(), csenv.m, m2)) else - SolveTypeEqualsTypeEqns csenv ndeep m2 trace cxsln l1 l2 (* nb. can unify since no variance *) + SolveTypeEqualsTypeEqns csenvInner ndeep m2 trace cxsln l1 l2 (* nb. can unify since no variance *) | TType_fun (domainTy1, rangeTy1, nullness1), TType_fun (domainTy2, rangeTy2, nullness2) -> // nb. can unify since no variance trackErrors { - do! SolveFunTypeEqn csenv ndeep m2 trace cxsln domainTy1 domainTy2 rangeTy1 rangeTy2 - do! SolveNullnessSubsumesNullness csenv m2 trace ty1 ty2 nullness1 nullness2 + do! SolveFunTypeEqn csenvInner ndeep m2 trace cxsln domainTy1 domainTy2 rangeTy1 rangeTy2 + do! SolveNullnessSubsumesNullness csenvInner m2 trace ty1 ty2 nullness1 nullness2 } | TType_anon (anonInfo1, l1), TType_anon (anonInfo2, l2) -> trackErrors { - do! SolveAnonInfoEqualsAnonInfo csenv m2 anonInfo1 anonInfo2 - do! SolveTypeEqualsTypeEqns csenv ndeep m2 trace cxsln l1 l2 + do! SolveAnonInfoEqualsAnonInfo csenvInner m2 anonInfo1 anonInfo2 + do! SolveTypeEqualsTypeEqns csenvInner ndeep m2 trace cxsln l1 l2 } | TType_measure ms1, TType_measure ms2 -> - UnifyMeasures csenv trace ms1 ms2 + UnifyMeasures csenvInner trace ms1 ms2 // Enforce the identities float=float<1>, float32=float32<1> and decimal=decimal<1> | _, TType_app (tc2, [ms2], _) when tc2.IsMeasureableReprTycon && typeEquiv csenv.g sty1 (reduceTyconRefMeasureableOrProvided csenv.g tc2 [ms2]) -> trackErrors { - do! SolveTypeEqualsTypeKeepAbbrevsWithCxsln csenv ndeep m2 trace cxsln ms2 (TType_measure(Measure.One m2)) - do! SolveNullnessSubsumesNullness csenv m2 trace ty1 ty2 (nullnessOfTy g sty1) (nullnessOfTy g sty2) + do! SolveTypeEqualsTypeKeepAbbrevsWithCxsln csenvInner ndeep m2 trace cxsln ms2 (TType_measure(Measure.One m2)) + do! SolveNullnessSubsumesNullness csenvInner m2 trace ty1 ty2 (nullnessOfTy g sty1) (nullnessOfTy g sty2) } | TType_app (tc1, [ms1], _), _ when tc1.IsMeasureableReprTycon && typeEquiv csenv.g sty2 (reduceTyconRefMeasureableOrProvided csenv.g tc1 [ms1]) -> trackErrors { - do! SolveTypeEqualsTypeKeepAbbrevsWithCxsln csenv ndeep m2 trace cxsln ms1 (TType_measure(Measure.One m2)) - do! SolveNullnessSubsumesNullness csenv m2 trace ty1 ty2 (nullnessOfTy g sty1) (nullnessOfTy g sty2) + do! SolveTypeEqualsTypeKeepAbbrevsWithCxsln csenvInner ndeep m2 trace cxsln ms1 (TType_measure(Measure.One m2)) + do! SolveNullnessSubsumesNullness csenvInner m2 trace ty1 ty2 (nullnessOfTy g sty1) (nullnessOfTy g sty2) } // Special subsumption rule for byref tags | TType_app (tc1, l1, _) , TType_app (tc2, l2, _) when tyconRefEq g tc1 tc2 && g.byref2_tcr.CanDeref && tyconRefEq g g.byref2_tcr tc1 -> match l1, l2 with | [ h1; tag1 ], [ h2; tag2 ] -> trackErrors { - do! SolveTypeEqualsType csenv ndeep m2 trace None h1 h2 + do! SolveTypeEqualsType csenvInner ndeep m2 trace None h1 h2 match stripTyEqnsA csenv.g canShortcut tag1, stripTyEqnsA csenv.g canShortcut tag2 with | TType_app(tagc1, [], _), TType_app(tagc2, [], _) when (tyconRefEq g tagc2 g.byrefkind_InOut_tcr && (tyconRefEq g tagc1 g.byrefkind_In_tcr || tyconRefEq g tagc1 g.byrefkind_Out_tcr) ) -> () - | _ -> return! SolveTypeEqualsType csenv ndeep m2 trace cxsln tag1 tag2 + | _ -> return! SolveTypeEqualsType csenvInner ndeep m2 trace cxsln tag1 tag2 } - | _ -> SolveTypeEqualsTypeWithContravarianceEqns csenv ndeep m2 trace cxsln l1 l2 tc1.TyparsNoRange tc1 + | _ -> SolveTypeEqualsTypeWithContravarianceEqns csenvInner ndeep m2 trace cxsln l1 l2 tc1.TyparsNoRange tc1 // Special handling for delegate types - ignore nullness differences // Delegates from C# interfaces without nullable annotations should match F# events // See https://github.com/dotnet/fsharp/issues/18361 and https://github.com/dotnet/fsharp/issues/18349 | TType_app (tc1, l1, _), TType_app (tc2, l2, _) when tyconRefEq g tc1 tc2 && isDelegateTy g sty1 -> - SolveTypeEqualsTypeWithContravarianceEqns csenv ndeep m2 trace cxsln l1 l2 tc1.TyparsNoRange tc1 + SolveTypeEqualsTypeWithContravarianceEqns csenvInner ndeep m2 trace cxsln l1 l2 tc1.TyparsNoRange tc1 | TType_app (tc1, l1, _) , TType_app (tc2, l2, _) when tyconRefEq g tc1 tc2 -> trackErrors { - do! SolveTypeEqualsTypeWithContravarianceEqns csenv ndeep m2 trace cxsln l1 l2 tc1.TyparsNoRange tc1 + do! SolveTypeEqualsTypeWithContravarianceEqns csenvInner ndeep m2 trace cxsln l1 l2 tc1.TyparsNoRange tc1 do! SolveNullnessSubsumesNullness csenv m2 trace ty1 ty2 (nullnessOfTy g sty1) (nullnessOfTy g sty2) } | TType_ucase (uc1, l1), TType_ucase (uc2, l2) when g.unionCaseRefEq uc1 uc2 -> - SolveTypeEqualsTypeEqns csenv ndeep m2 trace cxsln l1 l2 + SolveTypeEqualsTypeEqns csenvInner ndeep m2 trace cxsln l1 l2 | _ -> // By now we know the type is not a variable type @@ -1620,7 +1629,7 @@ and SolveTypeSubsumesType (csenv: ConstraintSolverEnv) ndeep m2 (trace: Optional match tinst1 with | [elemTy1] -> let elemTy2 = destArrayTy g ty2 - SolveTypeEqualsTypeKeepAbbrevsWithCxsln csenv ndeep m2 trace cxsln elemTy1 elemTy2 + SolveTypeEqualsTypeKeepAbbrevsWithCxsln csenvInner ndeep m2 trace cxsln elemTy1 elemTy2 | _ -> error(InternalError("destArrayTy", m)) | _ -> diff --git a/tests/FSharp.Compiler.ComponentTests/Language/Nullness/NullableReferenceTypesTests.fs b/tests/FSharp.Compiler.ComponentTests/Language/Nullness/NullableReferenceTypesTests.fs index bea4925d928..415ad2aa71d 100644 --- a/tests/FSharp.Compiler.ComponentTests/Language/Nullness/NullableReferenceTypesTests.fs +++ b/tests/FSharp.Compiler.ComponentTests/Language/Nullness/NullableReferenceTypesTests.fs @@ -2462,6 +2462,19 @@ let f (xs: System.Collections.Generic.List | null) = xs.First()""" "Nullness warning: Possible dereference of a null value when accessing member 'First' on the nullable value 'xs' of type 'int seq'." ] +[] +let ``Issue 19658 - nested nullable generic receiver does not produce misleading inner-type-arg message`` () = + FSharp """module MyLib +let f (xs: System.Collections.Generic.List | null) = + xs.Count""" + |> asLibrary + |> typeCheckWithStrictNullness + |> shouldFail + |> withDiagnostics [ + Error 3261, Line 3, Col 5, Line 3, Col 7, + "Nullness warning: Possible dereference of a null value when accessing member 'Count' on the nullable value 'xs' of type 'System.Collections.Generic.List'." + ] + [] let ``Issue 19658 - static call still uses generic nullness warning`` () = FSharp """module MyLib From 08784a88ade124fb6e4a2c3d6cef36487bc55fcd Mon Sep 17 00:00:00 2001 From: Tomas Grosup Date: Wed, 20 May 2026 15:51:17 +0200 Subject: [PATCH 06/18] Gate objArgInfo construction and withObjArgContext on g.checkNullness (#19658) Defense-in-depth: avoid setting ContextInfo.MemberAccessOnNullable on csenv when nullness checking is off, so future code that branches on this context case without re-checking g.checkNullness can't accidentally fire. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Compiler/Checking/ConstraintSolver.fs | 12 ++++++------ .../Checking/Expressions/CheckExpressions.fs | 18 ++++++++++-------- .../Nullness/NullableReferenceTypesTests.fs | 10 ++++++++++ 3 files changed, 26 insertions(+), 14 deletions(-) diff --git a/src/Compiler/Checking/ConstraintSolver.fs b/src/Compiler/Checking/ConstraintSolver.fs index b84fb85d102..7e7f675a692 100644 --- a/src/Compiler/Checking/ConstraintSolver.fs +++ b/src/Compiler/Checking/ConstraintSolver.fs @@ -3517,9 +3517,9 @@ and ResolveOverloadingCore let withObjArgContext (env: ConstraintSolverEnv) = match objArgInfo with - | Some (mObj, memberName, bindingName) -> + | Some (mObj, memberName, bindingName) when env.g.checkNullness -> { env with eContextInfo = ContextInfo.MemberAccessOnNullable(mObj, memberName, bindingName) } - | None -> env + | _ -> env // Always take the return type into account for // -- op_Explicit, op_Implicit @@ -3634,9 +3634,9 @@ and ResolveOverloading let withObjArgContext (env: ConstraintSolverEnv) = match objArgInfo with - | Some (mObj, memberName, bindingName) -> + | Some (mObj, memberName, bindingName) when env.g.checkNullness -> { env with eContextInfo = ContextInfo.MemberAccessOnNullable(mObj, memberName, bindingName) } - | None -> env + | _ -> env let isOpConversion = (methodName = "op_Explicit") || @@ -4015,9 +4015,9 @@ let UnifyUniqueOverloading let csenv = MakeConstraintSolverEnv ContextInfo.NoContext css m denv let withObjArgContext (env: ConstraintSolverEnv) = match objArgInfo with - | Some (mObj, memberName, bindingName) -> + | Some (mObj, memberName, bindingName) when env.g.checkNullness -> { env with eContextInfo = ContextInfo.MemberAccessOnNullable(mObj, memberName, bindingName) } - | None -> env + | _ -> env let m = csenv.m // See what candidates we have based on name and arity let candidates = calledMethGroup |> List.filter (fun cmeth -> cmeth.IsCandidate(m, ad)) diff --git a/src/Compiler/Checking/Expressions/CheckExpressions.fs b/src/Compiler/Checking/Expressions/CheckExpressions.fs index 20c3edae3f8..eaeba05e4a3 100644 --- a/src/Compiler/Checking/Expressions/CheckExpressions.fs +++ b/src/Compiler/Checking/Expressions/CheckExpressions.fs @@ -10395,14 +10395,16 @@ and TcMethodApplication let calledMeths = calledMethsAndProps |> List.map fst let objArgInfo = - match objArgs with - | [objExpr] -> - let bindingName = - match stripDebugPoints objExpr with - | Expr.Val (vref, _, _) -> Some vref.DisplayName - | _ -> None - Some (objExpr.Range, methodName, bindingName) - | _ -> None + if g.checkNullness then + match objArgs with + | [objExpr] -> + let bindingName = + match stripDebugPoints objExpr with + | Expr.Val (vref, _, _) -> Some vref.DisplayName + | _ -> None + Some (objExpr.Range, methodName, bindingName) + | _ -> None + else None // Uses of curried members are ALWAYS treated as if they are first class uses of members. // Curried members may not be overloaded (checked at use-site for curried members brought into scope through extension members) diff --git a/tests/FSharp.Compiler.ComponentTests/Language/Nullness/NullableReferenceTypesTests.fs b/tests/FSharp.Compiler.ComponentTests/Language/Nullness/NullableReferenceTypesTests.fs index 415ad2aa71d..5845f09e236 100644 --- a/tests/FSharp.Compiler.ComponentTests/Language/Nullness/NullableReferenceTypesTests.fs +++ b/tests/FSharp.Compiler.ComponentTests/Language/Nullness/NullableReferenceTypesTests.fs @@ -2489,3 +2489,13 @@ C.Do(x)""" Error 3261, Line 5, Col 6, Line 5, Col 7, "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 ``Issue 19658 - no FS3261 when nullness checking is off`` () = + // Without strict nullness, x.PadLeft compiles clean - the new context + // must not introduce diagnostics when --checknulls is not enabled. + FSharp """module MyLib +let f (x: string) = x.PadLeft(1)""" + |> asLibrary + |> compile + |> shouldSucceed From 4490365ed6af6580c4150d20c3c255d51d828f28 Mon Sep 17 00:00:00 2001 From: Tomas Grosup Date: Wed, 20 May 2026 16:15:22 +0200 Subject: [PATCH 07/18] Emit SeeAlso pointing at member call when receiver is on a different line (#19658) Matches the convention of all sibling FS3261 handlers. When the receiver and the member access are on different source lines (fluent chains), the warning now hyperlinks the member range so IDEs surface both locations. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Compiler/Driver/CompilerDiagnostics.fs | 5 ++++- .../Nullness/NullableReferenceTypesTests.fs | 14 ++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/src/Compiler/Driver/CompilerDiagnostics.fs b/src/Compiler/Driver/CompilerDiagnostics.fs index 458e53a4f57..37924e674e5 100644 --- a/src/Compiler/Driver/CompilerDiagnostics.fs +++ b/src/Compiler/Driver/CompilerDiagnostics.fs @@ -738,7 +738,7 @@ type Exception with if m.StartLine <> m2.StartLine then os.Append(SeeAlsoE().Format(stringOfRange m)) |> ignore - | ConstraintSolverNullnessWarningOnDotAccess(denv, objTy, memberName, bindingName, _, _) -> + | ConstraintSolverNullnessWarningOnDotAccess(denv, objTy, memberName, bindingName, m, m2) -> let denv = { denv with showNullnessAnnotations = Some false @@ -754,6 +754,9 @@ type Exception with os.Append(ConstraintSolverNullnessWarningOnDotAccessE().Format memberName tyStr) |> ignore + if m.StartLine <> m2.StartLine || m.EndLine <> m2.EndLine then + os.Append(SeeAlsoE().Format(stringOfRange m2)) |> ignore + | ConstraintSolverNullnessWarning(msg, m, m2) -> os.Append(ConstraintSolverNullnessWarningE().Format(msg)) |> ignore diff --git a/tests/FSharp.Compiler.ComponentTests/Language/Nullness/NullableReferenceTypesTests.fs b/tests/FSharp.Compiler.ComponentTests/Language/Nullness/NullableReferenceTypesTests.fs index 5845f09e236..5c9ae3538fe 100644 --- a/tests/FSharp.Compiler.ComponentTests/Language/Nullness/NullableReferenceTypesTests.fs +++ b/tests/FSharp.Compiler.ComponentTests/Language/Nullness/NullableReferenceTypesTests.fs @@ -2499,3 +2499,17 @@ let f (x: string) = x.PadLeft(1)""" |> asLibrary |> compile |> shouldSucceed + +[] +let ``Issue 19658 - multi-line receiver emits SeeAlso pointing at member call site`` () = + FSharp """module MyLib +let f (x: string | null) = + x + .Length""" + |> asLibrary + |> typeCheckWithStrictNullness + |> shouldFail + |> withDiagnostics [ + Error 3261, Line 3, Col 5, Line 3, Col 6, + "Nullness warning: Possible dereference of a null value when accessing member 'Length' on the nullable value 'x' of type 'string'.. See also test.fs(3,4)-(4,15)." + ] From ed0b095e36fcef5352b568d7c49ddf4dadcf74c0 Mon Sep 17 00:00:00 2001 From: Tomas Grosup Date: Wed, 20 May 2026 16:22:29 +0200 Subject: [PATCH 08/18] Walk through Coerce wrappers and filter compiler-generated receiver names (#19658) - tryGetBindingName recurses through Expr.Op(TOp.Coerce, ...) so binding names surface for interface-upcast receivers. - vref.IsCompilerGenerated check prevents internal names (_arg1, matchValue, copyOfStruct, ...) from leaking into user-facing messages. - Document why ConstraintSolverNullnessWarningOnDotAccess uses showNullnessAnnotations = Some false (avoid redundant '| null' phrasing). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Checking/Expressions/CheckExpressions.fs | 15 +++++++-- src/Compiler/Driver/CompilerDiagnostics.fs | 5 +++ .../Nullness/NullableReferenceTypesTests.fs | 32 +++++++++++++++++++ 3 files changed, 49 insertions(+), 3 deletions(-) diff --git a/src/Compiler/Checking/Expressions/CheckExpressions.fs b/src/Compiler/Checking/Expressions/CheckExpressions.fs index eaeba05e4a3..9359a71d925 100644 --- a/src/Compiler/Checking/Expressions/CheckExpressions.fs +++ b/src/Compiler/Checking/Expressions/CheckExpressions.fs @@ -10398,10 +10398,19 @@ and TcMethodApplication if g.checkNullness then match objArgs with | [objExpr] -> - let bindingName = - match stripDebugPoints objExpr with - | Expr.Val (vref, _, _) -> Some vref.DisplayName + // Receiver may be wrapped in a debug point and/or an interface + // upcast (Expr.Op(TOp.Coerce, ...)). Drill through both. Filter + // out compiler-generated vals (e.g. _arg1, matchValue, + // copyOfStruct, tupledArg) so internal names never leak into + // user-facing nullness warnings. + let rec tryGetBindingName expr = + match stripDebugPoints expr with + | Expr.Val (vref, _, _) when not vref.IsCompilerGenerated -> + Some vref.DisplayName + | Expr.Op (TOp.Coerce, _, [innerExpr], _) -> + tryGetBindingName innerExpr | _ -> None + let bindingName = tryGetBindingName objExpr Some (objExpr.Range, methodName, bindingName) | _ -> None else None diff --git a/src/Compiler/Driver/CompilerDiagnostics.fs b/src/Compiler/Driver/CompilerDiagnostics.fs index 37924e674e5..b413f6a6f7c 100644 --- a/src/Compiler/Driver/CompilerDiagnostics.fs +++ b/src/Compiler/Driver/CompilerDiagnostics.fs @@ -739,6 +739,11 @@ type Exception with os.Append(SeeAlsoE().Format(stringOfRange m)) |> ignore | ConstraintSolverNullnessWarningOnDotAccess(denv, objTy, memberName, bindingName, m, m2) -> + // Sibling FS3261 handlers print raw type equations and show + // the | null annotation. This message already states "nullable + // value/expression", so we suppress the annotation to avoid + // redundant phrasings like "nullable value 'x' of type + // 'string | null'". Keep "Some false" intentional. let denv = { denv with showNullnessAnnotations = Some false diff --git a/tests/FSharp.Compiler.ComponentTests/Language/Nullness/NullableReferenceTypesTests.fs b/tests/FSharp.Compiler.ComponentTests/Language/Nullness/NullableReferenceTypesTests.fs index 5c9ae3538fe..cb9b3d63613 100644 --- a/tests/FSharp.Compiler.ComponentTests/Language/Nullness/NullableReferenceTypesTests.fs +++ b/tests/FSharp.Compiler.ComponentTests/Language/Nullness/NullableReferenceTypesTests.fs @@ -2513,3 +2513,35 @@ let f (x: string | null) = Error 3261, Line 3, Col 5, Line 3, Col 6, "Nullness warning: Possible dereference of a null value when accessing member 'Length' on the nullable value 'x' of type 'string'.. See also test.fs(3,4)-(4,15)." ] + +[] +let ``Issue 19658 - binding name surfaces through interface upcast Coerce`` () = + // The receiver xs typed as IEnumerable | null gets a + // Expr.Op(TOp.Coerce, ...) wrapper at the call site of GetEnumerator + // (interface-method dispatch). Binding name 'xs' must still surface. + FSharp """module MyLib +let f (xs: System.Collections.Generic.IEnumerable | null) = xs.GetEnumerator()""" + |> asLibrary + |> typeCheckWithStrictNullness + |> shouldFail + |> withDiagnostics [ + Error 3261, Line 2, Col 66, Line 2, Col 68, + "Nullness warning: Possible dereference of a null value when accessing member 'GetEnumerator' on the nullable value 'xs' of type 'System.Collections.Generic.IEnumerable'." + ] + +[] +let ``Issue 19658 - implicit 'this' receiver does not leak name`` () = + // 'this.Get()' is an Expr.App, not an Expr.Val, so tryGetBindingName + // returns None and the "expression" form fires - documents the contract + // that complex expressions get the "expression" wording. + FSharp """module MyLib +type C() = + member _.Get () : string | null = null + member this.Use () = this.Get().Length""" + |> asLibrary + |> typeCheckWithStrictNullness + |> shouldFail + |> withDiagnostics [ + Error 3261, Line 4, Col 26, Line 4, Col 36, + "Nullness warning: Possible dereference of a null value when accessing member 'Length' on a nullable expression of type 'string'." + ] From f043effbe0f580d8880bd2c28c6390ecb7ab800a Mon Sep 17 00:00:00 2001 From: Tomas Grosup Date: Wed, 20 May 2026 16:29:37 +0200 Subject: [PATCH 09/18] Add coverage + gap-documenting tests for dot-access nullness warning (#19658) - Property setter and piped lambda receiver: verify new message fires. - Indexer, F# record field, anonymous record, SRTP: pin current behavior of paths intentionally not routed through TcMethodApplication. Marked KNOWN GAP in test names. Widening coverage is tracked under #17409. - Anonymous record gap is documented via the FS3260 'does not support a nullness qualification' error since '{| ... |} | null' is rejected by the type system; this still pins that the dot-access path cannot fire. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Nullness/NullableReferenceTypesTests.fs | 93 +++++++++++++++++++ 1 file changed, 93 insertions(+) diff --git a/tests/FSharp.Compiler.ComponentTests/Language/Nullness/NullableReferenceTypesTests.fs b/tests/FSharp.Compiler.ComponentTests/Language/Nullness/NullableReferenceTypesTests.fs index cb9b3d63613..f81f0fe4410 100644 --- a/tests/FSharp.Compiler.ComponentTests/Language/Nullness/NullableReferenceTypesTests.fs +++ b/tests/FSharp.Compiler.ComponentTests/Language/Nullness/NullableReferenceTypesTests.fs @@ -2545,3 +2545,96 @@ type C() = Error 3261, Line 4, Col 26, Line 4, Col 36, "Nullness warning: Possible dereference of a null value when accessing member 'Length' on a nullable expression of type 'string'." ] + +// -------- Coverage tests (positive: new dot-access message is used) -------- + +[] +let ``Issue 19658 - property setter on nullable receiver uses new message`` () = + FSharp """module MyLib +open System.Text +let f (sb: StringBuilder | null) = sb.Length <- 0""" + |> asLibrary + |> typeCheckWithStrictNullness + |> shouldFail + |> withDiagnostics [ + Error 3261, Line 3, Col 36, Line 3, Col 38, + "Nullness warning: Possible dereference of a null value when accessing member 'Length' on the nullable value 'sb' of type 'StringBuilder'." + ] + +[] +let ``Issue 19658 - piped lambda receiver uses new message`` () = + FSharp """module MyLib +let f () = + let x: string | null = "" + x |> fun s -> s.Length""" + |> asLibrary + |> typeCheckWithStrictNullness + |> shouldFail + |> withDiagnostics [ + Error 3261, Line 4, Col 19, Line 4, Col 20, + "Nullness warning: Possible dereference of a null value when accessing member 'Length' on the nullable value 's' of type 'string'." + ] + +// -------- KNOWN GAP tests (negative: old generic message still used) -------- +// These pin the current behavior of paths intentionally NOT routed through +// TcMethodApplication. Widening coverage to these paths is a separate +// enhancement tracked under #17409. If a future change starts emitting the +// new dot-access message for any of these, that change must update these +// tests deliberately. + +[] +let ``Issue 19658 - KNOWN GAP indexer access falls back to generic nullness warning`` () = + FSharp """module MyLib +let f (x: string | null) = x.[0]""" + |> asLibrary + |> typeCheckWithStrictNullness + |> shouldFail + |> withDiagnostics [ + Error 3261, Line 2, Col 28, Line 2, Col 33, + "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 ``Issue 19658 - KNOWN GAP F# record field access falls back to generic nullness warning`` () = + FSharp """module MyLib +type R = { Field: int } +let f (r: R | null) = r.Field""" + |> asLibrary + |> typeCheckWithStrictNullness + |> shouldFail + |> withDiagnostics [ + Error 3261, Line 3, Col 23, Line 3, Col 30, + "Nullness warning: The types 'R' and 'R | null' do not have compatible nullability." + ] + +[] +let ``Issue 19658 - KNOWN GAP anonymous record type cannot be marked nullable`` () = + // Anonymous record types do not support a `| null` qualification at all, + // so the dot-access nullness path cannot fire on them. This pins the + // current behavior: instead of FS3261, the language rejects the type. + FSharp """module MyLib +let f (r: {| Field: int |} | null) = r.Field""" + |> asLibrary + |> typeCheckWithStrictNullness + |> shouldFail + |> withDiagnostics [ + Error 3260, Line 2, Col 11, Line 2, Col 34, + "The type '{| Field: int |}' does not support a nullness qualification." + ] + +[] +let ``Issue 19658 - KNOWN GAP SRTP member call still uses generic nullness warning`` () = + // SRTP-constrained members do not go through TcMethodApplication's + // overload resolution path; objArgInfo is None. The generic FS3261 + // message fires. If/when SRTP gets routed through the same path, this + // test should be updated. + FSharp """module MyLib +let inline f (x: ^T when ^T: (member Length: int)) : int = x.Length +let g (s: string | null) = f s""" + |> asLibrary + |> typeCheckWithStrictNullness + |> shouldFail + |> withDiagnostics [ + Error 3261, Line 3, Col 30, Line 3, Col 31, + "Nullness warning: The types 'string' and 'string | null' do not have compatible nullability." + ] From 0c15fbdaeb83f45b0eaecde2ac77e837590a4dfa Mon Sep 17 00:00:00 2001 From: Tomas Grosup Date: Wed, 20 May 2026 18:19:24 +0200 Subject: [PATCH 10/18] Introduce ObjArgInfo record and extract applyObjArgContext helper Replace the anonymous (range * string * string option) option tuple with a named ObjArgInfo record for readability, and extract the withObjArgContext closure (duplicated in ResolveOverloadingCore, ResolveOverloading, and UnifyUniqueOverloading) into a single top-level applyObjArgContext function. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Compiler/Checking/ConstraintSolver.fs | 43 +++++++++---------- src/Compiler/Checking/ConstraintSolver.fsi | 12 +++++- .../Checking/Expressions/CheckExpressions.fs | 4 +- 3 files changed, 33 insertions(+), 26 deletions(-) diff --git a/src/Compiler/Checking/ConstraintSolver.fs b/src/Compiler/Checking/ConstraintSolver.fs index 7e7f675a692..f27e81335b1 100644 --- a/src/Compiler/Checking/ConstraintSolver.fs +++ b/src/Compiler/Checking/ConstraintSolver.fs @@ -189,6 +189,14 @@ type ContextInfo = /// Obj-argument type check in a dotted member access on a nullable receiver. | MemberAccessOnNullable of objExprRange: range * memberName: string * bindingName: string option +/// Receiver information for a dotted member access, used to produce +/// targeted nullness warnings (e.g. "Possible dereference of null when +/// accessing member 'M' on the nullable value 'x'"). +type ObjArgInfo = + { ObjExprRange: range + MemberName: string + BindingName: string option } + /// Captures relevant information for a particular failed overload resolution. type OverloadInformation = { @@ -363,6 +371,14 @@ let MakeConstraintSolverEnv contextInfo css m denv = ExtraRigidTypars = emptyFreeTypars } +let applyObjArgContext (objArgInfo: ObjArgInfo option) (csenv: ConstraintSolverEnv) = + match objArgInfo with + | Some info when csenv.g.checkNullness -> + { csenv with + eContextInfo = ContextInfo.MemberAccessOnNullable(info.ObjExprRange, info.MemberName, info.BindingName) + } + | _ -> csenv + /// Check whether a type variable occurs in the r.h.s. of a type, e.g. to catch /// infinite equations such as /// 'a = 'a list @@ -3515,12 +3531,6 @@ and ResolveOverloadingCore let infoReader = csenv.InfoReader let m = csenv.m - let withObjArgContext (env: ConstraintSolverEnv) = - match objArgInfo with - | Some (mObj, memberName, bindingName) when env.g.checkNullness -> - { env with eContextInfo = ContextInfo.MemberAccessOnNullable(mObj, memberName, bindingName) } - | _ -> env - // Always take the return type into account for // -- op_Explicit, op_Implicit // -- candidate method sets that potentially use tupling of unfilled out args @@ -3534,7 +3544,7 @@ and ResolveOverloadingCore let exactMatchCandidates = candidates |> FilterEachThenUndo (fun newTrace calledMeth -> let csenv = { csenv with IsSpeculativeForMethodOverloading = true } - let objCsenv = withObjArgContext csenv + let objCsenv = applyObjArgContext objArgInfo csenv let cxsln = AssumeMethodSolvesTrait csenv cx m (WithTrace newTrace) calledMeth CanMemberSigsMatchUpToCheck csenv @@ -3558,7 +3568,7 @@ and ResolveOverloadingCore let applicable = candidates |> FilterEachThenUndo (fun newTrace candidate -> let csenv = { csenv with IsSpeculativeForMethodOverloading = true } - let objCsenv = withObjArgContext csenv + let objCsenv = applyObjArgContext objArgInfo csenv let cxsln = AssumeMethodSolvesTrait csenv cx m (WithTrace newTrace) candidate CanMemberSigsMatchUpToCheck csenv @@ -3581,7 +3591,7 @@ and ResolveOverloadingCore |> List.choose (fun calledMeth -> match CollectThenUndo (fun newTrace -> let csenv = { csenv with IsSpeculativeForMethodOverloading = true } - let objCsenv = withObjArgContext csenv + let objCsenv = applyObjArgContext objArgInfo csenv let cxsln = AssumeMethodSolvesTrait csenv cx m (WithTrace newTrace) calledMeth CanMemberSigsMatchUpToCheck csenv @@ -3632,12 +3642,6 @@ and ResolveOverloading let g = csenv.g let m = csenv.m - let withObjArgContext (env: ConstraintSolverEnv) = - match objArgInfo with - | Some (mObj, memberName, bindingName) when env.g.checkNullness -> - { env with eContextInfo = ContextInfo.MemberAccessOnNullable(mObj, memberName, bindingName) } - | _ -> env - let isOpConversion = (methodName = "op_Explicit") || (methodName = "op_Implicit") @@ -3724,7 +3728,7 @@ and ResolveOverloading trackErrors { do! errors let cxsln = AssumeMethodSolvesTrait csenv cx m trace calledMeth - let objCsenv = withObjArgContext csenv + let objCsenv = applyObjArgContext objArgInfo csenv match calledMethTrace with | NoTrace -> let! _usesTDC = @@ -4013,11 +4017,6 @@ let UnifyUniqueOverloading reqdRetTy // The expected return type, if known = let csenv = MakeConstraintSolverEnv ContextInfo.NoContext css m denv - let withObjArgContext (env: ConstraintSolverEnv) = - match objArgInfo with - | Some (mObj, memberName, bindingName) when env.g.checkNullness -> - { env with eContextInfo = ContextInfo.MemberAccessOnNullable(mObj, memberName, bindingName) } - | _ -> env let m = csenv.m // See what candidates we have based on name and arity let candidates = calledMethGroup |> List.filter (fun cmeth -> cmeth.IsCandidate(m, ad)) @@ -4031,7 +4030,7 @@ let UnifyUniqueOverloading true // permitOptArgs true // always check return type (TypesEquiv csenv ndeep NoTrace None) - (TypesMustSubsume (withObjArgContext csenv) ndeep NoTrace None m) + (TypesMustSubsume (applyObjArgContext objArgInfo csenv) ndeep NoTrace None m) (ReturnTypesMustSubsumeOrConvert csenv ad ndeep NoTrace None false m) (ArgsMustSubsumeOrConvert csenv ad ndeep NoTrace None false false) (Some reqdRetTy) diff --git a/src/Compiler/Checking/ConstraintSolver.fsi b/src/Compiler/Checking/ConstraintSolver.fsi index b446e3246f2..3baed64891a 100644 --- a/src/Compiler/Checking/ConstraintSolver.fsi +++ b/src/Compiler/Checking/ConstraintSolver.fsi @@ -69,6 +69,14 @@ type ContextInfo = /// Obj-argument type check in a dotted member access on a nullable receiver. | MemberAccessOnNullable of objExprRange: range * memberName: string * bindingName: string option +/// Receiver information for a dotted member access, used to produce +/// targeted nullness warnings (e.g. "Possible dereference of null when +/// accessing member 'M' on the nullable value 'x'"). +type ObjArgInfo = + { ObjExprRange: range + MemberName: string + BindingName: string option } + /// Captures relevant information for a particular failed overload resolution. type OverloadInformation = { methodSlot: CalledMeth @@ -257,7 +265,7 @@ val ResolveOverloadingForCall: DisplayEnv -> ConstraintSolverState -> range -> - objArgInfo: (range * string * string option) option -> + objArgInfo: ObjArgInfo option -> methodName: string -> callerArgs: CallerArgs -> AccessorDomain -> @@ -271,7 +279,7 @@ val UnifyUniqueOverloading: ConstraintSolverState -> range -> int * int -> - objArgInfo: (range * string * string option) option -> + objArgInfo: ObjArgInfo option -> string -> AccessorDomain -> CalledMeth list -> diff --git a/src/Compiler/Checking/Expressions/CheckExpressions.fs b/src/Compiler/Checking/Expressions/CheckExpressions.fs index 9359a71d925..12f3d5abbc5 100644 --- a/src/Compiler/Checking/Expressions/CheckExpressions.fs +++ b/src/Compiler/Checking/Expressions/CheckExpressions.fs @@ -10394,7 +10394,7 @@ and TcMethodApplication let callerObjArgTys = objArgs |> List.map (tyOfExpr g) let calledMeths = calledMethsAndProps |> List.map fst - let objArgInfo = + let objArgInfo: ObjArgInfo option = if g.checkNullness then match objArgs with | [objExpr] -> @@ -10411,7 +10411,7 @@ and TcMethodApplication tryGetBindingName innerExpr | _ -> None let bindingName = tryGetBindingName objExpr - Some (objExpr.Range, methodName, bindingName) + Some { ObjExprRange = objExpr.Range; MemberName = methodName; BindingName = bindingName } | _ -> None else None From b422bc6b3ba7a7b36605430f86d0984d97423253 Mon Sep 17 00:00:00 2001 From: Tomas Grosup Date: Thu, 21 May 2026 14:19:09 +0200 Subject: [PATCH 11/18] Flip csenv default in SolveTypeSubsumesType (#19658) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Inside the else-branch recursion, shadow csenv with the stripped variant so MemberAccessOnNullable context is dropped by default for deep subsumption descents. Keep the original under csenvOuter for the two sites that genuinely describe the outer ty1/ty2 pair: the same-tycon outer-nullness check, and the FindUniqueFeasibleSupertype recursion which still subsumes the original ty1. This eliminates the fragility of having to remember to use csenvInner on every new recursive branch — the safe behavior is now the default. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Compiler/Checking/ConstraintSolver.fs | 78 ++++++++++++----------- 1 file changed, 42 insertions(+), 36 deletions(-) diff --git a/src/Compiler/Checking/ConstraintSolver.fs b/src/Compiler/Checking/ConstraintSolver.fs index f27e81335b1..fab8af1c6ef 100644 --- a/src/Compiler/Checking/ConstraintSolver.fs +++ b/src/Compiler/Checking/ConstraintSolver.fs @@ -1515,22 +1515,28 @@ and SolveTypeSubsumesType (csenv: ConstraintSolverEnv) ndeep m2 (trace: Optional let g = csenv.g let canShortcut = not trace.HasTrace - // MemberAccessOnNullable describes the OUTER receiver's nullness only. - // Strip it before recursing into type-args/tuple/fun components so deep - // nullness mismatches don't get reported as if they were the receiver's - // (wrong message AND wrong range). See issue #19658. - let csenvInner = - match csenv.eContextInfo with - | ContextInfo.MemberAccessOnNullable _ -> { csenv with eContextInfo = ContextInfo.NoContext } - | _ -> csenv - // 'a :> objnull ---> if isObjNullTy g ty1 then CompleteD elif isObjTyAnyNullness g ty1 && not csenv.MatchingOnly && not(isTyparTy g ty2) then let nullness t = t |> stripTyEqnsA g canShortcut |> nullnessOfTy g SolveNullnessSubsumesNullness csenv m2 trace ty1 ty2 (nullness ty1) (nullness ty2) - else + else + // Keep the caller's csenv available for the *outer* nullness check + // on the same-tycon branch below. + let csenvOuter = csenv + + // Inside the recursion we describe the *inner* types (tuple components, + // type args, fun domain/range, ...). A MemberAccessOnNullable context + // describes the OUTER receiver only; recursing with it would attach the + // dot-access message to a deep mismatch (wrong message AND wrong range). + // Strip it once here so all recursive callsites below default to the + // safe behavior. See #19658. + let csenv = + match csenv.eContextInfo with + | ContextInfo.MemberAccessOnNullable _ -> { csenv with eContextInfo = ContextInfo.NoContext } + | _ -> csenv + let sty1 = stripTyEqnsA csenv.g canShortcut ty1 let sty2 = stripTyEqnsA csenv.g canShortcut ty2 let amap = csenv.amap @@ -1540,85 +1546,85 @@ and SolveTypeSubsumesType (csenv: ConstraintSolverEnv) ndeep m2 (trace: Optional match sty1, sty2 with | TType_var (tp1, nullness1) , _ -> match aenv.EquivTypars.TryFind tp1 with - | Some tpTy1 -> SolveTypeSubsumesType csenvInner ndeep m2 trace cxsln tpTy1 ty2 + | Some tpTy1 -> SolveTypeSubsumesType csenv ndeep m2 trace cxsln tpTy1 ty2 | _ -> match sty2 with | TType_var (r2, nullness2) when typarEq tp1 r2 -> - SolveNullnessEquiv csenvInner m2 trace ty1 ty2 nullness1 nullness2 + SolveNullnessEquiv csenv m2 trace ty1 ty2 nullness1 nullness2 | TType_var (r2, nullness2) when not csenv.MatchingOnly -> trackErrors { - do! SolveTyparSubtypeOfType csenvInner ndeep m2 trace r2 ty1 + do! SolveTyparSubtypeOfType csenv ndeep m2 trace r2 ty1 let nullnessAfterSolution2 = combineNullness (nullnessOfTy g sty2) nullness2 - do! SolveNullnessSubsumesNullness csenvInner m2 trace ty1 ty2 nullness1 nullnessAfterSolution2 + do! SolveNullnessSubsumesNullness csenv m2 trace ty1 ty2 nullness1 nullnessAfterSolution2 } - | _ -> SolveTypeEqualsTypeKeepAbbrevsWithCxsln csenvInner ndeep m2 trace cxsln ty1 ty2 + | _ -> SolveTypeEqualsTypeKeepAbbrevsWithCxsln csenv ndeep m2 trace cxsln ty1 ty2 | _, TType_var (r2, nullness2) when not csenv.MatchingOnly -> trackErrors { - do! SolveTyparSubtypeOfType csenvInner ndeep m2 trace r2 ty1 + do! SolveTyparSubtypeOfType csenv ndeep m2 trace r2 ty1 let nullnessAfterSolution2 = combineNullness (nullnessOfTy g sty2) nullness2 - do! SolveNullnessSubsumesNullness csenvInner m2 trace ty1 ty2 (nullnessOfTy g sty1) nullnessAfterSolution2 + do! SolveNullnessSubsumesNullness csenv m2 trace ty1 ty2 (nullnessOfTy g sty1) nullnessAfterSolution2 } | TType_tuple (tupInfo1, l1), TType_tuple (tupInfo2, l2) -> if evalTupInfoIsStruct tupInfo1 <> evalTupInfoIsStruct tupInfo2 then ErrorD (ConstraintSolverError(FSComp.SR.tcTupleStructMismatch(), csenv.m, m2)) else - SolveTypeEqualsTypeEqns csenvInner ndeep m2 trace cxsln l1 l2 (* nb. can unify since no variance *) + SolveTypeEqualsTypeEqns csenv ndeep m2 trace cxsln l1 l2 (* nb. can unify since no variance *) | TType_fun (domainTy1, rangeTy1, nullness1), TType_fun (domainTy2, rangeTy2, nullness2) -> // nb. can unify since no variance trackErrors { - do! SolveFunTypeEqn csenvInner ndeep m2 trace cxsln domainTy1 domainTy2 rangeTy1 rangeTy2 - do! SolveNullnessSubsumesNullness csenvInner m2 trace ty1 ty2 nullness1 nullness2 + do! SolveFunTypeEqn csenv ndeep m2 trace cxsln domainTy1 domainTy2 rangeTy1 rangeTy2 + do! SolveNullnessSubsumesNullness csenv m2 trace ty1 ty2 nullness1 nullness2 } | TType_anon (anonInfo1, l1), TType_anon (anonInfo2, l2) -> trackErrors { - do! SolveAnonInfoEqualsAnonInfo csenvInner m2 anonInfo1 anonInfo2 - do! SolveTypeEqualsTypeEqns csenvInner ndeep m2 trace cxsln l1 l2 + do! SolveAnonInfoEqualsAnonInfo csenv m2 anonInfo1 anonInfo2 + do! SolveTypeEqualsTypeEqns csenv ndeep m2 trace cxsln l1 l2 } | TType_measure ms1, TType_measure ms2 -> - UnifyMeasures csenvInner trace ms1 ms2 + UnifyMeasures csenv trace ms1 ms2 // Enforce the identities float=float<1>, float32=float32<1> and decimal=decimal<1> | _, TType_app (tc2, [ms2], _) when tc2.IsMeasureableReprTycon && typeEquiv csenv.g sty1 (reduceTyconRefMeasureableOrProvided csenv.g tc2 [ms2]) -> trackErrors { - do! SolveTypeEqualsTypeKeepAbbrevsWithCxsln csenvInner ndeep m2 trace cxsln ms2 (TType_measure(Measure.One m2)) - do! SolveNullnessSubsumesNullness csenvInner m2 trace ty1 ty2 (nullnessOfTy g sty1) (nullnessOfTy g sty2) + do! SolveTypeEqualsTypeKeepAbbrevsWithCxsln csenv ndeep m2 trace cxsln ms2 (TType_measure(Measure.One m2)) + do! SolveNullnessSubsumesNullness csenv m2 trace ty1 ty2 (nullnessOfTy g sty1) (nullnessOfTy g sty2) } | TType_app (tc1, [ms1], _), _ when tc1.IsMeasureableReprTycon && typeEquiv csenv.g sty2 (reduceTyconRefMeasureableOrProvided csenv.g tc1 [ms1]) -> trackErrors { - do! SolveTypeEqualsTypeKeepAbbrevsWithCxsln csenvInner ndeep m2 trace cxsln ms1 (TType_measure(Measure.One m2)) - do! SolveNullnessSubsumesNullness csenvInner m2 trace ty1 ty2 (nullnessOfTy g sty1) (nullnessOfTy g sty2) + do! SolveTypeEqualsTypeKeepAbbrevsWithCxsln csenv ndeep m2 trace cxsln ms1 (TType_measure(Measure.One m2)) + do! SolveNullnessSubsumesNullness csenv m2 trace ty1 ty2 (nullnessOfTy g sty1) (nullnessOfTy g sty2) } // Special subsumption rule for byref tags | TType_app (tc1, l1, _) , TType_app (tc2, l2, _) when tyconRefEq g tc1 tc2 && g.byref2_tcr.CanDeref && tyconRefEq g g.byref2_tcr tc1 -> match l1, l2 with | [ h1; tag1 ], [ h2; tag2 ] -> trackErrors { - do! SolveTypeEqualsType csenvInner ndeep m2 trace None h1 h2 + do! SolveTypeEqualsType csenv ndeep m2 trace None h1 h2 match stripTyEqnsA csenv.g canShortcut tag1, stripTyEqnsA csenv.g canShortcut tag2 with | TType_app(tagc1, [], _), TType_app(tagc2, [], _) when (tyconRefEq g tagc2 g.byrefkind_InOut_tcr && (tyconRefEq g tagc1 g.byrefkind_In_tcr || tyconRefEq g tagc1 g.byrefkind_Out_tcr) ) -> () - | _ -> return! SolveTypeEqualsType csenvInner ndeep m2 trace cxsln tag1 tag2 + | _ -> return! SolveTypeEqualsType csenv ndeep m2 trace cxsln tag1 tag2 } - | _ -> SolveTypeEqualsTypeWithContravarianceEqns csenvInner ndeep m2 trace cxsln l1 l2 tc1.TyparsNoRange tc1 + | _ -> SolveTypeEqualsTypeWithContravarianceEqns csenv ndeep m2 trace cxsln l1 l2 tc1.TyparsNoRange tc1 // Special handling for delegate types - ignore nullness differences // Delegates from C# interfaces without nullable annotations should match F# events // See https://github.com/dotnet/fsharp/issues/18361 and https://github.com/dotnet/fsharp/issues/18349 | TType_app (tc1, l1, _), TType_app (tc2, l2, _) when tyconRefEq g tc1 tc2 && isDelegateTy g sty1 -> - SolveTypeEqualsTypeWithContravarianceEqns csenvInner ndeep m2 trace cxsln l1 l2 tc1.TyparsNoRange tc1 + SolveTypeEqualsTypeWithContravarianceEqns csenv ndeep m2 trace cxsln l1 l2 tc1.TyparsNoRange tc1 | TType_app (tc1, l1, _) , TType_app (tc2, l2, _) when tyconRefEq g tc1 tc2 -> trackErrors { - do! SolveTypeEqualsTypeWithContravarianceEqns csenvInner ndeep m2 trace cxsln l1 l2 tc1.TyparsNoRange tc1 - do! SolveNullnessSubsumesNullness csenv m2 trace ty1 ty2 (nullnessOfTy g sty1) (nullnessOfTy g sty2) + do! SolveTypeEqualsTypeWithContravarianceEqns csenv ndeep m2 trace cxsln l1 l2 tc1.TyparsNoRange tc1 + do! SolveNullnessSubsumesNullness csenvOuter m2 trace ty1 ty2 (nullnessOfTy g sty1) (nullnessOfTy g sty2) } | TType_ucase (uc1, l1), TType_ucase (uc2, l2) when g.unionCaseRefEq uc1 uc2 -> - SolveTypeEqualsTypeEqns csenvInner ndeep m2 trace cxsln l1 l2 + SolveTypeEqualsTypeEqns csenv ndeep m2 trace cxsln l1 l2 | _ -> // By now we know the type is not a variable type @@ -1645,7 +1651,7 @@ and SolveTypeSubsumesType (csenv: ConstraintSolverEnv) ndeep m2 (trace: Optional match tinst1 with | [elemTy1] -> let elemTy2 = destArrayTy g ty2 - SolveTypeEqualsTypeKeepAbbrevsWithCxsln csenvInner ndeep m2 trace cxsln elemTy1 elemTy2 + SolveTypeEqualsTypeKeepAbbrevsWithCxsln csenv ndeep m2 trace cxsln elemTy1 elemTy2 | _ -> error(InternalError("destArrayTy", m)) | _ -> @@ -1654,7 +1660,7 @@ and SolveTypeSubsumesType (csenv: ConstraintSolverEnv) ndeep m2 (trace: Optional // may feasibly convert to Head. match FindUniqueFeasibleSupertype g amap m ty1 ty2 with | None -> ErrorD(ConstraintSolverTypesNotInSubsumptionRelation(denv, ty1, ty2, m, m2)) - | Some t -> SolveTypeSubsumesType csenv ndeep m2 trace cxsln ty1 t + | Some t -> SolveTypeSubsumesType csenvOuter ndeep m2 trace cxsln ty1 t and SolveTypeSubsumesTypeKeepAbbrevs csenv ndeep m2 trace cxsln ty1 ty2 = let denv = csenv.DisplayEnv From 88f346b23a7b28659d803ff20884b24f4e058e79 Mon Sep 17 00:00:00 2001 From: Tomas Grosup Date: Thu, 21 May 2026 14:42:04 +0200 Subject: [PATCH 12/18] Collapse Issue 19658 tests into Theory; drop misleading KNOWN GAP label (#19658) Merge seven template-identical Issue 19658 dot-access nullness tests into a single [][] driven by (source, range, member, binding, type) tuples. The mechanical repetition added noise without helping readability. Rename the four 'KNOWN GAP' tests to neutral 'falls back to generic nullness warning' / 'rejects nullable annotation' wording. These tests pin the intentional scope boundary (paths NOT routed through TcMethodApplication) and are not bugs. Update the comment block above them to reflect this. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Nullness/NullableReferenceTypesTests.fs | 122 +++++------------- 1 file changed, 30 insertions(+), 92 deletions(-) diff --git a/tests/FSharp.Compiler.ComponentTests/Language/Nullness/NullableReferenceTypesTests.fs b/tests/FSharp.Compiler.ComponentTests/Language/Nullness/NullableReferenceTypesTests.fs index f81f0fe4410..9cbbefd1d35 100644 --- a/tests/FSharp.Compiler.ComponentTests/Language/Nullness/NullableReferenceTypesTests.fs +++ b/tests/FSharp.Compiler.ComponentTests/Language/Nullness/NullableReferenceTypesTests.fs @@ -2374,28 +2374,32 @@ let main _ = 0 |> run |> verifyOutputContains [|"-1"|] -[] -let ``Issue 19658 - nullness warning on dotted method access underlines the receiver`` () = - FSharp """module MyLib -let f (x: string | null) = x.PadLeft(1)""" - |> asLibrary - |> typeCheckWithStrictNullness - |> shouldFail - |> withDiagnostics [ - Error 3261, Line 2, Col 28, Line 2, Col 29, - "Nullness warning: Possible dereference of a null value when accessing member 'PadLeft' on the nullable value 'x' of type 'string'." - ] - -[] -let ``Issue 19658 - nullness warning on dotted property access underlines the receiver`` () = - FSharp """module MyLib -let f (x: string | null) = x.Length""" +[] +// (source, line, col1, col2, memberName, bindingName, typeName) +[] +[] +[] +[] +[ | null) = xs.First()", + 3, 59, 61, "First", "xs", "int seq")>] +[ | null) = xs.GetEnumerator()", + 2, 66, 68, "GetEnumerator", "xs", "System.Collections.Generic.IEnumerable")>] +[] +let ``Issue 19658 - dot-access on nullable receiver names the binding and member`` + (source: string, line: int, col1: int, col2: int, + memberName: string, bindingName: string, typeName: string) = + FSharp source |> asLibrary |> typeCheckWithStrictNullness |> shouldFail |> withDiagnostics [ - Error 3261, Line 2, Col 28, Line 2, Col 29, - "Nullness warning: Possible dereference of a null value when accessing member 'Length' on the nullable value 'x' of type 'string'." + Error 3261, Line line, Col col1, Line line, Col col2, + $"Nullness warning: Possible dereference of a null value when accessing member '{memberName}' on the nullable value '{bindingName}' of type '{typeName}'." ] [] @@ -2411,18 +2415,6 @@ let f () = (getStr()).Length""" "Nullness warning: Possible dereference of a null value when accessing member 'Length' on a nullable expression of type 'string'." ] -[] -let ``Issue 19658 - chained access warns only on the nullable receiver`` () = - FSharp """module MyLib -let f (x: string | null) = x.Trim().Length""" - |> asLibrary - |> typeCheckWithStrictNullness - |> shouldFail - |> withDiagnostics [ - Error 3261, Line 2, Col 28, Line 2, Col 29, - "Nullness warning: Possible dereference of a null value when accessing member 'Trim' on the nullable value 'x' of type 'string'." - ] - [] let ``Issue 19658 - mutable receiver shows binding name`` () = FSharp """module MyLib @@ -2437,31 +2429,6 @@ let f () = "Nullness warning: Possible dereference of a null value when accessing member 'Length' on the nullable value 's' of type 'string'." ] -[] -let ``Issue 19658 - overloaded method does not double-fire`` () = - FSharp """module MyLib -let f (x: string | null) = x.Split(',')""" - |> asLibrary - |> typeCheckWithStrictNullness - |> shouldFail - |> withDiagnostics [ - Error 3261, Line 2, Col 28, Line 2, Col 29, - "Nullness warning: Possible dereference of a null value when accessing member 'Split' on the nullable value 'x' of type 'string'." - ] - -[] -let ``Issue 19658 - extension method on nullable receiver`` () = - FSharp """module MyLib -open System.Linq -let f (xs: System.Collections.Generic.List | null) = xs.First()""" - |> asLibrary - |> typeCheckWithStrictNullness - |> shouldFail - |> withDiagnostics [ - Error 3261, Line 3, Col 59, Line 3, Col 61, - "Nullness warning: Possible dereference of a null value when accessing member 'First' on the nullable value 'xs' of type 'int seq'." - ] - [] let ``Issue 19658 - nested nullable generic receiver does not produce misleading inner-type-arg message`` () = FSharp """module MyLib @@ -2514,21 +2481,6 @@ let f (x: string | null) = "Nullness warning: Possible dereference of a null value when accessing member 'Length' on the nullable value 'x' of type 'string'.. See also test.fs(3,4)-(4,15)." ] -[] -let ``Issue 19658 - binding name surfaces through interface upcast Coerce`` () = - // The receiver xs typed as IEnumerable | null gets a - // Expr.Op(TOp.Coerce, ...) wrapper at the call site of GetEnumerator - // (interface-method dispatch). Binding name 'xs' must still surface. - FSharp """module MyLib -let f (xs: System.Collections.Generic.IEnumerable | null) = xs.GetEnumerator()""" - |> asLibrary - |> typeCheckWithStrictNullness - |> shouldFail - |> withDiagnostics [ - Error 3261, Line 2, Col 66, Line 2, Col 68, - "Nullness warning: Possible dereference of a null value when accessing member 'GetEnumerator' on the nullable value 'xs' of type 'System.Collections.Generic.IEnumerable'." - ] - [] let ``Issue 19658 - implicit 'this' receiver does not leak name`` () = // 'this.Get()' is an Expr.App, not an Expr.Val, so tryGetBindingName @@ -2548,19 +2500,6 @@ type C() = // -------- Coverage tests (positive: new dot-access message is used) -------- -[] -let ``Issue 19658 - property setter on nullable receiver uses new message`` () = - FSharp """module MyLib -open System.Text -let f (sb: StringBuilder | null) = sb.Length <- 0""" - |> asLibrary - |> typeCheckWithStrictNullness - |> shouldFail - |> withDiagnostics [ - Error 3261, Line 3, Col 36, Line 3, Col 38, - "Nullness warning: Possible dereference of a null value when accessing member 'Length' on the nullable value 'sb' of type 'StringBuilder'." - ] - [] let ``Issue 19658 - piped lambda receiver uses new message`` () = FSharp """module MyLib @@ -2575,15 +2514,14 @@ let f () = "Nullness warning: Possible dereference of a null value when accessing member 'Length' on the nullable value 's' of type 'string'." ] -// -------- KNOWN GAP tests (negative: old generic message still used) -------- +// -------- Out-of-scope-path tests (positive: old generic message still used) -------- // These pin the current behavior of paths intentionally NOT routed through -// TcMethodApplication. Widening coverage to these paths is a separate -// enhancement tracked under #17409. If a future change starts emitting the -// new dot-access message for any of these, that change must update these -// tests deliberately. +// TcMethodApplication. Widening coverage to these paths is tracked separately +// under #17409. If a future change starts emitting the new dot-access message +// for any of these, that change must update these tests deliberately. [] -let ``Issue 19658 - KNOWN GAP indexer access falls back to generic nullness warning`` () = +let ``Issue 19658 - indexer access falls back to generic nullness warning`` () = FSharp """module MyLib let f (x: string | null) = x.[0]""" |> asLibrary @@ -2595,7 +2533,7 @@ let f (x: string | null) = x.[0]""" ] [] -let ``Issue 19658 - KNOWN GAP F# record field access falls back to generic nullness warning`` () = +let ``Issue 19658 - F# record field access falls back to generic nullness warning`` () = FSharp """module MyLib type R = { Field: int } let f (r: R | null) = r.Field""" @@ -2608,7 +2546,7 @@ let f (r: R | null) = r.Field""" ] [] -let ``Issue 19658 - KNOWN GAP anonymous record type cannot be marked nullable`` () = +let ``Issue 19658 - anonymous record type cannot be marked nullable`` () = // Anonymous record types do not support a `| null` qualification at all, // so the dot-access nullness path cannot fire on them. This pins the // current behavior: instead of FS3261, the language rejects the type. @@ -2623,7 +2561,7 @@ let f (r: {| Field: int |} | null) = r.Field""" ] [] -let ``Issue 19658 - KNOWN GAP SRTP member call still uses generic nullness warning`` () = +let ``Issue 19658 - SRTP member call uses generic nullness warning`` () = // SRTP-constrained members do not go through TcMethodApplication's // overload resolution path; objArgInfo is None. The generic FS3261 // message fires. If/when SRTP gets routed through the same path, this From a6b963a3f38ff763dae727af8f3b3905ad0db6a9 Mon Sep 17 00:00:00 2001 From: Tomas Grosup Date: Thu, 21 May 2026 14:55:16 +0200 Subject: [PATCH 13/18] Remove objArgInfo parameter threading; carry context via eContextInfo (#19658) Internal API change: ResolveOverloading and ResolveOverloadingCore no longer take an objArgInfo parameter. The public entry points ResolveOverloadingForCall and UnifyUniqueOverloading still accept the ObjArgInfo and set ContextInfo.MemberAccessOnNullable on the constructed ConstraintSolverEnv. CanMemberSigsMatchUpToCheck call sites locally strip the context for the non-obj-arg callbacks so deep arg/return mismatches keep using the generic FS3261 message. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Compiler/Checking/ConstraintSolver.fs | 112 ++++++++++++---------- 1 file changed, 64 insertions(+), 48 deletions(-) diff --git a/src/Compiler/Checking/ConstraintSolver.fs b/src/Compiler/Checking/ConstraintSolver.fs index fab8af1c6ef..0ebc8f71af3 100644 --- a/src/Compiler/Checking/ConstraintSolver.fs +++ b/src/Compiler/Checking/ConstraintSolver.fs @@ -371,14 +371,6 @@ let MakeConstraintSolverEnv contextInfo css m denv = ExtraRigidTypars = emptyFreeTypars } -let applyObjArgContext (objArgInfo: ObjArgInfo option) (csenv: ConstraintSolverEnv) = - match objArgInfo with - | Some info when csenv.g.checkNullness -> - { csenv with - eContextInfo = ContextInfo.MemberAccessOnNullable(info.ObjExprRange, info.MemberName, info.BindingName) - } - | _ -> csenv - /// Check whether a type variable occurs in the r.h.s. of a type, e.g. to catch /// infinite equations such as /// 'a = 'a list @@ -2108,7 +2100,7 @@ and SolveMemberConstraint (csenv: ConstraintSolverEnv) ignoreUnresolvedOverload let methOverloadResult, errors = trace.CollectThenUndoOrCommit (fun (a, _) -> Option.isSome a) - (fun trace -> ResolveOverloading csenv (WithTrace trace) nm ndeep (Some traitInfo) None CallerArgs.Empty AccessibleFromEverywhere calledMethGroup false (Some (MustEqual retTy))) + (fun trace -> ResolveOverloading csenv (WithTrace trace) nm ndeep (Some traitInfo) CallerArgs.Empty AccessibleFromEverywhere calledMethGroup false (Some (MustEqual retTy))) match anonRecdPropSearch, recdPropSearch, methOverloadResult with | Some (anonInfo, tinst, i), None, None -> @@ -3520,7 +3512,6 @@ and ResolveOverloadingCore methodName ndeep cx - objArgInfo (callerArgs: CallerArgs) ad (calledMethGroup: CalledMeth list) @@ -3550,16 +3541,19 @@ and ResolveOverloadingCore let exactMatchCandidates = candidates |> FilterEachThenUndo (fun newTrace calledMeth -> let csenv = { csenv with IsSpeculativeForMethodOverloading = true } - let objCsenv = applyObjArgContext objArgInfo csenv - let cxsln = AssumeMethodSolvesTrait csenv cx m (WithTrace newTrace) calledMeth + let csenvNoCtx = + match csenv.eContextInfo with + | ContextInfo.MemberAccessOnNullable _ -> { csenv with eContextInfo = ContextInfo.NoContext } + | _ -> csenv + let cxsln = AssumeMethodSolvesTrait csenvNoCtx cx m (WithTrace newTrace) calledMeth CanMemberSigsMatchUpToCheck - csenv + csenvNoCtx permitOptArgs alwaysCheckReturn - (TypesEquiv csenv ndeep (WithTrace newTrace) cxsln) // instantiations equivalent - (TypesMustSubsume objCsenv ndeep (WithTrace newTrace) cxsln m) // obj can subsume - (ReturnTypesMustSubsumeOrConvert csenv ad ndeep (WithTrace newTrace) cxsln cx.IsSome m) // return can subsume or convert - (ArgsEquivOrConvert csenv ad ndeep (WithTrace newTrace) cxsln cx.IsSome) // args exact + (TypesEquiv csenvNoCtx ndeep (WithTrace newTrace) cxsln) // instantiations equivalent + (TypesMustSubsume csenv ndeep (WithTrace newTrace) cxsln m) // obj can subsume + (ReturnTypesMustSubsumeOrConvert csenvNoCtx ad ndeep (WithTrace newTrace) cxsln cx.IsSome m) // return can subsume or convert + (ArgsEquivOrConvert csenvNoCtx ad ndeep (WithTrace newTrace) cxsln cx.IsSome) // args exact reqdRetTyOpt calledMeth) @@ -3574,16 +3568,19 @@ and ResolveOverloadingCore let applicable = candidates |> FilterEachThenUndo (fun newTrace candidate -> let csenv = { csenv with IsSpeculativeForMethodOverloading = true } - let objCsenv = applyObjArgContext objArgInfo csenv - let cxsln = AssumeMethodSolvesTrait csenv cx m (WithTrace newTrace) candidate + let csenvNoCtx = + match csenv.eContextInfo with + | ContextInfo.MemberAccessOnNullable _ -> { csenv with eContextInfo = ContextInfo.NoContext } + | _ -> csenv + let cxsln = AssumeMethodSolvesTrait csenvNoCtx cx m (WithTrace newTrace) candidate CanMemberSigsMatchUpToCheck - csenv + csenvNoCtx permitOptArgs alwaysCheckReturn - (TypesEquiv csenv ndeep (WithTrace newTrace) cxsln) // instantiations equivalent - (TypesMustSubsume objCsenv ndeep (WithTrace newTrace) cxsln m) // obj can subsume - (ReturnTypesMustSubsumeOrConvert csenv ad ndeep (WithTrace newTrace) cxsln cx.IsSome m) // return can subsume or convert - (ArgsMustSubsumeOrConvertWithContextualReport csenv ad ndeep (WithTrace newTrace) cxsln cx.IsSome candidate) // args can subsume + (TypesEquiv csenvNoCtx ndeep (WithTrace newTrace) cxsln) // instantiations equivalent + (TypesMustSubsume csenv ndeep (WithTrace newTrace) cxsln m) // obj can subsume + (ReturnTypesMustSubsumeOrConvert csenvNoCtx ad ndeep (WithTrace newTrace) cxsln cx.IsSome m) // return can subsume or convert + (ArgsMustSubsumeOrConvertWithContextualReport csenvNoCtx ad ndeep (WithTrace newTrace) cxsln cx.IsSome candidate) // args can subsume reqdRetTyOpt candidate) @@ -3597,16 +3594,19 @@ and ResolveOverloadingCore |> List.choose (fun calledMeth -> match CollectThenUndo (fun newTrace -> let csenv = { csenv with IsSpeculativeForMethodOverloading = true } - let objCsenv = applyObjArgContext objArgInfo csenv - let cxsln = AssumeMethodSolvesTrait csenv cx m (WithTrace newTrace) calledMeth + let csenvNoCtx = + match csenv.eContextInfo with + | ContextInfo.MemberAccessOnNullable _ -> { csenv with eContextInfo = ContextInfo.NoContext } + | _ -> csenv + let cxsln = AssumeMethodSolvesTrait csenvNoCtx cx m (WithTrace newTrace) calledMeth CanMemberSigsMatchUpToCheck - csenv + csenvNoCtx permitOptArgs alwaysCheckReturn - (TypesEquiv csenv ndeep (WithTrace newTrace) cxsln) - (TypesMustSubsume objCsenv ndeep (WithTrace newTrace) cxsln m) - (ReturnTypesMustSubsumeOrConvert csenv ad ndeep (WithTrace newTrace) cxsln cx.IsSome m) - (ArgsMustSubsumeOrConvertWithContextualReport csenv ad ndeep (WithTrace newTrace) cxsln cx.IsSome calledMeth) + (TypesEquiv csenvNoCtx ndeep (WithTrace newTrace) cxsln) + (TypesMustSubsume csenv ndeep (WithTrace newTrace) cxsln m) + (ReturnTypesMustSubsumeOrConvert csenvNoCtx ad ndeep (WithTrace newTrace) cxsln cx.IsSome m) + (ArgsMustSubsumeOrConvertWithContextualReport csenvNoCtx ad ndeep (WithTrace newTrace) cxsln cx.IsSome calledMeth) reqdRetTyOpt calledMeth) with | OkResult _ -> None @@ -3637,7 +3637,6 @@ and ResolveOverloading methodName // The name of the method being called, for error reporting ndeep // Depth of inference cx // We're doing overload resolution as part of constraint solving, where special rules apply for op_Explicit and op_Implicit constraints. - objArgInfo // Information about the receiver of a dotted call, used to refine nullness warnings. (callerArgs: CallerArgs) ad // The access domain of the caller, e.g. a module, type etc. calledMethGroup // The set of methods being called @@ -3717,7 +3716,7 @@ and ResolveOverloading match cachedHit with | Some result -> result | None -> - ResolveOverloadingCore csenv methodName ndeep cx objArgInfo callerArgs ad calledMethGroup candidates permitOptArgs reqdRetTyOpt isOpConversion retTyOpt anyHasOutArgs cacheKeyOpt cache + ResolveOverloadingCore csenv methodName ndeep cx callerArgs ad calledMethGroup candidates permitOptArgs reqdRetTyOpt isOpConversion retTyOpt anyHasOutArgs cacheKeyOpt cache // If we've got a candidate solution: make the final checks - no undo here! // Allow subsumption on arguments. Include the return type. @@ -3733,19 +3732,22 @@ and ResolveOverloading calledMethOpt, trackErrors { do! errors - let cxsln = AssumeMethodSolvesTrait csenv cx m trace calledMeth - let objCsenv = applyObjArgContext objArgInfo csenv + let csenvNoCtx = + match csenv.eContextInfo with + | ContextInfo.MemberAccessOnNullable _ -> { csenv with eContextInfo = ContextInfo.NoContext } + | _ -> csenv + let cxsln = AssumeMethodSolvesTrait csenvNoCtx cx m trace calledMeth match calledMethTrace with | NoTrace -> let! _usesTDC = CanMemberSigsMatchUpToCheck - csenv + csenvNoCtx permitOptArgs true - (TypesEquiv csenv ndeep trace cxsln) // instantiations equal - (TypesMustSubsume objCsenv ndeep trace cxsln m) // obj can subsume - (ReturnTypesMustSubsumeOrConvert csenv ad ndeep trace cxsln cx.IsSome m) // return can subsume or convert - (ArgsMustSubsumeOrConvert csenv ad ndeep trace cxsln cx.IsSome true) // args can subsume or convert + (TypesEquiv csenvNoCtx ndeep trace cxsln) // instantiations equal + (TypesMustSubsume csenv ndeep trace cxsln m) // obj can subsume + (ReturnTypesMustSubsumeOrConvert csenvNoCtx ad ndeep trace cxsln cx.IsSome m) // return can subsume or convert + (ArgsMustSubsumeOrConvert csenvNoCtx ad ndeep trace cxsln cx.IsSome true) // args can subsume or convert reqdRetTyOpt calledMeth return () @@ -4007,8 +4009,15 @@ and GetMostApplicableOverload csenv ndeep candidates applicableMeths calledMethG None, ErrorD err, NoTrace let ResolveOverloadingForCall denv css m objArgInfo methodName callerArgs ad calledMethGroup permitOptArgs reqdRetTy = - let csenv = MakeConstraintSolverEnv ContextInfo.NoContext css m denv - ResolveOverloading csenv NoTrace methodName 0 None objArgInfo callerArgs ad calledMethGroup permitOptArgs (Some reqdRetTy) + let csenvNoCtx = MakeConstraintSolverEnv ContextInfo.NoContext css m denv + let csenv = + match objArgInfo with + | Some info when csenvNoCtx.g.checkNullness -> + { csenvNoCtx with + eContextInfo = + ContextInfo.MemberAccessOnNullable(info.ObjExprRange, info.MemberName, info.BindingName) } + | _ -> csenvNoCtx + ResolveOverloading csenv NoTrace methodName 0 None callerArgs ad calledMethGroup permitOptArgs (Some reqdRetTy) /// This is used before analyzing the types of arguments in a single overload resolution let UnifyUniqueOverloading @@ -4022,7 +4031,14 @@ let UnifyUniqueOverloading (calledMethGroup: CalledMeth list) reqdRetTy // The expected return type, if known = - let csenv = MakeConstraintSolverEnv ContextInfo.NoContext css m denv + let csenvNoCtx = MakeConstraintSolverEnv ContextInfo.NoContext css m denv + let csenv = + match objArgInfo with + | Some info when csenvNoCtx.g.checkNullness -> + { csenvNoCtx with + eContextInfo = + ContextInfo.MemberAccessOnNullable(info.ObjExprRange, info.MemberName, info.BindingName) } + | _ -> csenvNoCtx let m = csenv.m // See what candidates we have based on name and arity let candidates = calledMethGroup |> List.filter (fun cmeth -> cmeth.IsCandidate(m, ad)) @@ -4032,13 +4048,13 @@ let UnifyUniqueOverloading let! _usesTDC = // Only one candidate found - we thus know the types we expect of arguments CanMemberSigsMatchUpToCheck - csenv + csenvNoCtx true // permitOptArgs true // always check return type - (TypesEquiv csenv ndeep NoTrace None) - (TypesMustSubsume (applyObjArgContext objArgInfo csenv) ndeep NoTrace None m) - (ReturnTypesMustSubsumeOrConvert csenv ad ndeep NoTrace None false m) - (ArgsMustSubsumeOrConvert csenv ad ndeep NoTrace None false false) + (TypesEquiv csenvNoCtx ndeep NoTrace None) + (TypesMustSubsume csenv ndeep NoTrace None m) + (ReturnTypesMustSubsumeOrConvert csenvNoCtx ad ndeep NoTrace None false m) + (ArgsMustSubsumeOrConvert csenvNoCtx ad ndeep NoTrace None false false) (Some reqdRetTy) calledMeth return true From 01b6fa94ca0f8dd1325d292de349cf376228eff0 Mon Sep 17 00:00:00 2001 From: Tomas Grosup Date: Thu, 21 May 2026 15:20:02 +0200 Subject: [PATCH 14/18] Extract stripMemberAccessOnNullableCtx helper to dedupe 5 inline copies (#19658) CODE-QUALITY fixup: the strip-MemberAccessOnNullable pattern was inlined at 5 sites (SolveTypeSubsumesType + 4 overload resolution sites in ResolveOverloading). Extract a single helper near MakeConstraintSolverEnv and call it at all five callsites. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Compiler/Checking/ConstraintSolver.fs | 34 ++++++++++------------- 1 file changed, 14 insertions(+), 20 deletions(-) diff --git a/src/Compiler/Checking/ConstraintSolver.fs b/src/Compiler/Checking/ConstraintSolver.fs index 0ebc8f71af3..5b029579add 100644 --- a/src/Compiler/Checking/ConstraintSolver.fs +++ b/src/Compiler/Checking/ConstraintSolver.fs @@ -371,6 +371,15 @@ let MakeConstraintSolverEnv contextInfo css m denv = ExtraRigidTypars = emptyFreeTypars } +/// Strip a MemberAccessOnNullable context before recursing into inner type +/// components. That context describes the OUTER receiver of a dot-access and +/// must not leak into recursive subsumption/unification of inner types +/// (tuple components, type args, fun domain/range, ...). See #19658. +let stripMemberAccessOnNullableCtx (csenv: ConstraintSolverEnv) = + match csenv.eContextInfo with + | ContextInfo.MemberAccessOnNullable _ -> { csenv with eContextInfo = ContextInfo.NoContext } + | _ -> csenv + /// Check whether a type variable occurs in the r.h.s. of a type, e.g. to catch /// infinite equations such as /// 'a = 'a list @@ -1524,10 +1533,7 @@ and SolveTypeSubsumesType (csenv: ConstraintSolverEnv) ndeep m2 (trace: Optional // dot-access message to a deep mismatch (wrong message AND wrong range). // Strip it once here so all recursive callsites below default to the // safe behavior. See #19658. - let csenv = - match csenv.eContextInfo with - | ContextInfo.MemberAccessOnNullable _ -> { csenv with eContextInfo = ContextInfo.NoContext } - | _ -> csenv + let csenv = stripMemberAccessOnNullableCtx csenv let sty1 = stripTyEqnsA csenv.g canShortcut ty1 let sty2 = stripTyEqnsA csenv.g canShortcut ty2 @@ -3541,10 +3547,7 @@ and ResolveOverloadingCore let exactMatchCandidates = candidates |> FilterEachThenUndo (fun newTrace calledMeth -> let csenv = { csenv with IsSpeculativeForMethodOverloading = true } - let csenvNoCtx = - match csenv.eContextInfo with - | ContextInfo.MemberAccessOnNullable _ -> { csenv with eContextInfo = ContextInfo.NoContext } - | _ -> csenv + let csenvNoCtx = stripMemberAccessOnNullableCtx csenv let cxsln = AssumeMethodSolvesTrait csenvNoCtx cx m (WithTrace newTrace) calledMeth CanMemberSigsMatchUpToCheck csenvNoCtx @@ -3568,10 +3571,7 @@ and ResolveOverloadingCore let applicable = candidates |> FilterEachThenUndo (fun newTrace candidate -> let csenv = { csenv with IsSpeculativeForMethodOverloading = true } - let csenvNoCtx = - match csenv.eContextInfo with - | ContextInfo.MemberAccessOnNullable _ -> { csenv with eContextInfo = ContextInfo.NoContext } - | _ -> csenv + let csenvNoCtx = stripMemberAccessOnNullableCtx csenv let cxsln = AssumeMethodSolvesTrait csenvNoCtx cx m (WithTrace newTrace) candidate CanMemberSigsMatchUpToCheck csenvNoCtx @@ -3594,10 +3594,7 @@ and ResolveOverloadingCore |> List.choose (fun calledMeth -> match CollectThenUndo (fun newTrace -> let csenv = { csenv with IsSpeculativeForMethodOverloading = true } - let csenvNoCtx = - match csenv.eContextInfo with - | ContextInfo.MemberAccessOnNullable _ -> { csenv with eContextInfo = ContextInfo.NoContext } - | _ -> csenv + let csenvNoCtx = stripMemberAccessOnNullableCtx csenv let cxsln = AssumeMethodSolvesTrait csenvNoCtx cx m (WithTrace newTrace) calledMeth CanMemberSigsMatchUpToCheck csenvNoCtx @@ -3732,10 +3729,7 @@ and ResolveOverloading calledMethOpt, trackErrors { do! errors - let csenvNoCtx = - match csenv.eContextInfo with - | ContextInfo.MemberAccessOnNullable _ -> { csenv with eContextInfo = ContextInfo.NoContext } - | _ -> csenv + let csenvNoCtx = stripMemberAccessOnNullableCtx csenv let cxsln = AssumeMethodSolvesTrait csenvNoCtx cx m trace calledMeth match calledMethTrace with | NoTrace -> From 4a0ec7090911caf26fcfc4c69edf01e23b4caebc Mon Sep 17 00:00:00 2001 From: Tomas Grosup Date: Fri, 22 May 2026 15:57:00 +0200 Subject: [PATCH 15/18] Show correct nullable type in dot-access warning message (#19658) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use minimalStringOfTypeWithNullness instead of suppressing nullness annotations. The type 'string' was misleading — the actual type is 'string | null'. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Compiler/Driver/CompilerDiagnostics.fs | 12 +------ .../Nullness/NullableCsharpImportTests.fs | 4 +-- .../Nullness/NullableReferenceTypesTests.fs | 28 +++++++-------- ...s-syntax-positive.fs.checknulls_on.err.bsl | 36 +++++++++---------- 4 files changed, 35 insertions(+), 45 deletions(-) diff --git a/src/Compiler/Driver/CompilerDiagnostics.fs b/src/Compiler/Driver/CompilerDiagnostics.fs index b413f6a6f7c..098e23e147f 100644 --- a/src/Compiler/Driver/CompilerDiagnostics.fs +++ b/src/Compiler/Driver/CompilerDiagnostics.fs @@ -739,17 +739,7 @@ type Exception with os.Append(SeeAlsoE().Format(stringOfRange m)) |> ignore | ConstraintSolverNullnessWarningOnDotAccess(denv, objTy, memberName, bindingName, m, m2) -> - // Sibling FS3261 handlers print raw type equations and show - // the | null annotation. This message already states "nullable - // value/expression", so we suppress the annotation to avoid - // redundant phrasings like "nullable value 'x' of type - // 'string | null'". Keep "Some false" intentional. - let denv = - { denv with - showNullnessAnnotations = Some false - } - - let tyStr = NicePrint.minimalStringOfType denv objTy + let tyStr = NicePrint.minimalStringOfTypeWithNullness denv objTy match bindingName with | Some name -> diff --git a/tests/FSharp.Compiler.ComponentTests/Language/Nullness/NullableCsharpImportTests.fs b/tests/FSharp.Compiler.ComponentTests/Language/Nullness/NullableCsharpImportTests.fs index 51df61f2e89..0284263d714 100644 --- a/tests/FSharp.Compiler.ComponentTests/Language/Nullness/NullableCsharpImportTests.fs +++ b/tests/FSharp.Compiler.ComponentTests/Language/Nullness/NullableCsharpImportTests.fs @@ -123,7 +123,7 @@ let s : string = d.Name // should warn here!! |> asLibrary |> typeCheckWithStrictNullness |> shouldFail - |> withDiagnostics [Error 3261, Line 6, Col 18, Line 6, Col 19, "Nullness warning: Possible dereference of a null value when accessing member 'Name' on the nullable value 'd' of type 'DirectoryInfo'."] + |> withDiagnostics [Error 3261, Line 6, Col 18, Line 6, Col 19, "Nullness warning: Possible dereference of a null value when accessing member 'Name' on the nullable value 'd' of type 'DirectoryInfo | null'."] [] let ``Consumption of netstandard2 BCL api which is not annotated`` () = @@ -258,6 +258,6 @@ let theOtherOne = NullableClass.nullableImmArrayOfNotNullStrings |> compile |> shouldFail |> withDiagnostics - [Error 3261, Line 7, Col 18, Line 7, Col 29, "Nullness warning: Possible dereference of a null value when accessing member 'Length' on the nullable value 'firstString' of type 'string'."] + [Error 3261, Line 7, Col 18, Line 7, Col 29, "Nullness warning: Possible dereference of a null value when accessing member 'Length' on the nullable value 'firstString' of type 'string | null'."] diff --git a/tests/FSharp.Compiler.ComponentTests/Language/Nullness/NullableReferenceTypesTests.fs b/tests/FSharp.Compiler.ComponentTests/Language/Nullness/NullableReferenceTypesTests.fs index 9cbbefd1d35..cefe8cf9560 100644 --- a/tests/FSharp.Compiler.ComponentTests/Language/Nullness/NullableReferenceTypesTests.fs +++ b/tests/FSharp.Compiler.ComponentTests/Language/Nullness/NullableReferenceTypesTests.fs @@ -295,7 +295,7 @@ let getLength (x: string | null) = x.Length |> asLibrary |> typeCheckWithStrictNullness |> shouldFail - |> withDiagnostics [Error 3261, Line 3, Col 36, Line 3, Col 37, "Nullness warning: Possible dereference of a null value when accessing member 'Length' on the nullable value 'x' of type 'string'."] + |> withDiagnostics [Error 3261, Line 3, Col 36, Line 3, Col 37, "Nullness warning: Possible dereference of a null value when accessing member 'Length' on the nullable value 'x' of type 'string | null'."] [] let ``Does report warning on obj to static member`` () = @@ -2377,19 +2377,19 @@ let main _ = 0 [] // (source, line, col1, col2, memberName, bindingName, typeName) [] + 2, 28, 29, "PadLeft", "x", "string | null")>] [] + 2, 28, 29, "Length", "x", "string | null")>] [] + 2, 28, 29, "Trim", "x", "string | null")>] [] + 2, 28, 29, "Split", "x", "string | null")>] [ | null) = xs.First()", - 3, 59, 61, "First", "xs", "int seq")>] + 3, 59, 61, "First", "xs", "int seq | null")>] [ | null) = xs.GetEnumerator()", - 2, 66, 68, "GetEnumerator", "xs", "System.Collections.Generic.IEnumerable")>] + 2, 66, 68, "GetEnumerator", "xs", "System.Collections.Generic.IEnumerable | null")>] [] + 3, 36, 38, "Length", "sb", "StringBuilder | null")>] let ``Issue 19658 - dot-access on nullable receiver names the binding and member`` (source: string, line: int, col1: int, col2: int, memberName: string, bindingName: string, typeName: string) = @@ -2412,7 +2412,7 @@ let f () = (getStr()).Length""" |> shouldFail |> withDiagnostics [ Error 3261, Line 3, Col 13, Line 3, Col 21, - "Nullness warning: Possible dereference of a null value when accessing member 'Length' on a nullable expression of type 'string'." + "Nullness warning: Possible dereference of a null value when accessing member 'Length' on a nullable expression of type 'string | null'." ] [] @@ -2426,7 +2426,7 @@ let f () = |> shouldFail |> withDiagnostics [ Error 3261, Line 4, Col 5, Line 4, Col 6, - "Nullness warning: Possible dereference of a null value when accessing member 'Length' on the nullable value 's' of type 'string'." + "Nullness warning: Possible dereference of a null value when accessing member 'Length' on the nullable value 's' of type 'string | null'." ] [] @@ -2439,7 +2439,7 @@ let f (xs: System.Collections.Generic.List | null) = |> shouldFail |> withDiagnostics [ Error 3261, Line 3, Col 5, Line 3, Col 7, - "Nullness warning: Possible dereference of a null value when accessing member 'Count' on the nullable value 'xs' of type 'System.Collections.Generic.List'." + "Nullness warning: Possible dereference of a null value when accessing member 'Count' on the nullable value 'xs' of type 'System.Collections.Generic.List | null'." ] [] @@ -2478,7 +2478,7 @@ let f (x: string | null) = |> shouldFail |> withDiagnostics [ Error 3261, Line 3, Col 5, Line 3, Col 6, - "Nullness warning: Possible dereference of a null value when accessing member 'Length' on the nullable value 'x' of type 'string'.. See also test.fs(3,4)-(4,15)." + "Nullness warning: Possible dereference of a null value when accessing member 'Length' on the nullable value 'x' of type 'string | null'.. See also test.fs(3,4)-(4,15)." ] [] @@ -2495,7 +2495,7 @@ type C() = |> shouldFail |> withDiagnostics [ Error 3261, Line 4, Col 26, Line 4, Col 36, - "Nullness warning: Possible dereference of a null value when accessing member 'Length' on a nullable expression of type 'string'." + "Nullness warning: Possible dereference of a null value when accessing member 'Length' on a nullable expression of type 'string | null'." ] // -------- Coverage tests (positive: new dot-access message is used) -------- @@ -2511,7 +2511,7 @@ let f () = |> shouldFail |> withDiagnostics [ Error 3261, Line 4, Col 19, Line 4, Col 20, - "Nullness warning: Possible dereference of a null value when accessing member 'Length' on the nullable value 's' of type 'string'." + "Nullness warning: Possible dereference of a null value when accessing member 'Length' on the nullable value 's' of type 'string | null'." ] // -------- Out-of-scope-path tests (positive: old generic message still used) -------- diff --git a/tests/FSharp.Compiler.ComponentTests/Language/Nullness/using-nullness-syntax-positive.fs.checknulls_on.err.bsl b/tests/FSharp.Compiler.ComponentTests/Language/Nullness/using-nullness-syntax-positive.fs.checknulls_on.err.bsl index 5ad627e5131..7332a64cf1d 100644 --- a/tests/FSharp.Compiler.ComponentTests/Language/Nullness/using-nullness-syntax-positive.fs.checknulls_on.err.bsl +++ b/tests/FSharp.Compiler.ComponentTests/Language/Nullness/using-nullness-syntax-positive.fs.checknulls_on.err.bsl @@ -4,25 +4,25 @@ using-nullness-syntax-positive.fs (13,18)-(13,24) typecheck error Nullness warni using-nullness-syntax-positive.fs (17,15)-(17,19) typecheck error Nullness warning: The type 'obj' does not support 'null'. using-nullness-syntax-positive.fs (18,15)-(18,19) typecheck error Nullness warning: The type 'String | null' supports 'null' but a non-null type is expected. using-nullness-syntax-positive.fs (19,15)-(19,21) typecheck error Nullness warning: The type 'int option' uses 'null' as a representation value but a non-null type is expected. -using-nullness-syntax-positive.fs (27,14)-(27,15) typecheck error Nullness warning: Possible dereference of a null value when accessing member 'P' on the nullable value 'c' of type 'C'. -using-nullness-syntax-positive.fs (27,14)-(27,15) typecheck error Nullness warning: Possible dereference of a null value when accessing member 'P' on the nullable value 'c' of type 'C'. -using-nullness-syntax-positive.fs (28,14)-(28,15) typecheck error Nullness warning: Possible dereference of a null value when accessing member 'M' on the nullable value 'c' of type 'C'. -using-nullness-syntax-positive.fs (28,14)-(28,15) typecheck error Nullness warning: Possible dereference of a null value when accessing member 'M' on the nullable value 'c' of type 'C'. -using-nullness-syntax-positive.fs (29,14)-(29,15) typecheck error Nullness warning: Possible dereference of a null value when accessing member 'M' on the nullable value 'c' of type 'C'. -using-nullness-syntax-positive.fs (29,14)-(29,15) typecheck error Nullness warning: Possible dereference of a null value when accessing member 'M' on the nullable value 'c' of type 'C'. +using-nullness-syntax-positive.fs (27,14)-(27,15) typecheck error Nullness warning: Possible dereference of a null value when accessing member 'P' on the nullable value 'c' of type 'C | null'. +using-nullness-syntax-positive.fs (27,14)-(27,15) typecheck error Nullness warning: Possible dereference of a null value when accessing member 'P' on the nullable value 'c' of type 'C | null'. +using-nullness-syntax-positive.fs (28,14)-(28,15) typecheck error Nullness warning: Possible dereference of a null value when accessing member 'M' on the nullable value 'c' of type 'C | null'. +using-nullness-syntax-positive.fs (28,14)-(28,15) typecheck error Nullness warning: Possible dereference of a null value when accessing member 'M' on the nullable value 'c' of type 'C | null'. +using-nullness-syntax-positive.fs (29,14)-(29,15) typecheck error Nullness warning: Possible dereference of a null value when accessing member 'M' on the nullable value 'c' of type 'C | null'. +using-nullness-syntax-positive.fs (29,14)-(29,15) typecheck error Nullness warning: Possible dereference of a null value when accessing member 'M' on the nullable value 'c' of type 'C | null'. using-nullness-syntax-positive.fs (43,26)-(43,30) typecheck error Nullness warning: The type 'String' does not support 'null'. -using-nullness-syntax-positive.fs (85,63)-(85,64) typecheck error Nullness warning: Possible dereference of a null value when accessing member 'Value' on the nullable value 's' of type 'C'. -using-nullness-syntax-positive.fs (85,63)-(85,64) typecheck error Nullness warning: Possible dereference of a null value when accessing member 'Value' on the nullable value 's' of type 'C'. -using-nullness-syntax-positive.fs (86,81)-(86,84) typecheck error Nullness warning: Possible dereference of a null value when accessing member 'Value' on the nullable value 'fmt' of type 'C'. -using-nullness-syntax-positive.fs (86,81)-(86,84) typecheck error Nullness warning: Possible dereference of a null value when accessing member 'Value' on the nullable value 'fmt' of type 'C'. -using-nullness-syntax-positive.fs (86,92)-(86,96) typecheck error Nullness warning: Possible dereference of a null value when accessing member 'Value' on the nullable value 'arg1' of type 'C'. -using-nullness-syntax-positive.fs (86,92)-(86,96) typecheck error Nullness warning: Possible dereference of a null value when accessing member 'Value' on the nullable value 'arg1' of type 'C'. -using-nullness-syntax-positive.fs (91,53)-(91,54) typecheck error Nullness warning: Possible dereference of a null value when accessing member 'Value' on the nullable value 's' of type 'C'. -using-nullness-syntax-positive.fs (91,53)-(91,54) typecheck error Nullness warning: Possible dereference of a null value when accessing member 'Value' on the nullable value 's' of type 'C'. -using-nullness-syntax-positive.fs (92,72)-(92,75) typecheck error Nullness warning: Possible dereference of a null value when accessing member 'Value' on the nullable value 'fmt' of type 'C'. -using-nullness-syntax-positive.fs (92,72)-(92,75) typecheck error Nullness warning: Possible dereference of a null value when accessing member 'Value' on the nullable value 'fmt' of type 'C'. -using-nullness-syntax-positive.fs (92,83)-(92,87) typecheck error Nullness warning: Possible dereference of a null value when accessing member 'Value' on the nullable value 'arg1' of type 'C'. -using-nullness-syntax-positive.fs (92,83)-(92,87) typecheck error Nullness warning: Possible dereference of a null value when accessing member 'Value' on the nullable value 'arg1' of type 'C'. +using-nullness-syntax-positive.fs (85,63)-(85,64) typecheck error Nullness warning: Possible dereference of a null value when accessing member 'Value' on the nullable value 's' of type 'C | null'. +using-nullness-syntax-positive.fs (85,63)-(85,64) typecheck error Nullness warning: Possible dereference of a null value when accessing member 'Value' on the nullable value 's' of type 'C | null'. +using-nullness-syntax-positive.fs (86,81)-(86,84) typecheck error Nullness warning: Possible dereference of a null value when accessing member 'Value' on the nullable value 'fmt' of type 'C | null'. +using-nullness-syntax-positive.fs (86,81)-(86,84) typecheck error Nullness warning: Possible dereference of a null value when accessing member 'Value' on the nullable value 'fmt' of type 'C | null'. +using-nullness-syntax-positive.fs (86,92)-(86,96) typecheck error Nullness warning: Possible dereference of a null value when accessing member 'Value' on the nullable value 'arg1' of type 'C | null'. +using-nullness-syntax-positive.fs (86,92)-(86,96) typecheck error Nullness warning: Possible dereference of a null value when accessing member 'Value' on the nullable value 'arg1' of type 'C | null'. +using-nullness-syntax-positive.fs (91,53)-(91,54) typecheck error Nullness warning: Possible dereference of a null value when accessing member 'Value' on the nullable value 's' of type 'C | null'. +using-nullness-syntax-positive.fs (91,53)-(91,54) typecheck error Nullness warning: Possible dereference of a null value when accessing member 'Value' on the nullable value 's' of type 'C | null'. +using-nullness-syntax-positive.fs (92,72)-(92,75) typecheck error Nullness warning: Possible dereference of a null value when accessing member 'Value' on the nullable value 'fmt' of type 'C | null'. +using-nullness-syntax-positive.fs (92,72)-(92,75) typecheck error Nullness warning: Possible dereference of a null value when accessing member 'Value' on the nullable value 'fmt' of type 'C | null'. +using-nullness-syntax-positive.fs (92,83)-(92,87) typecheck error Nullness warning: Possible dereference of a null value when accessing member 'Value' on the nullable value 'arg1' of type 'C | null'. +using-nullness-syntax-positive.fs (92,83)-(92,87) typecheck error Nullness warning: Possible dereference of a null value when accessing member 'Value' on the nullable value 'arg1' of type 'C | null'. using-nullness-syntax-positive.fs (120,32)-(120,36) typecheck error Nullness warning: The type 'obj array' does not support 'null'. using-nullness-syntax-positive.fs (129,4)-(129,34) typecheck error Nullness warning: The type 'String' does not support 'null'. using-nullness-syntax-positive.fs (134,5)-(134,44) typecheck error Nullness warning: The type 'String' does not support 'null'. From 68fab52fa2111ce58c2918d979d484cb2f16ef44 Mon Sep 17 00:00:00 2001 From: Tomas Grosup Date: Mon, 25 May 2026 10:01:02 +0200 Subject: [PATCH 16/18] Remove range parameter from Entity.Typars; fix shared entity typar race MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Entity.Typars was a method taking a range parameter that was passed to LazyWithContext.Force as context for error recovery. For IL-imported entities shared via FrameworkImportsCache, the first caller's range was cached permanently, creating non-deterministic typar ranges in parallel compilations. Analysis of git history (back to the first open-source commit) shows the range parameter was never meaningful: - F# entities use NotLazy for entity_typars — Force ignores the context - IL entities receive the import-time range via NewILTycon, stored in entity_range — the Force-time range was redundant Change Entity.Typars from a method to a property that uses entity.Range as the Force context. Remove the now-redundant TyparsNoRange member. Clean up cascading unused range parameters in callers. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Checking/AugmentWithHashCompare.fs | 27 ++++++------ src/Compiler/Checking/CheckDeclarations.fs | 42 +++++++++---------- .../Checking/CheckIncrementalClasses.fs | 2 +- src/Compiler/Checking/CheckPatterns.fs | 4 +- src/Compiler/Checking/ConstraintSolver.fs | 10 ++--- .../Checking/Expressions/CheckExpressions.fs | 8 ++-- src/Compiler/Checking/InfoReader.fs | 4 +- src/Compiler/Checking/InfoReader.fsi | 2 +- src/Compiler/Checking/MethodCalls.fs | 2 +- src/Compiler/Checking/NameResolution.fs | 40 +++++++++--------- src/Compiler/Checking/NicePrint.fs | 4 +- src/Compiler/Checking/PostInferenceChecks.fs | 4 +- src/Compiler/Checking/QuotationTranslator.fs | 6 +-- src/Compiler/Checking/SignatureConformance.fs | 4 +- src/Compiler/Checking/SignatureHash.fs | 2 +- src/Compiler/Checking/import.fs | 13 ++++-- src/Compiler/Checking/infos.fs | 14 +++---- src/Compiler/Checking/infos.fsi | 6 +-- src/Compiler/CodeGen/IlxGen.fs | 18 ++++---- src/Compiler/Driver/CompilerDiagnostics.fs | 2 +- src/Compiler/Service/FSharpCheckerResults.fs | 6 +-- .../Service/ServiceDeclarationLists.fs | 8 ++-- src/Compiler/Symbols/Exprs.fs | 2 +- src/Compiler/Symbols/Symbols.fs | 18 ++++---- src/Compiler/TypedTree/TypedTree.fs | 22 ++++------ src/Compiler/TypedTree/TypedTree.fsi | 14 ++----- .../TypedTree/TypedTreeOps.Attributes.fs | 2 +- .../TypedTree/TypedTreeOps.FreeVars.fs | 6 +-- src/Compiler/TypedTree/TypedTreeOps.Remap.fs | 2 +- .../TypedTree/TypedTreeOps.Remapping.fs | 2 +- .../TypedTree/TypedTreeOps.Transforms.fs | 4 +- src/Compiler/TypedTree/TypedTreePickle.fs | 2 +- 32 files changed, 145 insertions(+), 157 deletions(-) diff --git a/src/Compiler/Checking/AugmentWithHashCompare.fs b/src/Compiler/Checking/AugmentWithHashCompare.fs index 0d3c12b7599..c5ae2d1459f 100644 --- a/src/Compiler/Checking/AugmentWithHashCompare.fs +++ b/src/Compiler/Checking/AugmentWithHashCompare.fs @@ -1241,7 +1241,7 @@ let unaryArg = [ ValReprInfo.unnamedTopArg ] let tupArg = [ [ ValReprInfo.unnamedTopArg1; ValReprInfo.unnamedTopArg1 ] ] let mkValSpecAux g m (tcref: TyconRef) ty vis slotsig methn valTy argData isGetter isCompGen = - let tps = tcref.Typars m + let tps = tcref.Typars let membInfo = match slotsig with @@ -1298,18 +1298,16 @@ let mkImpliedValSpec g m tcref ty vis slotsig methn valTy argData isGetter = v let MakeValsForCompareAugmentation g (tcref: TyconRef) = - let m = tcref.Range let _, ty = mkMinimalTy g tcref - let tps = tcref.Typars m + let tps = tcref.Typars let vis = tcref.TypeReprAccessibility mkValSpec g tcref ty vis (Some(mkIComparableCompareToSlotSig g)) "CompareTo" (tps +-> (mkCompareObjTy g ty)) unaryArg false, mkValSpec g tcref ty vis (Some(mkGenericIComparableCompareToSlotSig g ty)) "CompareTo" (tps +-> (mkCompareTy g ty)) unaryArg false let MakeValsForCompareWithComparerAugmentation g (tcref: TyconRef) = - let m = tcref.Range let _, ty = mkMinimalTy g tcref - let tps = tcref.Typars m + let tps = tcref.Typars let vis = tcref.TypeReprAccessibility mkValSpec @@ -1324,10 +1322,9 @@ let MakeValsForCompareWithComparerAugmentation g (tcref: TyconRef) = false let MakeValsForEqualsAugmentation g (tcref: TyconRef) = - let m = tcref.Range let _, ty = mkMinimalTy g tcref let vis = tcref.Accessibility - let tps = tcref.Typars m + let tps = tcref.Typars let objEqualsVal = mkValSpec g tcref ty vis (Some(mkEqualsSlotSig g)) "Equals" (tps +-> (mkEqualsObjTy g ty)) unaryArg false @@ -1352,7 +1349,7 @@ let MakeValsForEqualsAugmentation g (tcref: TyconRef) = let MakeValsForEqualityWithComparerAugmentation g (tcref: TyconRef) = let _, ty = mkMinimalTy g tcref let vis = tcref.Accessibility - let tps = tcref.Typars tcref.Range + let tps = tcref.Typars let objGetHashCodeVal = mkValSpec g tcref ty vis (Some(mkGetHashCodeSlotSig g)) "GetHashCode" (tps +-> (mkHashTy g ty)) unitArg false @@ -1395,7 +1392,7 @@ let MakeValsForEqualityWithComparerAugmentation g (tcref: TyconRef) = let MakeBindingsForCompareAugmentation g (tycon: Tycon) = let tcref = mkLocalTyconRef tycon let m = tycon.Range - let tps = tycon.Typars m + let tps = tycon.Typars let mkCompare comparef = match tycon.GeneratedCompareToValues with @@ -1439,7 +1436,7 @@ let MakeBindingsForCompareAugmentation g (tycon: Tycon) = let MakeBindingsForCompareWithComparerAugmentation g (tycon: Tycon) = let tcref = mkLocalTyconRef tycon let m = tycon.Range - let tps = tycon.Typars m + let tps = tycon.Typars let mkCompare comparef = match tycon.GeneratedCompareToWithComparerValues with @@ -1471,7 +1468,7 @@ let MakeBindingsForCompareWithComparerAugmentation g (tycon: Tycon) = let MakeBindingsForEqualityWithComparerAugmentation (g: TcGlobals) (tycon: Tycon) = let tcref = mkLocalTyconRef tycon let m = tycon.Range - let tps = tycon.Typars m + let tps = tycon.Typars let mkStructuralEquatable hashf equalsf = match tycon.GeneratedHashAndEqualsWithComparerValues with @@ -1590,7 +1587,7 @@ let MakeBindingsForEqualityWithComparerAugmentation (g: TcGlobals) (tycon: Tycon let MakeBindingsForEqualsAugmentation (g: TcGlobals) (tycon: Tycon) = let tcref = mkLocalTyconRef tycon let m = tycon.Range - let tps = tycon.Typars m + let tps = tycon.Typars let mkEquals equalsf = match tycon.GeneratedHashAndEqualsValues with @@ -1668,7 +1665,7 @@ let rec TypeDefinitelyHasEquality g ty = ) && // Check the (possibly inferred) structural dependencies - (tinst, tcref.TyparsNoRange) + (tinst, tcref.Typars) ||> List.lengthsEqAndForall2 (fun ty tp -> not tp.EqualityConditionalOn || TypeDefinitelyHasEquality g ty) | _ -> false @@ -1676,7 +1673,7 @@ let MakeValsForUnionAugmentation g (tcref: TyconRef) = let m = tcref.Range let _, tmty = mkMinimalTy g tcref let vis = tcref.TypeReprAccessibility - let tps = tcref.Typars m + let tps = tcref.Typars tcref.UnionCasesAsList |> List.map (fun uc -> @@ -1690,7 +1687,7 @@ let MakeValsForUnionAugmentation g (tcref: TyconRef) = let MakeBindingsForUnionAugmentation g (tycon: Tycon) (vals: ValRef list) = let tcref = mkLocalTyconRef tycon let m = tycon.Range - let tps = tycon.Typars m + let tps = tycon.Typars let tinst, ty = mkMinimalTy g tcref let thisv, thise = mkThisVar g m ty let unitv, _ = mkCompGenLocal m "unitArg" g.unit_ty diff --git a/src/Compiler/Checking/CheckDeclarations.fs b/src/Compiler/Checking/CheckDeclarations.fs index 149f8217e96..0505e805bfa 100644 --- a/src/Compiler/Checking/CheckDeclarations.fs +++ b/src/Compiler/Checking/CheckDeclarations.fs @@ -1701,7 +1701,7 @@ module MutRecBindingChecking = let thisValOpt = GetInstanceMemberThisVariable (v, x) // Members have at least as many type parameters as the enclosing class. Just grab the type variables for the type. - let thisTyInst = List.map mkTyparTy (List.truncate (tcref.Typars(v.Range).Length) v.Typars) + let thisTyInst = List.map mkTyparTy (List.truncate (tcref.Typars.Length) v.Typars) let x = localReps.FixupIncrClassExprPhase2C cenv thisValOpt safeStaticInitInfo thisTyInst x @@ -2153,7 +2153,7 @@ module TyconConstraintInference = | ValueSome tp -> // Within structural types, type parameters can be optimistically assumed to have comparison // We record the ones for which we have made this assumption. - if tycon.TyparsNoRange |> List.exists (fun tp2 -> typarRefEq tp tp2) then + if tycon.Typars |> List.exists (fun tp2 -> typarRefEq tp tp2) then assumedTyparsAcc <- assumedTyparsAcc.Add(tp.Stamp) true else @@ -2180,7 +2180,7 @@ module TyconConstraintInference = not (EntityHasWellKnownAttribute g WellKnownEntityAttributes.NoComparisonAttribute tcref.Deref) && // Check the structural dependencies - (tinst, tcref.TyparsNoRange) ||> List.lengthsEqAndForall2 (fun ty tp -> + (tinst, tcref.Typars) ||> List.lengthsEqAndForall2 (fun ty tp -> if tp.ComparisonConditionalOn || assumedTypars.Contains tp.Stamp then checkIfFieldTypeSupportsComparison tycon ty else @@ -2239,7 +2239,7 @@ module TyconConstraintInference = // OK, we're done, Record the results for the type variable which provide the support for tyconStamp in uneliminatedTycons do let tycon, _ = tab[tyconStamp] - for tp in tycon.Typars(tycon.Range) do + for tp in tycon.Typars do if assumedTyparsActual.Contains(tp.Stamp) then tp.SetComparisonDependsOn true @@ -2274,7 +2274,7 @@ module TyconConstraintInference = | ValueSome tp -> // Within structural types, type parameters can be optimistically assumed to have equality // We record the ones for which we have made this assumption. - if tycon.Typars(tycon.Range) |> List.exists (fun tp2 -> typarRefEq tp tp2) then + if tycon.Typars |> List.exists (fun tp2 -> typarRefEq tp tp2) then assumedTyparsAcc <- assumedTyparsAcc.Add(tp.Stamp) true else @@ -2300,7 +2300,7 @@ module TyconConstraintInference = not (EntityHasWellKnownAttribute g WellKnownEntityAttributes.NoEqualityAttribute tcref.Deref) && // Check the structural dependencies - (tinst, tcref.TyparsNoRange) ||> List.lengthsEqAndForall2 (fun ty tp -> + (tinst, tcref.Typars) ||> List.lengthsEqAndForall2 (fun ty tp -> if tp.EqualityConditionalOn || assumedTypars.Contains tp.Stamp then checkIfFieldTypeSupportsEquality tycon ty else @@ -2360,7 +2360,7 @@ module TyconConstraintInference = // OK, we're done, Record the results for the type variable which provide the support for tyconStamp in uneliminatedTycons do let tycon, _ = tab[tyconStamp] - for tp in tycon.Typars(tycon.Range) do + for tp in tycon.Typars do if assumedTyparsActual.Contains(tp.Stamp) then tp.SetEqualityDependsOn true @@ -2604,8 +2604,7 @@ module EstablishTypeDefinitionCores = let private GetStructuralElementsOfTyconDefn (cenv: cenv) env tpenv (MutRecDefnsPhase1DataForTycon(_, synTyconRepr, _, _, _, _)) tycon = let thisTyconRef = mkLocalTyconRef tycon let g = cenv.g - let m = tycon.Range - let env = AddDeclaredTypars CheckForDuplicateTypars (tycon.Typars m) env + let env = AddDeclaredTypars CheckForDuplicateTypars (tycon.Typars) env let env = MakeInnerEnvForTyconRef env thisTyconRef false [ match synTyconRepr with | SynTypeDefnSimpleRepr.None _ -> () @@ -2646,7 +2645,7 @@ module EstablishTypeDefinitionCores = for arg in ctorArgNames do let ty = names[arg].Type let m = names[arg].Ident.idRange - if not (isNil (ListSet.subtract typarEq (freeInTypeLeftToRight g false ty) tycon.TyparsNoRange)) then + if not (isNil (ListSet.subtract typarEq (freeInTypeLeftToRight g false ty) tycon.Typars)) then errorR(Error(FSComp.SR.tcStructsMustDeclareTypesOfImplicitCtorArgsExplicitly(), m)) yield (ty, m) @@ -3194,7 +3193,7 @@ module EstablishTypeDefinitionCores = let hasMeasureAttr = attribsHaveEntityFlag g WellKnownEntityAttributes.MeasureAttribute attrs let hasMeasureableAttr = attribsHaveEntityFlag g WellKnownEntityAttributes.MeasureableAttribute attrs - let envinner = AddDeclaredTypars CheckForDuplicateTypars (tycon.Typars m) envinner + let envinner = AddDeclaredTypars CheckForDuplicateTypars (tycon.Typars) envinner let envinner = MakeInnerEnvForTyconRef envinner thisTyconRef false match synTyconRepr with @@ -3205,7 +3204,7 @@ module EstablishTypeDefinitionCores = // "type x = | A" can always be used instead. | TyconCoreAbbrevThatIsReallyAUnion (hasMeasureAttr, envinner, id) _ -> () - | SynTypeDefnSimpleRepr.TypeAbbrev(ParserDetail.Ok, rhsType, m) -> + | SynTypeDefnSimpleRepr.TypeAbbrev(ParserDetail.Ok, rhsType, _m) -> #if !NO_TYPEPROVIDERS // Check we have not already decided that this is a generative provided type definition. If we have already done this (i.e. this is the second pass @@ -3254,7 +3253,7 @@ module EstablishTypeDefinitionCores = if not firstPass then let ftyvs = freeInTypeLeftToRight g false ty - let typars = tycon.Typars m + let typars = tycon.Typars if ftyvs.Length <> typars.Length then errorR(Deprecated(FSComp.SR.tcTypeAbbreviationHasTypeParametersMissingOnType(), tycon.Range)) @@ -3279,9 +3278,8 @@ module EstablishTypeDefinitionCores = match origInfo, tyconAndAttrsOpt with | (typeDefCore, _, _), Some (tycon, (attrs, _)) -> let (MutRecDefnsPhase1DataForTycon(_, synTyconRepr, explicitImplements, _, _, _)) = typeDefCore - let m = tycon.Range let tcref = mkLocalTyconRef tycon - let envinner = AddDeclaredTypars CheckForDuplicateTypars (tycon.Typars m) envinner + let envinner = AddDeclaredTypars CheckForDuplicateTypars (tycon.Typars) envinner let envinner = MakeInnerEnvForTyconRef envinner tcref false let implementedTys, _ = List.mapFold (mapFoldFst (TcTypeAndRecover cenv NoNewTypars checkConstraints ItemOccurrence.UseInType WarnOnIWSAM.No envinner)) tpenv explicitImplements @@ -3442,7 +3440,7 @@ module EstablishTypeDefinitionCores = if allowed then if kind = explicitKind then warning(PossibleUnverifiableCode m) - elif List.isEmpty (thisTyconRef.Typars m) then + elif List.isEmpty (thisTyconRef.Typars) then errorR (Error(FSComp.SR.tcOnlyStructsCanHaveStructLayout(), m)) else errorR (Error(FSComp.SR.tcGenericTypesCannotHaveStructLayout(), m)) @@ -3474,7 +3472,7 @@ module EstablishTypeDefinitionCores = if not ctorArgNames.IsEmpty then errorR (Error(FSComp.SR.parsOnlyClassCanTakeValueArguments(), pat.Range)) - let envinner = AddDeclaredTypars CheckForDuplicateTypars (tycon.Typars m) envinner + let envinner = AddDeclaredTypars CheckForDuplicateTypars (tycon.Typars) envinner let envinner = MakeInnerEnvForTyconRef envinner thisTyconRef false let multiCaseUnionStructCheck (unionCases: UnionCase list) = @@ -3640,7 +3638,7 @@ module EstablishTypeDefinitionCores = writeFakeRecordFieldsToSink userFields let superTy = tycon.TypeContents.tcaug_super - let containerInfo = TyconContainerInfo(innerParent, thisTyconRef, thisTyconRef.Typars m, NoSafeInitInfo) + let containerInfo = TyconContainerInfo(innerParent, thisTyconRef, thisTyconRef.Typars, NoSafeInitInfo) let kind = InferTyconKind g (kind, attrs, slotsigs, fields, inSig, isConcrete, m) match kind with | SynTypeDefnKind.Opaque -> @@ -3712,7 +3710,7 @@ module EstablishTypeDefinitionCores = let _, _, curriedArgInfos, returnTy, _ = GetValReprTypeInCompiledForm g (arity |> TranslateSynValInfo cenv m (TcAttributes cenv envinner) |> TranslatePartialValReprInfo []) 0 tyR m if curriedArgInfos.Length < 1 then error(Error(FSComp.SR.tcInvalidDelegateSpecification(), m)) if curriedArgInfos.Length > 1 then error(Error(FSComp.SR.tcDelegatesCannotBeCurried(), m)) - let ttps = thisTyconRef.Typars m + let ttps = thisTyconRef.Typars let fparams = curriedArgInfos.Head |> List.map (fun (ty, argInfo: ArgReprInfo) -> @@ -4028,7 +4026,7 @@ module EstablishTypeDefinitionCores = let (MutRecDefnsPhase1DataForTycon(synTyconInfo, _, _, _, _, _)) = typeDefCore let (SynComponentInfo(_, TyparsAndConstraints (_, cs1), cs2, _, _, _, _, _)) = synTyconInfo let synTyconConstraints = cs1 @ cs2 - let envForTycon = AddDeclaredTypars CheckForDuplicateTypars (tycon.Typars m) envForDecls + let envForTycon = AddDeclaredTypars CheckForDuplicateTypars (tycon.Typars) envForDecls let thisTyconRef = mkLocalTyconRef tycon let envForTycon = MakeInnerEnvForTyconRef envForTycon thisTyconRef false try @@ -4143,7 +4141,7 @@ module EstablishTypeDefinitionCores = // No inferred constraints allowed on declared typars (envMutRecPrelim, withEnvs) ||> MutRecShapes.iterTyconsWithEnv (fun envForDecls (_, tyconOpt) -> - tyconOpt |> Option.iter (fun tycon -> tycon.Typars m |> List.iter (SetTyparRigid envForDecls.DisplayEnv m))) + tyconOpt |> Option.iter (fun tycon -> tycon.Typars |> List.iter (SetTyparRigid envForDecls.DisplayEnv m))) // Phase1E. OK, now recheck the abbreviations, super/interface and explicit constraints types (this time checking constraints) (envMutRecPrelim, withAttrs) ||> MutRecShapes.iterTyconsWithEnv (fun envForDecls (origInfo, tyconAndAttrsOpt) -> @@ -4246,7 +4244,7 @@ module TcDeclarations = tcref.Deref.IsFSharpDelegateTycon || tcref.Deref.IsFSharpEnumTycon - let reqTypars = tcref.Typars m + let reqTypars = tcref.Typars // Member definitions are intrinsic (added directly to the type) if: // a) For interfaces, only if it is in the original defn. diff --git a/src/Compiler/Checking/CheckIncrementalClasses.fs b/src/Compiler/Checking/CheckIncrementalClasses.fs index 846aa5f9085..556e4b9cb59 100644 --- a/src/Compiler/Checking/CheckIncrementalClasses.fs +++ b/src/Compiler/Checking/CheckIncrementalClasses.fs @@ -503,7 +503,7 @@ type IncrClassReprInfo = let ctorDeclaredTypars = staticCtorInfo.GetNormalizedIncrCtorDeclaredTypars cenv denv staticCtorInfo.TyconRef.Range // Note: tcrefObjTy contains the original "formal" typars, thisTy is the "fresh" one... f<>fresh. - let revTypeInst = List.zip ctorDeclaredTypars (tcref.TyparsNoRange |> List.map mkTyparTy) + let revTypeInst = List.zip ctorDeclaredTypars (tcref.Typars |> List.map mkTyparTy) yield MakeIncrClassField(localRep.RepInfoTcGlobals, cpath, revTypeInst, v, isStatic, rfref) | _ -> diff --git a/src/Compiler/Checking/CheckPatterns.fs b/src/Compiler/Checking/CheckPatterns.fs index e6d0e5c7dce..4023253ed34 100644 --- a/src/Compiler/Checking/CheckPatterns.fs +++ b/src/Compiler/Checking/CheckPatterns.fs @@ -510,7 +510,7 @@ and TcRecordPat warnOnUpper (cenv: cenv) env vFlags patEnv ty fieldPats m = | Some(tinst, tcref, fldsmap, _fldsList) -> let gtyp = mkWoNullAppTy tcref tinst - let inst = List.zip (tcref.Typars m) tinst + let inst = List.zip (tcref.Typars) tinst UnifyTypes cenv env m ty gtyp @@ -658,7 +658,7 @@ and ApplyUnionCaseOrExn m (cenv: cenv) env overallTy item = CheckUnionCaseAttributes g ucref m |> CommitOperationResult CheckUnionCaseAccessible cenv.amap m ad ucref |> ignore let resTy = actualResultTyOfUnionCase ucinfo.TypeInst ucref - let inst = mkTyparInst ucref.TyconRef.TyparsNoRange ucinfo.TypeInst + let inst = mkTyparInst ucref.TyconRef.Typars ucinfo.TypeInst let mkf = try UnifyTypes cenv env m overallTy resTy diff --git a/src/Compiler/Checking/ConstraintSolver.fs b/src/Compiler/Checking/ConstraintSolver.fs index 5b029579add..78688b0c717 100644 --- a/src/Compiler/Checking/ConstraintSolver.fs +++ b/src/Compiler/Checking/ConstraintSolver.fs @@ -1607,17 +1607,17 @@ and SolveTypeSubsumesType (csenv: ConstraintSolverEnv) ndeep m2 (trace: Optional (tyconRefEq g tagc1 g.byrefkind_In_tcr || tyconRefEq g tagc1 g.byrefkind_Out_tcr) ) -> () | _ -> return! SolveTypeEqualsType csenv ndeep m2 trace cxsln tag1 tag2 } - | _ -> SolveTypeEqualsTypeWithContravarianceEqns csenv ndeep m2 trace cxsln l1 l2 tc1.TyparsNoRange tc1 + | _ -> SolveTypeEqualsTypeWithContravarianceEqns csenv ndeep m2 trace cxsln l1 l2 tc1.Typars tc1 // Special handling for delegate types - ignore nullness differences // Delegates from C# interfaces without nullable annotations should match F# events // See https://github.com/dotnet/fsharp/issues/18361 and https://github.com/dotnet/fsharp/issues/18349 | TType_app (tc1, l1, _), TType_app (tc2, l2, _) when tyconRefEq g tc1 tc2 && isDelegateTy g sty1 -> - SolveTypeEqualsTypeWithContravarianceEqns csenv ndeep m2 trace cxsln l1 l2 tc1.TyparsNoRange tc1 + SolveTypeEqualsTypeWithContravarianceEqns csenv ndeep m2 trace cxsln l1 l2 tc1.Typars tc1 | TType_app (tc1, l1, _) , TType_app (tc2, l2, _) when tyconRefEq g tc1 tc2 -> trackErrors { - do! SolveTypeEqualsTypeWithContravarianceEqns csenv ndeep m2 trace cxsln l1 l2 tc1.TyparsNoRange tc1 + do! SolveTypeEqualsTypeWithContravarianceEqns csenv ndeep m2 trace cxsln l1 l2 tc1.Typars tc1 do! SolveNullnessSubsumesNullness csenvOuter m2 trace ty1 ty2 (nullnessOfTy g sty1) (nullnessOfTy g sty2) } @@ -2872,7 +2872,7 @@ and SolveTypeSupportsComparison (csenv: ConstraintSolverEnv) ndeep m2 trace ty = match ty with | AppTy g (tcref, tinst) -> // Check the (possibly inferred) structural dependencies - (tinst, tcref.TyparsNoRange) ||> Iterate2D (fun ty tp -> + (tinst, tcref.Typars) ||> Iterate2D (fun ty tp -> if tp.ComparisonConditionalOn then SolveTypeSupportsComparison (csenv: ConstraintSolverEnv) ndeep m2 trace ty else @@ -2919,7 +2919,7 @@ and SolveTypeSupportsEquality (csenv: ConstraintSolverEnv) ndeep m2 trace ty = ErrorD (ConstraintSolverError(FSComp.SR.csTypeDoesNotSupportEquality3(NicePrint.minimalStringOfType denv ty), m, m2)) else // Check the (possibly inferred) structural dependencies - (tinst, tcref.TyparsNoRange) ||> Iterate2D (fun ty tp -> + (tinst, tcref.Typars) ||> Iterate2D (fun ty tp -> if tp.EqualityConditionalOn then SolveTypeSupportsEquality csenv ndeep m2 trace ty else diff --git a/src/Compiler/Checking/Expressions/CheckExpressions.fs b/src/Compiler/Checking/Expressions/CheckExpressions.fs index 12f3d5abbc5..7f37b98fe85 100644 --- a/src/Compiler/Checking/Expressions/CheckExpressions.fs +++ b/src/Compiler/Checking/Expressions/CheckExpressions.fs @@ -1923,7 +1923,7 @@ let FreshenPossibleForallTy g m rigid ty = origTypars, tps, tinst, instType renaming tau let FreshenTyconRef2 (g: TcGlobals) m (tcref: TyconRef) = - let tps, renaming, tinst = FreshenTypeInst g m (tcref.Typars m) + let tps, renaming, tinst = FreshenTypeInst g m (tcref.Typars) tps, renaming, tinst, TType_app (tcref, tinst, g.knownWithoutNull) /// Given a abstract method, which may be a generic method, freshen the type in preparation @@ -2078,7 +2078,7 @@ let ApplyUnionCaseOrExn (makerForUnionCase, makerForExnTag) m (cenv: cenv) env o CheckUnionCaseAttributes g ucref m |> CommitOperationResult CheckUnionCaseAccessible cenv.amap m ad ucref |> ignore let resTy = actualResultTyOfUnionCase ucinfo.TypeInst ucref - let inst = mkTyparInst ucref.TyconRef.TyparsNoRange ucinfo.TypeInst + let inst = mkTyparInst ucref.TyconRef.Typars ucinfo.TypeInst UnifyTypes cenv env m overallTy resTy let mkf = makerForUnionCase(ucref, ucinfo.TypeInst) mkf, actualTysOfUnionCaseFields inst ucref, [ for f in ucref.AllFieldsAsList -> f.Id ] @@ -4190,7 +4190,7 @@ let rec TcTyparConstraint ridx (cenv: cenv) newOk checkConstraints occ (env: TcE match checkConstraints with | NoCheckCxs -> //let formalEnclosingTypars = [] - let tpsorig = tcref.Typars(m) //List.map (destTyparTy g) inst //, _, tinst, _ = FreshenTyconRef2 g m tcref + let tpsorig = tcref.Typars //List.map (destTyparTy g) inst //, _, tinst, _ = FreshenTyconRef2 g m tcref let tps = List.map (destTyparTy g) tinst //, _, tinst, _ = FreshenTyconRef2 g m tcref let tprefInst, _tptys = mkTyparToTyparRenaming tpsorig tps //let tprefInst = mkTyparInst formalEnclosingTypars tinst @ renaming @@ -4673,7 +4673,7 @@ and TcLongIdentAppType kindOpt (cenv: cenv) newOk checkConstraints occ iwsam env TType_measure (NewErrorMeasure ()), tpenv | _, TyparKind.Type -> - if postfix && tcref.Typars m |> List.exists (fun tp -> match tp.Kind with TyparKind.Measure -> true | _ -> false) then + if postfix && tcref.Typars |> List.exists (fun tp -> match tp.Kind with TyparKind.Measure -> true | _ -> false) then error(Error(FSComp.SR.tcInvalidUnitsOfMeasurePrefix(), m)) TcTypeApp cenv newOk checkConstraints occ env tpenv m tcref tinstEnclosing args inst diff --git a/src/Compiler/Checking/InfoReader.fs b/src/Compiler/Checking/InfoReader.fs index 121b087f5e5..b050ed48cdd 100644 --- a/src/Compiler/Checking/InfoReader.fs +++ b/src/Compiler/Checking/InfoReader.fs @@ -1163,13 +1163,13 @@ let PropTypeOfEventInfo (infoReader: InfoReader) m ad (einfo: EventInfo) = mkIEventType g delTy argsTy /// Try to find the name of the metadata file for this external definition -let TryFindMetadataInfoOfExternalEntityRef (infoReader: InfoReader) m eref = +let TryFindMetadataInfoOfExternalEntityRef (infoReader: InfoReader) (_m: range) eref = let g = infoReader.g match eref with | ERefLocal _ -> None | ERefNonLocal nlref -> // Generalize to get a formal signature - let formalTypars = eref.Typars m + let formalTypars = eref.Typars let formalTypeInst = generalizeTypars formalTypars let ty = TType_app(eref, formalTypeInst, KnownAmbivalentToNull) if isILAppTy g ty then diff --git a/src/Compiler/Checking/InfoReader.fsi b/src/Compiler/Checking/InfoReader.fsi index df3fbaf0d2d..ff64d28c41c 100644 --- a/src/Compiler/Checking/InfoReader.fsi +++ b/src/Compiler/Checking/InfoReader.fsi @@ -344,7 +344,7 @@ val PropTypeOfEventInfo: infoReader: InfoReader -> m: range -> ad: AccessorDomai /// Try to find the name of the metadata file for this external definition val TryFindMetadataInfoOfExternalEntityRef: - infoReader: InfoReader -> m: range -> eref: EntityRef -> (string option * Typars * ILTypeInfo) option + infoReader: InfoReader -> _m: range -> eref: EntityRef -> (string option * Typars * ILTypeInfo) option /// Try to find the xml doc associated with the assembly name and metadata key val TryFindXmlDocByAssemblyNameAndSig: diff --git a/src/Compiler/Checking/MethodCalls.fs b/src/Compiler/Checking/MethodCalls.fs index 07e0e95baed..b6a436dd83c 100644 --- a/src/Compiler/Checking/MethodCalls.fs +++ b/src/Compiler/Checking/MethodCalls.fs @@ -1868,7 +1868,7 @@ module ProvidedMethodCalls = else if isGeneric then let genericArgs = st.PApplyArray((fun st -> st.GetGenericArguments()), "GetGenericArguments", m) - let typars = headTypeAsFSharpType.Typars(m) + let typars = headTypeAsFSharpType.Typars // Drop the generic arguments that don't correspond to type arguments, i.e. are units-of-measure let genericArgs = [| for genericArg, tp in Seq.zip genericArgs typars do diff --git a/src/Compiler/Checking/NameResolution.fs b/src/Compiler/Checking/NameResolution.fs index 54290c21bd0..e72fa26517b 100644 --- a/src/Compiler/Checking/NameResolution.fs +++ b/src/Compiler/Checking/NameResolution.fs @@ -524,12 +524,12 @@ type ResultCollectionSettings = let NextExtensionMethodPriority() = uint64 (newStamp()) /// Checks if the type is used for C# style extension members. -let IsTyconRefUsedForCSharpStyleExtensionMembers g m (tcref: TyconRef) = +let IsTyconRefUsedForCSharpStyleExtensionMembers g _m (tcref: TyconRef) = // Type must be non-generic and have 'Extension' attribute match metadataOfTycon tcref.Deref with | ILTypeMetadata(TILObjectReprData(_, _, tdef)) -> tdef.CanContainExtensionMethods | _ -> true - && isNil(tcref.Typars m) && TyconRefHasWellKnownAttribute g WellKnownILAttributes.ExtensionAttribute tcref + && isNil(tcref.Typars) && TyconRefHasWellKnownAttribute g WellKnownILAttributes.ExtensionAttribute tcref /// Checks if the type is used for C# style extension members. let IsTypeUsedForCSharpStyleExtensionMembers g m ty = @@ -1151,7 +1151,7 @@ let GetNestedTyconRefsOfType (infoReader: InfoReader) (amap: Import.ImportMap) ( /// Handle the .NET/C# business where nested generic types implicitly accumulate the type parameters /// from their enclosing types. let MakeNestedType (ncenv: NameResolver) (tinst: TType list) m (tcrefNested: TyconRef) = - let tps = match tcrefNested.Typars m with [] -> [] | l -> l[tinst.Length..] + let tps = match tcrefNested.Typars with [] -> [] | l -> l[tinst.Length..] let tinstNested = ncenv.InstantiationGenerator m tps mkWoNullAppTy tcrefNested (tinst @ tinstNested) @@ -1352,11 +1352,11 @@ and private AddStaticPartsOfTyconRefToNameEnv bulkAddMode ownDefinition g amap m eIndexedExtensionMembers = eIndexedExtensionMembers eUnindexedExtensionMembers = eUnindexedExtensionMembers } -and private CanAutoOpenTyconRef (g: TcGlobals) m (tcref: TyconRef) = +and private CanAutoOpenTyconRef (g: TcGlobals) _m (tcref: TyconRef) = g.langVersion.SupportsFeature LanguageFeature.OpenTypeDeclaration && not tcref.IsILTycon && EntityHasWellKnownAttribute g WellKnownEntityAttributes.AutoOpenAttribute tcref.Deref && - tcref.Typars(m) |> List.isEmpty + tcref.Typars |> List.isEmpty /// Add any implied contents of a type definition to the environment. and private AddPartsOfTyconRefToNameEnv bulkAddMode ownDefinition (g: TcGlobals) amap ad m nenv (tcref: TyconRef) = @@ -1570,14 +1570,14 @@ let AddDeclaredTyparsToNameEnv check nenv typars = /// Convert a reference to a named type into a type that includes /// a fresh set of inference type variables for the type parameters. let FreshenTycon (ncenv: NameResolver) m (tcref: TyconRef) = - let tinst = ncenv.InstantiationGenerator m (tcref.Typars m) + let tinst = ncenv.InstantiationGenerator m (tcref.Typars) let improvedTy = ncenv.g.decompileType tcref tinst ncenv.g.knownWithoutNull improvedTy /// Convert a reference to a named type into a type that includes /// a set of enclosing type instantiations and a fresh set of inference type variables for the type parameters. let FreshenTyconWithEnclosingTypeInst (ncenv: NameResolver) m (tinstEnclosing: TypeInst) (tcref: TyconRef) = - let tps = ncenv.InstantiationGenerator m (tcref.Typars m) + let tps = ncenv.InstantiationGenerator m (tcref.Typars) let tinst = List.skip tinstEnclosing.Length tps let improvedTy = ncenv.g.decompileType tcref (tinstEnclosing @ tinst) ncenv.g.knownWithoutNull improvedTy @@ -1585,12 +1585,12 @@ let FreshenTyconWithEnclosingTypeInst (ncenv: NameResolver) m (tinstEnclosing: T /// Convert a reference to a union case into a UnionCaseInfo that includes /// a fresh set of inference type variables for the type parameters of the union type. let FreshenUnionCaseRef (ncenv: NameResolver) m (ucref: UnionCaseRef) = - let tinst = ncenv.InstantiationGenerator m (ucref.TyconRef.Typars m) + let tinst = ncenv.InstantiationGenerator m (ucref.TyconRef.Typars) UnionCaseInfo(tinst, ucref) /// Generate a new reference to a record field with a fresh type instantiation let FreshenRecdFieldRef (ncenv: NameResolver) m (rfref: RecdFieldRef) = - RecdFieldInfo(ncenv.InstantiationGenerator m (rfref.Tycon.Typars m), rfref) + RecdFieldInfo(ncenv.InstantiationGenerator m (rfref.Tycon.Typars), rfref) //------------------------------------------------------------------------- // Generate type variables and record them in within the scope of the @@ -2513,7 +2513,7 @@ let CheckForTypeLegitimacyAndMultipleGenericTypeAmbiguities // remove later duplicates (if we've opened the same module more than once) |> List.distinctBy (fun (_, tcref) -> tcref.Stamp) // List.sortBy is a STABLE sort (the order matters!) - |> List.sortBy (fun (resInfo, tcref) -> tcref.Typars(m).Length - resInfo.EnclosingTypeInst.Length) + |> List.sortBy (fun (resInfo, tcref) -> tcref.Typars.Length - resInfo.EnclosingTypeInst.Length) let tcrefs = match tcrefs with @@ -2523,14 +2523,14 @@ let CheckForTypeLegitimacyAndMultipleGenericTypeAmbiguities // no explicit type instantiation typeNameResInfo.StaticArgsInfo.HasNoStaticArgsInfo && // some type arguments required on all types (note sorted by typar count above) - ((tcref.Typars m).Length - resInfo.EnclosingTypeInst.Length) > 0 && + ((tcref.Typars).Length - resInfo.EnclosingTypeInst.Length) > 0 && // plausible types have different arities - (tcrefs |> Seq.distinctBy (fun (_, tcref) -> tcref.Typars(m).Length) |> Seq.length > 1) -> + (tcrefs |> Seq.distinctBy (fun (_, tcref) -> tcref.Typars.Length) |> Seq.length > 1) -> [ for resInfo, tcref in tcrefs do let resInfo = resInfo.AddWarning (fun _typarChecker -> errorR(Error(FSComp.SR.nrTypeInstantiationNeededToDisambiguateTypesWithSameName(tcref.DisplayName, tcref.DisplayNameWithStaticParametersAndUnderscoreTypars), m))) yield (resInfo, tcref) ] - | [(resInfo, tcref)] when typeNameResInfo.StaticArgsInfo.HasNoStaticArgsInfo && ((tcref.Typars m).Length - resInfo.EnclosingTypeInst.Length) > 0 && typeNameResInfo.ResolutionFlag = ResolveTypeNamesToTypeRefs -> + | [(resInfo, tcref)] when typeNameResInfo.StaticArgsInfo.HasNoStaticArgsInfo && ((tcref.Typars).Length - resInfo.EnclosingTypeInst.Length) > 0 && typeNameResInfo.ResolutionFlag = ResolveTypeNamesToTypeRefs -> let resInfo = resInfo.AddWarning (fun (ResultTyparChecker typarChecker) -> if not (typarChecker()) then @@ -3215,7 +3215,7 @@ let rec ResolveExprLongIdentPrim sink (ncenv: NameResolver) first fullyQualified |> ResolveUnqualifiedTyconRefs nenv |> List.filter (fun (resInfo, tcref) -> typeNameResInfo.StaticArgsInfo.HasNoStaticArgsInfo || - typeNameResInfo.StaticArgsInfo.NumStaticArgs = tcref.Typars(m).Length - resInfo.EnclosingTypeInst.Length) + typeNameResInfo.StaticArgsInfo.NumStaticArgs = tcref.Typars.Length - resInfo.EnclosingTypeInst.Length) let search = ChooseTyconRefInExpr (ncenv, m, ad, nenv, id, typeNameResInfo, tcrefs) match AtMostOneResult m search with @@ -3279,7 +3279,7 @@ let rec ResolveExprLongIdentPrim sink (ncenv: NameResolver) first fullyQualified match tyconSearch () with | Result((resInfo, tcref) :: _) -> - let _, _, tyargs = FreshenTypeInst ncenv.g m (tcref.Typars m) + let _, _, tyargs = FreshenTypeInst ncenv.g m (tcref.Typars) let item = Item.Types(id.idText, [TType_app(tcref, tyargs, ncenv.g.knownWithoutNull)]) success (resInfo, item) | _ -> @@ -3594,8 +3594,8 @@ let ResolvePatternLongIdent sink (ncenv: NameResolver) warnOnUpper newDef m ad n // // X.ListEnumerator // does not resolve // -let ResolveNestedTypeThroughAbbreviation (ncenv: NameResolver) (tcref: TyconRef) m = - if tcref.IsTypeAbbrev && tcref.Typars(m).IsEmpty then +let ResolveNestedTypeThroughAbbreviation (ncenv: NameResolver) (tcref: TyconRef) _m = + if tcref.IsTypeAbbrev && tcref.Typars.IsEmpty then match tryAppTy ncenv.g tcref.TypeAbbrev.Value with | ValueSome (abbrevTcref, []) -> abbrevTcref | _ -> tcref @@ -3656,7 +3656,7 @@ let ResolveTypeLongIdentInTyconRef sink (ncenv: NameResolver) nenv typeNameResIn ForceRaise (ResolveTypeLongIdentInTyconRefPrim ncenv typeNameResInfo ad ResolutionInfo.Empty PermitDirectReferenceToGeneratedType.No 0 m tcref id rest) ResolutionInfo.SendEntityPathToSink(sink, ncenv, nenv, ItemOccurrence.Use, ad, resInfo, ResultTyparChecker(fun () -> true)) - let _, tinst, tyargs = FreshenTypeInst ncenv.g m (tcref.Typars m) + let _, tinst, tyargs = FreshenTypeInst ncenv.g m (tcref.Typars) let item = Item.Types(tcref.DisplayName, [TType_app(tcref, tyargs, ncenv.g.knownWithoutNull)]) CallNameResolutionSink sink (rangeOfLid lid, nenv, item, tinst, ItemOccurrence.UseInType, ad) @@ -3819,7 +3819,7 @@ let ResolveTypeLongIdentAux sink (ncenv: NameResolver) occurrence fullyQualified | Result (resInfo, tcref) -> ResolutionInfo.SendEntityPathToSink(sink, ncenv, nenv, ItemOccurrence.UseInType, ad, resInfo, ResultTyparChecker(fun () -> true)) - let _, tinst, tyargs = FreshenTypeInst ncenv.g m (tcref.Typars m) + let _, tinst, tyargs = FreshenTypeInst ncenv.g m (tcref.Typars) let item = Item.Types(tcref.DisplayName, [TType_app(tcref, tyargs, ncenv.g.knownWithoutNull)]) CallNameResolutionSink sink (m, nenv, item, tinst, occurrence, ad) @@ -5122,7 +5122,7 @@ let getRecordFieldsInScope nenv = nenv.eFieldLabels |> Seq.collect (fun (KeyValue(_, v)) -> v) |> Seq.map (fun fref -> - let typeInsts = fref.TyconRef.TyparsNoRange |> List.map mkTyparTy + let typeInsts = fref.TyconRef.Typars |> List.map mkTyparTy Item.RecdField(RecdFieldInfo(typeInsts, fref))) |> List.ofSeq diff --git a/src/Compiler/Checking/NicePrint.fs b/src/Compiler/Checking/NicePrint.fs index de8daffa7c5..0fa25a31dfc 100644 --- a/src/Compiler/Checking/NicePrint.fs +++ b/src/Compiler/Checking/NicePrint.fs @@ -1356,7 +1356,7 @@ module PrintTastMemberOrVals = let memberHasSameTyparNameAsParentTypeTypars = let parentTyparNames = - vref.DeclaringEntity.TyparsNoRange + vref.DeclaringEntity.Typars |> Seq.choose (fun tp -> if tp.typar_id.idText = unassignedTyparName then None else Some tp.typar_id.idText) |> set niceMethodTypars @@ -2081,7 +2081,7 @@ module TastDefinitionPrinting = let nameL = ConvertLogicalNameToDisplayLayout (tagger >> mkNav tycon.DefinitionRange >> wordL) tycon.DisplayNameCore let lhsL = - let tps = tycon.TyparsNoRange + let tps = tycon.Typars let tpsL = layoutTyparDecls denv nameL tycon.IsPrefixDisplay tps let tpsL = layoutAccessibility denv tycon.Accessibility tpsL typewordL ^^ tpsL diff --git a/src/Compiler/Checking/PostInferenceChecks.fs b/src/Compiler/Checking/PostInferenceChecks.fs index 7924394c743..db53d382125 100644 --- a/src/Compiler/Checking/PostInferenceChecks.fs +++ b/src/Compiler/Checking/PostInferenceChecks.fs @@ -707,7 +707,7 @@ let CheckTypeAux permitByRefLike (cenv: cenv) env m ty onInnerByrefError = // with unconstrained generics (like List or Dictionary) is fine. // See: https://github.com/dotnet/fsharp/issues/19184 if tcref.CanDeref then - let typars = tcref.Typars m + let typars = tcref.Typars if typars.Length = tinst.Length then (typars, tinst) ||> List.iter2 (CheckInterfaceTypeArgForUnimplementedStaticAbstractMembers cenv m) @@ -2359,7 +2359,7 @@ let CheckEntityDefn cenv env (tycon: Entity) = let ty = generalizedTyconRef g tcref let env = { env with reflect = env.reflect || EntityHasWellKnownAttribute g WellKnownEntityAttributes.ReflectedDefinitionAttribute tycon } - let env = BindTypars g env (tycon.Typars m) + let env = BindTypars g env (tycon.Typars) CheckAttribs cenv env tycon.Attribs diff --git a/src/Compiler/Checking/QuotationTranslator.fs b/src/Compiler/Checking/QuotationTranslator.fs index f861a8ee267..82a1fe07145 100644 --- a/src/Compiler/Checking/QuotationTranslator.fs +++ b/src/Compiler/Checking/QuotationTranslator.fs @@ -616,7 +616,7 @@ and private ConvExprCore cenv (env : QuotationTranslationEnv) (expr: Expr) : Exp if useGenuineField tcref.Deref fspec then mkFieldSet(parentTyconR, fldOrPropName, tyargsR, argsR) else - let envinner = BindFormalTypars env tcref.TyparsNoRange + let envinner = BindFormalTypars env tcref.Typars let propRetTypeR = ConvType cenv envinner m fspec.FormalType mkPropSet( (parentTyconR, fldOrPropName, propRetTypeR, []), tyargsR, argsR) @@ -793,7 +793,7 @@ and private ConvClassOrRecdFieldGetCore cenv env m rfref tyargs args = if useGenuineField tcref.Deref fspec then mkFieldGet(parentTyconR, fldOrPropName, tyargsR, argsR) else - let envinner = BindFormalTypars env tcref.TyparsNoRange + let envinner = BindFormalTypars env tcref.Typars let propRetTypeR = ConvType cenv envinner m fspec.FormalType mkPropGet( (parentTyconR, fldOrPropName, propRetTypeR, []), tyargsR, argsR) @@ -1284,7 +1284,7 @@ let ConvMethodBase cenv env (methName, v: Val) = let vref = mkLocalValRef v let tps, witnessInfos, argInfos, retTy, _ = GetTypeOfMemberInMemberForm cenv.g vref - let numEnclTypeArgs = vref.MemberApparentEntity.TyparsNoRange.Length + let numEnclTypeArgs = vref.MemberApparentEntity.Typars.Length let argTys = argInfos |> List.concat |> List.map fst let isNewObj = (vspr.MemberFlags.MemberKind = SynMemberKind.Constructor) diff --git a/src/Compiler/Checking/SignatureConformance.fs b/src/Compiler/Checking/SignatureConformance.fs index 5c947bda13c..3fa579edd42 100644 --- a/src/Compiler/Checking/SignatureConformance.fs +++ b/src/Compiler/Checking/SignatureConformance.fs @@ -198,8 +198,8 @@ type Checker(g, amap, denv, remapInfo: SignatureRepackageInfo, checkingSig) = checkExnInfo (fun f -> FSharpExceptionNotContained(denv, infoReader, implTycon, sigTycon, f)) aenv infoReader implTycon sigTycon implTycon.ExceptionInfo sigTycon.ExceptionInfo && - let implTypars = implTycon.Typars m - let sigTypars = sigTycon.Typars m + let implTypars = implTycon.Typars + let sigTypars = sigTycon.Typars if implTypars.Length <> sigTypars.Length then errorR (Error(FSComp.SR.DefinitionsInSigAndImplNotCompatibleParameterCountsDiffer(implTycon.TypeOrMeasureKind.ToString(), implTycon.DisplayName), m)) diff --git a/src/Compiler/Checking/SignatureHash.fs b/src/Compiler/Checking/SignatureHash.fs index f81ada24082..5d639b2ddd7 100644 --- a/src/Compiler/Checking/SignatureHash.fs +++ b/src/Compiler/Checking/SignatureHash.fs @@ -78,7 +78,7 @@ module TyconDefinitionHash = let tyconHash = hashTyconRef tcref let attribHash = hashAttributeList tcref.Attribs - let typarsHash = hashTyparDecls g tycon.TyparsNoRange + let typarsHash = hashTyparDecls g tycon.Typars let topLevelDeclarationHash = tyconHash @@ attribHash @@ typarsHash // Interface implementation diff --git a/src/Compiler/Checking/import.fs b/src/Compiler/Checking/import.fs index 8979bab5e77..e13b88dd437 100644 --- a/src/Compiler/Checking/import.fs +++ b/src/Compiler/Checking/import.fs @@ -412,7 +412,7 @@ let rec ImportProvidedTypeAsILType (env: ImportMap) (m: range) (st: Tainted genericArgs.Length then error(Error(FSComp.SR.impInvalidNumberOfGenericArguments(tcref.CompiledName, tps.Length, genericArgs.Length), m)) // We're converting to an IL type, where generic arguments are erased @@ -489,7 +489,7 @@ let rec ImportProvidedType (env: ImportMap) (m: range) (* (tinst: TypeInst) *) ( else tcref - let tps = tcref.Typars m + let tps = tcref.Typars if tps.Length <> genericArgsLength then error(Error(FSComp.SR.impInvalidNumberOfGenericArguments(tcref.CompiledName, tps.Length, genericArgsLength), m)) @@ -701,9 +701,14 @@ let rec ImportILTypeDef amap m scoref (cpath: CompilationPath) enc nm (tdef: ILT Construct.NewILTycon (Some cpath) (nm, m) - // The read of the type parameters may fail to resolve types. We pick up a new range from the point where that read is forced + // The read of the type parameters may fail to resolve types. Entity.Typars forces + // entity_typars with entity_range, so the range used here is always the import-time + // range 'm' passed to NewILTycon above — never a caller's ad-hoc source range. // Make sure we reraise the original exception one occurs - see findOriginalException. - (LazyWithContext.Create((fun m -> ImportILGenericParameters amap m scoref [] nullableFallback tdef.GenericParams), findOriginalException)) + (LazyWithContext.Create( + (fun m -> ImportILGenericParameters amap m scoref [] nullableFallback tdef.GenericParams), + findOriginalException + )) (scoref, enc, tdef) (MaybeLazy.Lazy lazyModuleOrNamespaceTypeForNestedTypes) diff --git a/src/Compiler/Checking/infos.fs b/src/Compiler/Checking/infos.fs index 1a38e10f42b..2504d5d3eab 100644 --- a/src/Compiler/Checking/infos.fs +++ b/src/Compiler/Checking/infos.fs @@ -81,10 +81,10 @@ let GetCompiledReturnTyOfProvidedMethodInfo amap m (mi: Tainted - let parentToMemberInst, _ = mkTyparToTyparRenaming (ovByMethValRef.MemberApparentEntity.Typars m) enclosingTypars + let parentToMemberInst, _ = mkTyparToTyparRenaming (ovByMethValRef.MemberApparentEntity.Typars) enclosingTypars let res = instSlotSig parentToMemberInst slotsig res | None -> @@ -1331,7 +1331,7 @@ type MethInfo = // A slot signature is w.r.t. the type variables of the type it is associated with. // So we have to rename from the member type variables to the type variables of the type. - let formalEnclosingTypars = x.ApparentEnclosingTyconRef.Typars m + let formalEnclosingTypars = x.ApparentEnclosingTyconRef.Typars let formalEnclosingTyparsFromMethod, formalMethTypars = List.splitAt formalEnclosingTypars.Length allTyparsFromMethod let methodToParentRenaming, _ = mkTyparToTyparRenaming formalEnclosingTyparsFromMethod formalEnclosingTypars let formalParams = @@ -1350,7 +1350,7 @@ type MethInfo = // then that does not correspond to a slotsig compiled as a 'void' return type. // REVIEW: should we copy down attributes to slot params? let tcref = tcrefOfAppTy g x.ApparentEnclosingAppType - let formalEnclosingTyparsOrig = tcref.Typars m + let formalEnclosingTyparsOrig = tcref.Typars let formalEnclosingTypars = copyTypars false formalEnclosingTyparsOrig let _, formalEnclosingTyparTys = FixupNewTypars m [] [] formalEnclosingTyparsOrig formalEnclosingTypars let formalMethTypars = copyTypars false x.FormalMethodTypars @@ -1451,7 +1451,7 @@ type MethInfo = /// For extension methods, no type parameters are returned, because all the /// type parameters are part of the apparent type, rather the /// declaring type, even for extension methods extending generic types. - member x.GetFormalTyparsOfDeclaringType m = + member x.GetFormalTyparsOfDeclaringType(_m: range) = if x.IsExtensionMember then [] else match x with @@ -1460,7 +1460,7 @@ type MethInfo = let memberParentTypars, _, _, _ = AnalyzeTypeOfMemberVal false g (ty, vref) memberParentTypars | _ -> - x.DeclaringTyconRef.Typars m + x.DeclaringTyconRef.Typars /// Tries to get the object arg type if it's a byref type. member x.TryObjArgByrefType(amap, m, minst) = @@ -1689,7 +1689,7 @@ type UnionCaseInfo = member x.DisplayName = x.UnionCase.DisplayName /// Get the instantiation of the type parameters of the declaring type of the union case - member x.GetTyparInst m = mkTyparInst (x.TyconRef.Typars m) x.TypeInst + member x.GetTyparInst(_m: range) = mkTyparInst (x.TyconRef.Typars) x.TypeInst override x.ToString() = x.TyconRef.ToString() + "::" + x.DisplayNameCore diff --git a/src/Compiler/Checking/infos.fsi b/src/Compiler/Checking/infos.fsi index e091834e271..91913793a67 100644 --- a/src/Compiler/Checking/infos.fsi +++ b/src/Compiler/Checking/infos.fsi @@ -43,7 +43,7 @@ val GetCompiledReturnTyOfProvidedMethodInfo: /// The slotsig returned by methInfo.GetSlotSig is in terms of the type parameters on the parent type of the overriding method. /// Reverse-map the slotsig so it is in terms of the type parameters for the overriding method -val ReparentSlotSigToUseMethodTypars: g: TcGlobals -> m: range -> ovByMethValRef: ValRef -> slotsig: SlotSig -> SlotSig +val ReparentSlotSigToUseMethodTypars: g: TcGlobals -> _m: range -> ovByMethValRef: ValRef -> slotsig: SlotSig -> SlotSig /// Construct the data representing a parameter in the signature of an abstract method slot val MakeSlotParam: ty: TType * argInfo: ArgReprInfo -> SlotParam @@ -514,7 +514,7 @@ type MethInfo = /// For extension methods, no type parameters are returned, because all the /// type parameters are part of the apparent type, rather the /// declaring type, even for extension methods extending generic types. - member GetFormalTyparsOfDeclaringType: m: range -> Typar list + member GetFormalTyparsOfDeclaringType: _m: range -> Typar list /// Get the (zero or one) 'self'/'this'/'object' arguments associated with a method. /// An instance method returns one object argument. @@ -705,7 +705,7 @@ type UnionCaseInfo = member UnionCaseRef: UnionCaseRef /// Get the instantiation of the type parameters of the declaring type of the union case - member GetTyparInst: m: range -> TyparInstantiation + member GetTyparInst: _m: range -> TyparInstantiation /// Describes an F# use of a property backed by Abstract IL metadata [] diff --git a/src/Compiler/CodeGen/IlxGen.fs b/src/Compiler/CodeGen/IlxGen.fs index 939e3da324e..f86de5af1d2 100644 --- a/src/Compiler/CodeGen/IlxGen.fs +++ b/src/Compiler/CodeGen/IlxGen.fs @@ -558,7 +558,7 @@ type TypeReprEnv member eenv.ForTypars tps = eenv.ResetTypars().Add tps /// Get the environment for within a type definition - member eenv.ForTycon(tycon: Tycon) = eenv.ForTypars tycon.TyparsNoRange + member eenv.ForTycon(tycon: Tycon) = eenv.ForTypars tycon.Typars /// Get the environment for generating a reference to items within a type definition member eenv.ForTyconRef(tcref: TyconRef) = eenv.ForTycon tcref.Deref @@ -1460,7 +1460,7 @@ let GetMethodSpecForMemberVal cenv (memberInfo: ValMemberInfo) (vref: ValRef) = let isCtor = (memberInfo.MemberFlags.MemberKind = SynMemberKind.Constructor) let cctor = (memberInfo.MemberFlags.MemberKind = SynMemberKind.ClassConstructor) let parentTcref = vref.DeclaringEntity - let parentTypars = parentTcref.TyparsNoRange + let parentTypars = parentTcref.Typars let numParentTypars = parentTypars.Length if tps.Length < numParentTypars then @@ -4495,7 +4495,7 @@ and GenApp (cenv: cenv) cgbuf eenv (f, fty, tyargs, curriedArgs, m) sequel = // @REVIEW: refactor this let numEnclILTypeArgs = match vref.MemberInfo with - | Some _ when not vref.IsExtensionMember -> List.length (vref.MemberApparentEntity.TyparsNoRange |> DropErasedTypars) + | Some _ when not vref.IsExtensionMember -> List.length (vref.MemberApparentEntity.Typars |> DropErasedTypars) | _ -> 0 let ilEnclArgTys, ilMethArgTys = @@ -8678,7 +8678,7 @@ and GenBindingAfterDebugPoint cenv cgbuf eenv bind isStateVar startMarkOpt = // The initialization code for static 'let' and 'do' bindings gets compiled into the initialization .cctor for the whole file | _ when vspec.IsClassConstructor - && isNil vspec.DeclaringEntity.TyparsNoRange + && isNil vspec.DeclaringEntity.Typars && not isStateVar -> let tps, _, _, _, cctorBody, _ = @@ -11214,7 +11214,7 @@ and GenTypeDef cenv mgbuf lazyInitInfo eenv m (tycon: Tycon) : ILTypeRef option let ilThisTy = GenType cenv m eenvinner.tyenv thisTy let tref = ilThisTy.TypeRef - let ilGenParams = GenGenericParams cenv eenvinner tycon.TyparsNoRange + let ilGenParams = GenGenericParams cenv eenvinner tycon.Typars let checkNullness = g.langFeatureNullness && g.checkNullness let ilIntfTys = @@ -11423,7 +11423,7 @@ and GenTypeDef cenv mgbuf lazyInitInfo eenv m (tycon: Tycon) : ILTypeRef option isEmptyStruct && cenv.options.workAroundReflectionEmitBugs - && not tycon.TyparsNoRange.IsEmpty + && not tycon.Typars.IsEmpty // Compute a bunch of useful things for each field let isCLIMutable = @@ -11821,7 +11821,7 @@ and GenTypeDef cenv mgbuf lazyInitInfo eenv m (tycon: Tycon) : ILTypeRef option // If this property doesn't hold then the .cctor can end up running // before the main method even starts. let typeDefTrigger = - if eenv.isFinalFile || tycon.TyparsNoRange.IsEmpty then + if eenv.isFinalFile || tycon.Typars.IsEmpty then ILTypeInit.OnAny else ILTypeInit.BeforeField @@ -11920,7 +11920,7 @@ and GenTypeDef cenv mgbuf lazyInitInfo eenv m (tycon: Tycon) : ILTypeRef option || // Reflection emit doesn't let us emit 'pack' and 'size' for generic structs. // In that case we generate a dummy field instead - (cenv.options.workAroundReflectionEmitBugs && not tycon.TyparsNoRange.IsEmpty) + (cenv.options.workAroundReflectionEmitBugs && not tycon.Typars.IsEmpty) then ILTypeDefLayout.Sequential { Size = None; Pack = None }, ILDefaultPInvokeEncoding.Ansi else @@ -12169,7 +12169,7 @@ and GenTypeDef cenv mgbuf lazyInitInfo eenv m (tycon: Tycon) : ILTypeRef option // // In this case, the .cctor for this type must force the .cctor of the backing static class for the file. if - tycon.TyparsNoRange.IsEmpty + tycon.Typars.IsEmpty && not eenv.realsig && tycon.MembersOfFSharpTyconSorted |> List.exists (fun vref -> vref.Deref.IsClassConstructor) diff --git a/src/Compiler/Driver/CompilerDiagnostics.fs b/src/Compiler/Driver/CompilerDiagnostics.fs index 098e23e147f..5d3871558e9 100644 --- a/src/Compiler/Driver/CompilerDiagnostics.fs +++ b/src/Compiler/Driver/CompilerDiagnostics.fs @@ -1055,7 +1055,7 @@ type Exception with os.AppendString(FSComp.SR.notAFunctionWithType (NicePrint.prettyStringOfTy denv ty)) | TyconBadArgs(_, tcref, d, _) -> - let exp = tcref.TyparsNoRange.Length + let exp = tcref.Typars.Length if exp = 0 then os.AppendString(FSComp.SR.buildUnexpectedTypeArgs (fullDisplayTextOfTyconRef tcref, d)) diff --git a/src/Compiler/Service/FSharpCheckerResults.fs b/src/Compiler/Service/FSharpCheckerResults.fs index 6e9c1ced079..ce829a08f3d 100644 --- a/src/Compiler/Service/FSharpCheckerResults.fs +++ b/src/Compiler/Service/FSharpCheckerResults.fs @@ -2231,11 +2231,11 @@ type internal TypeCheckInfo |> List.sortBy (fun d -> let n = match d.Item with - | Item.Types(_, AbbrevOrAppTy(tcref, _) :: _) -> 1 + tcref.TyparsNoRange.Length + | Item.Types(_, AbbrevOrAppTy(tcref, _) :: _) -> 1 + tcref.Typars.Length // Put delegate ctors after types, sorted by #typars. RemoveDuplicateItems will remove FakeInterfaceCtor and DelegateCtor if an earlier type is also reported with this name - | Item.DelegateCtor(AbbrevOrAppTy(tcref, _)) -> 1000 + tcref.TyparsNoRange.Length + | Item.DelegateCtor(AbbrevOrAppTy(tcref, _)) -> 1000 + tcref.Typars.Length // Put type ctors after types, sorted by #typars. RemoveDuplicateItems will remove DefaultStructCtors if a type is also reported with this name - | Item.CtorGroup(_, cinfo :: _) -> 1000 + 10 * cinfo.DeclaringTyconRef.TyparsNoRange.Length + | Item.CtorGroup(_, cinfo :: _) -> 1000 + 10 * cinfo.DeclaringTyconRef.Typars.Length | _ -> 0 (d.Item.DisplayName, n)) diff --git a/src/Compiler/Service/ServiceDeclarationLists.fs b/src/Compiler/Service/ServiceDeclarationLists.fs index 98312a69306..f80d957c414 100644 --- a/src/Compiler/Service/ServiceDeclarationLists.fs +++ b/src/Compiler/Service/ServiceDeclarationLists.fs @@ -1092,12 +1092,12 @@ type DeclarationListInfo(declarations: DeclarationListItem[], isForType: bool, i items |> List.map (fun x -> match x.Item with - | Item.Types (_, TType_app(tcref, _, _) :: _) when isInterfaceTyconRef tcref -> { x with MinorPriority = 1000 + tcref.TyparsNoRange.Length } - | Item.Types (_, TType_app(tcref, _, _) :: _) -> { x with MinorPriority = 1 + tcref.TyparsNoRange.Length } + | Item.Types (_, TType_app(tcref, _, _) :: _) when isInterfaceTyconRef tcref -> { x with MinorPriority = 1000 + tcref.Typars.Length } + | Item.Types (_, TType_app(tcref, _, _) :: _) -> { x with MinorPriority = 1 + tcref.Typars.Length } // Put delegate ctors after types, sorted by #typars. RemoveDuplicateItems will remove FakeInterfaceCtor and DelegateCtor if an earlier type is also reported with this name - | Item.DelegateCtor (TType_app(tcref, _, _)) -> { x with MinorPriority = 1000 + tcref.TyparsNoRange.Length } + | Item.DelegateCtor (TType_app(tcref, _, _)) -> { x with MinorPriority = 1000 + tcref.Typars.Length } // Put type ctors after types, sorted by #typars. RemoveDuplicateItems will remove DefaultStructCtors if a type is also reported with this name - | Item.CtorGroup (_, cinfo :: _) -> { x with MinorPriority = 1000 + 10 * cinfo.DeclaringTyconRef.TyparsNoRange.Length } + | Item.CtorGroup (_, cinfo :: _) -> { x with MinorPriority = 1000 + 10 * cinfo.DeclaringTyconRef.Typars.Length } | Item.MethodGroup(_, minfo :: _, _) -> { x with IsOwnMember = tyconRefOptEq x.Type minfo.DeclaringTyconRef } | Item.Property(info = pinfo :: _) -> { x with IsOwnMember = tyconRefOptEq x.Type pinfo.DeclaringTyconRef } | Item.ILField finfo -> { x with IsOwnMember = tyconRefOptEq x.Type finfo.DeclaringTyconRef } diff --git a/src/Compiler/Symbols/Exprs.fs b/src/Compiler/Symbols/Exprs.fs index 4281393e5e8..48bca7384f9 100644 --- a/src/Compiler/Symbols/Exprs.fs +++ b/src/Compiler/Symbols/Exprs.fs @@ -1181,7 +1181,7 @@ module FSharpExprConvert = let isCtor = (ilMethRef.Name = ".ctor") let isStatic = isCtor || ilMethRef.CallingConv.IsStatic let scoref = ilMethRef.DeclaringTypeRef.Scope - let typars1 = tcref.Typars m + let typars1 = tcref.Typars let typars2 = [ 1 .. ilMethRef.GenericArity ] |> List.map (fun _ -> Construct.NewRigidTypar "T" m) let tinst1 = typars1 |> generalizeTypars let tinst2 = typars2 |> generalizeTypars diff --git a/src/Compiler/Symbols/Symbols.fs b/src/Compiler/Symbols/Symbols.fs index 08252dd76d5..772440f126f 100644 --- a/src/Compiler/Symbols/Symbols.fs +++ b/src/Compiler/Symbols/Symbols.fs @@ -389,7 +389,7 @@ type FSharpEntity(cenv: SymbolEnv, entity: EntityRef, tyargs: TType list) = | Some ccu -> ccuEq ccu cenv.g.fslibCcu new(cenv: SymbolEnv, tcref: TyconRef) = - let _, _, tyargs = FreshenTypeInst cenv.g range0 (tcref.Typars range0) + let _, _, tyargs = FreshenTypeInst cenv.g range0 (tcref.Typars) FSharpEntity(cenv, tcref, tyargs) member _.Entity = entity @@ -484,7 +484,7 @@ type FSharpEntity(cenv: SymbolEnv, entity: EntityRef, tyargs: TType list) = member _.GenericParameters = checkIsResolved() - entity.TyparsNoRange |> List.map (fun tp -> FSharpGenericParameter(cenv, tp)) |> makeReadOnlyCollection + entity.Typars |> List.map (fun tp -> FSharpGenericParameter(cenv, tp)) |> makeReadOnlyCollection member _.GenericArguments = checkIsResolved() @@ -742,7 +742,7 @@ type FSharpEntity(cenv: SymbolEnv, entity: EntityRef, tyargs: TType list) = if entity.IsILEnumTycon then let (TILObjectReprData(_scoref, _enc, tdef)) = entity.ILTyconInfo - let formalTypars = entity.Typars range0 + let formalTypars = entity.Typars let formalTypeInst = generalizeTypars formalTypars let ty = TType_app(entity, formalTypeInst, cenv.g.knownWithoutNull) let formalTypeInfo = ILTypeInfo.FromType cenv.g ty @@ -971,7 +971,7 @@ type FSharpUnionCase(cenv, v: UnionCaseRef) = inherit FSharpSymbol (cenv, (fun () -> checkEntityIsResolved v.TyconRef - Item.UnionCase(UnionCaseInfo(generalizeTypars v.TyconRef.TyparsNoRange, v), false)), + Item.UnionCase(UnionCaseInfo(generalizeTypars v.TyconRef.Typars, v), false)), (fun _this thisCcu2 ad -> checkForCrossProjectAccessibility cenv.g.ilg (thisCcu2, ad) (cenv.thisCcu, v.UnionCase.Accessibility)) //&& AccessibilityLogic.IsUnionCaseAccessible cenv.amap range0 ad v) @@ -1019,7 +1019,7 @@ type FSharpUnionCase(cenv, v: UnionCaseRef) = member _.XmlDocSig = checkIsResolved() - let unionCase = UnionCaseInfo(generalizeTypars v.TyconRef.TyparsNoRange, v) + let unionCase = UnionCaseInfo(generalizeTypars v.TyconRef.Typars, v) match GetXmlDocSigOfUnionCaseRef unionCase.UnionCaseRef with | Some (_, docsig) -> docsig | _ -> "" @@ -1090,10 +1090,10 @@ type FSharpField(cenv: SymbolEnv, d: FSharpFieldData) = Item.AnonRecdField(anonInfo, tinst, n, m) | RecdOrClass v -> checkEntityIsResolved v.TyconRef - Item.RecdField(RecdFieldInfo(generalizeTypars v.TyconRef.TyparsNoRange, v)) + Item.RecdField(RecdFieldInfo(generalizeTypars v.TyconRef.Typars, v)) | Union (v, fieldIndex) -> checkEntityIsResolved v.TyconRef - Item.UnionCaseField (UnionCaseInfo (generalizeTypars v.TyconRef.TyparsNoRange, v), fieldIndex) + Item.UnionCaseField (UnionCaseInfo (generalizeTypars v.TyconRef.Typars, v), fieldIndex) | ILField f -> Item.ILField f), (fun this thisCcu2 ad -> @@ -1194,10 +1194,10 @@ type FSharpField(cenv: SymbolEnv, d: FSharpFieldData) = let xmlsig = match d with | RecdOrClass v -> - let recd = RecdFieldInfo(generalizeTypars v.TyconRef.TyparsNoRange, v) + let recd = RecdFieldInfo(generalizeTypars v.TyconRef.Typars, v) GetXmlDocSigOfRecdFieldRef recd.RecdFieldRef | Union (v, _) -> - let unionCase = UnionCaseInfo(generalizeTypars v.TyconRef.TyparsNoRange, v) + let unionCase = UnionCaseInfo(generalizeTypars v.TyconRef.Typars, v) GetXmlDocSigOfUnionCaseRef unionCase.UnionCaseRef | ILField f -> GetXmlDocSigOfILFieldInfo cenv.infoReader range0 f diff --git a/src/Compiler/TypedTree/TypedTree.fs b/src/Compiler/TypedTree/TypedTree.fs index 733b269b588..6334697ef91 100644 --- a/src/Compiler/TypedTree/TypedTree.fs +++ b/src/Compiler/TypedTree/TypedTree.fs @@ -761,7 +761,7 @@ type Entity = #endif else ignore withStaticParameters - match x.TyparsNoRange with + match x.Typars with | [] -> nm | tps -> let nm = DemangleGenericTypeName nm @@ -893,12 +893,9 @@ type Entity = CompilationPath.DemangleEntityName x.LogicalName x.ModuleOrNamespaceType.ModuleOrNamespaceKind /// Get the type parameters for an entity that is a type declaration, otherwise return the empty list. - /// - /// Lazy because it may read metadata, must provide a context "range" in case error occurs reading metadata. - member x.Typars m = x.entity_typars.Force m - - /// Get the type parameters for an entity that is a type declaration, otherwise return the empty list. - member x.TyparsNoRange: Typars = x.Typars x.Range + /// + /// Lazy because it may read metadata. Uses the entity's own range for error context. + member x.Typars: Typars = x.entity_typars.Force x.Range /// Get the type abbreviated by this type definition, if it is an F# type abbreviation definition member x.TypeAbbrev = @@ -1331,7 +1328,7 @@ type Entity = | _ -> ilTypeRefForCompilationPath x.CompilationPath x.CompiledName // Pre-allocate a ILType for monomorphic types, to reduce memory usage from Abstract IL nodes let ilTypeOpt = - match x.TyparsNoRange with + match x.Typars with | [] -> Some (mkILTy boxity (mkILTySpec (ilTypeRef, []))) | _ -> None CompiledTypeRepr.ILAsmNamed (ilTypeRef, boxity, ilTypeOpt)) @@ -3792,12 +3789,9 @@ type EntityRef = member x.IsFSharpException = x.Deref.IsFSharpException /// Get the type parameters for an entity that is a type declaration, otherwise return the empty list. - /// - /// Lazy because it may read metadata, must provide a context "range" in case error occurs reading metadata. - member x.Typars m = x.Deref.Typars m - - /// Get the type parameters for an entity that is a type declaration, otherwise return the empty list. - member x.TyparsNoRange = x.Deref.TyparsNoRange + /// + /// Lazy because it may read metadata. Uses the entity's own range for error context. + member x.Typars = x.Deref.Typars /// Indicates if this entity is an F# type abbreviation definition member x.TypeAbbrev = x.Deref.TypeAbbrev diff --git a/src/Compiler/TypedTree/TypedTree.fsi b/src/Compiler/TypedTree/TypedTree.fsi index 7fbe446641a..be249ece112 100644 --- a/src/Compiler/TypedTree/TypedTree.fsi +++ b/src/Compiler/TypedTree/TypedTree.fsi @@ -509,8 +509,8 @@ type Entity = /// Get the type parameters for an entity that is a type declaration, otherwise return the empty list. /// - /// Lazy because it may read metadata, must provide a context "range" in case error occurs reading metadata. - member Typars: m: range -> Typars + /// Lazy because it may read metadata. Uses the entity's own range for error context. + member Typars: Typars /// Get the value representing the accessibility of an F# type definition or module. member Accessibility: Accessibility @@ -782,9 +782,6 @@ type Entity = /// These two bits represents the on-demand analysis about whether the entity has the IsReadOnly attribute member TryIsReadOnly: bool voption - /// Get the type parameters for an entity that is a type declaration, otherwise return the empty list. - member TyparsNoRange: Typars - /// Get the type abbreviated by this type definition, if it is an F# type abbreviation definition member TypeAbbrev: TType option @@ -2460,8 +2457,8 @@ type EntityRef = /// Get the type parameters for an entity that is a type declaration, otherwise return the empty list. /// - /// Lazy because it may read metadata, must provide a context "range" in case error occurs reading metadata. - member Typars: m: range -> Typars + /// Lazy because it may read metadata. Uses the entity's own range for error context. + member Typars: Typars /// Get the value representing the accessibility of an F# type definition or module. member Accessibility: Accessibility @@ -2731,9 +2728,6 @@ type EntityRef = /// The on-demand analysis about whether the entity has the IsReadOnly attribute member TryIsReadOnly: bool voption - /// Get the type parameters for an entity that is a type declaration, otherwise return the empty list. - member TyparsNoRange: Typars - /// Indicates if this entity is an F# type abbreviation definition member TypeAbbrev: TType option diff --git a/src/Compiler/TypedTree/TypedTreeOps.Attributes.fs b/src/Compiler/TypedTree/TypedTreeOps.Attributes.fs index a57c6e23804..21c180bd330 100644 --- a/src/Compiler/TypedTree/TypedTreeOps.Attributes.fs +++ b/src/Compiler/TypedTree/TypedTreeOps.Attributes.fs @@ -2318,7 +2318,7 @@ module internal DebugPrint = ) ) ^^ wordL (tagText tycon.DisplayName) - ^^ layoutTyparDecls tycon.TyparsNoRange + ^^ layoutTyparDecls tycon.Typars let lhsL = lhsL --- layoutAttribs tycon.Attribs diff --git a/src/Compiler/TypedTree/TypedTreeOps.FreeVars.fs b/src/Compiler/TypedTree/TypedTreeOps.FreeVars.fs index 054df3371fd..ffabfdab59c 100644 --- a/src/Compiler/TypedTree/TypedTreeOps.FreeVars.fs +++ b/src/Compiler/TypedTree/TypedTreeOps.FreeVars.fs @@ -661,7 +661,7 @@ module internal MemberRepresentation = | Some _ -> if v.IsExtensionMember then 0 elif not v.IsMember then 0 - else v.MemberApparentEntity.TyparsNoRange.Length + else v.MemberApparentEntity.Typars.Length let GetValReprTypeInCompiledForm g valReprInfo numEnclosingTypars ty m = let tps, paramArgInfos, retTy, retInfo = @@ -729,7 +729,7 @@ module internal MemberRepresentation = | Some arities -> let fullTypars, _ = destTopForallTy g arities v.Type let parent = v.MemberApparentEntity - let parentTypars = parent.TyparsNoRange + let parentTypars = parent.Typars let nparentTypars = parentTypars.Length if nparentTypars <= fullTypars.Length then @@ -816,7 +816,7 @@ module internal MemberRepresentation = // Generalize type constructors to types //--------------------------------------------------------------------------- - let generalTyconRefInst (tcref: TyconRef) = generalizeTypars tcref.TyparsNoRange + let generalTyconRefInst (tcref: TyconRef) = generalizeTypars tcref.Typars let generalizeTyconRef (g: TcGlobals) tcref = let tinst = generalTyconRefInst tcref diff --git a/src/Compiler/TypedTree/TypedTreeOps.Remap.fs b/src/Compiler/TypedTree/TypedTreeOps.Remap.fs index 605b189ce17..76830e1b377 100644 --- a/src/Compiler/TypedTree/TypedTreeOps.Remap.fs +++ b/src/Compiler/TypedTree/TypedTreeOps.Remap.fs @@ -515,7 +515,7 @@ module internal TypeRemapping = let tinst = generalizeTypars tps mkTyparInst tpsorig tinst, tinst - let mkTyconInst (tycon: Tycon) tinst = mkTyparInst tycon.TyparsNoRange tinst + let mkTyconInst (tycon: Tycon) tinst = mkTyparInst tycon.Typars tinst let mkTyconRefInst (tcref: TyconRef) tinst = mkTyconInst tcref.Deref tinst [] diff --git a/src/Compiler/TypedTree/TypedTreeOps.Remapping.fs b/src/Compiler/TypedTree/TypedTreeOps.Remapping.fs index de28bc34138..cf73a881aa9 100644 --- a/src/Compiler/TypedTree/TypedTreeOps.Remapping.fs +++ b/src/Compiler/TypedTree/TypedTreeOps.Remapping.fs @@ -2233,7 +2233,7 @@ module internal ExprRemapping = let lookupTycon tycon = lookupTycon tycon let tpsR, tmenvinner2 = - tmenvCopyRemapAndBindTypars (remapAttribs ctxt tmenvinner) tmenvinner (tcd.entity_typars.Force(tcd.entity_range)) + tmenvCopyRemapAndBindTypars (remapAttribs ctxt tmenvinner) tmenvinner tcd.Typars tcdR.entity_typars <- LazyWithContext.NotLazy tpsR tcdR.entity_attribs <- WellKnownEntityAttribs.Create(tcd.entity_attribs.AsList() |> remapAttribs ctxt tmenvinner2) diff --git a/src/Compiler/TypedTree/TypedTreeOps.Transforms.fs b/src/Compiler/TypedTree/TypedTreeOps.Transforms.fs index d9503800020..d2735bc6e5d 100644 --- a/src/Compiler/TypedTree/TypedTreeOps.Transforms.fs +++ b/src/Compiler/TypedTree/TypedTreeOps.Transforms.fs @@ -673,7 +673,7 @@ module internal TypeTestsAndPatterns = let GetMemberCallInfo g (vref: ValRef, vFlags) = match vref.MemberInfo with | Some membInfo when not vref.IsExtensionMember -> - let numEnclTypeArgs = vref.MemberApparentEntity.TyparsNoRange.Length + let numEnclTypeArgs = vref.MemberApparentEntity.Typars.Length let virtualCall = (membInfo.MemberFlags.IsOverrideOrExplicitImpl @@ -978,7 +978,7 @@ module internal Rewriting = let rec remapEntityDataToNonLocal ctxt tmenv (d: Entity) = let tpsR, tmenvinner = - tmenvCopyRemapAndBindTypars (remapAttribs ctxt tmenv) tmenv (d.entity_typars.Force(d.entity_range)) + tmenvCopyRemapAndBindTypars (remapAttribs ctxt tmenv) tmenv d.Typars let typarsR = LazyWithContext.NotLazy tpsR let attribsR = d.entity_attribs.AsList() |> remapAttribs ctxt tmenvinner diff --git a/src/Compiler/TypedTree/TypedTreePickle.fs b/src/Compiler/TypedTree/TypedTreePickle.fs index 93e277c05b4..4fe8eaf121f 100644 --- a/src/Compiler/TypedTree/TypedTreePickle.fs +++ b/src/Compiler/TypedTree/TypedTreePickle.fs @@ -2792,7 +2792,7 @@ and p_rfield_table x st = p_array p_recdfield_spec x.FieldsByIndex st and p_entity_spec_data (x: Entity) st = - p_tyar_specs (x.entity_typars.Force(x.entity_range)) st + p_tyar_specs x.Typars st p_string x.entity_logical_name st p_option p_string x.EntityCompiledName st p_range x.entity_range st From f49e53a55e367cccf04085ecdcb2bdc8a5fb3998 Mon Sep 17 00:00:00 2001 From: Tomas Grosup Date: Wed, 27 May 2026 12:31:56 +0200 Subject: [PATCH 17/18] Fix CI: replace LINQ extension test case with instance method; format IlxGen.fs - Replace List.First() (LINQ extension, fails on net472) with List.Contains(1) (instance method, works on all TFMs) - Run fantomas on IlxGen.fs Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Compiler/CodeGen/IlxGen.fs | 6 +----- .../Nullness/NullableReferenceTypesTests.fs | 20 +++++++++++++++++-- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/src/Compiler/CodeGen/IlxGen.fs b/src/Compiler/CodeGen/IlxGen.fs index f86de5af1d2..8dd3568efc7 100644 --- a/src/Compiler/CodeGen/IlxGen.fs +++ b/src/Compiler/CodeGen/IlxGen.fs @@ -8676,11 +8676,7 @@ and GenBindingAfterDebugPoint cenv cgbuf eenv bind isStateVar startMarkOpt = CommitStartScope cgbuf startMarkOpt // The initialization code for static 'let' and 'do' bindings gets compiled into the initialization .cctor for the whole file - | _ when - vspec.IsClassConstructor - && isNil vspec.DeclaringEntity.Typars - && not isStateVar - -> + | _ when vspec.IsClassConstructor && isNil vspec.DeclaringEntity.Typars && not isStateVar -> let tps, _, _, _, cctorBody, _ = IteratedAdjustLambdaToMatchValReprInfo g cenv.amap vspec.ValReprInfo.Value rhsExpr diff --git a/tests/FSharp.Compiler.ComponentTests/Language/Nullness/NullableReferenceTypesTests.fs b/tests/FSharp.Compiler.ComponentTests/Language/Nullness/NullableReferenceTypesTests.fs index cefe8cf9560..18e503d32cd 100644 --- a/tests/FSharp.Compiler.ComponentTests/Language/Nullness/NullableReferenceTypesTests.fs +++ b/tests/FSharp.Compiler.ComponentTests/Language/Nullness/NullableReferenceTypesTests.fs @@ -1,6 +1,7 @@ module Language.NullableReferenceTypes open Xunit +open FSharp.Test open FSharp.Test.Compiler let withNullnessOptions cu = @@ -2384,8 +2385,8 @@ let main _ = 0 2, 28, 29, "Trim", "x", "string | null")>] [] -[ | null) = xs.First()", - 3, 59, 61, "First", "xs", "int seq | null")>] +[ | null) = xs.Contains(1)", + 2, 59, 61, "Contains", "xs", "System.Collections.Generic.List | null")>] [ | null) = xs.GetEnumerator()", 2, 66, 68, "GetEnumerator", "xs", "System.Collections.Generic.IEnumerable | null")>] [] +let ``Issue 19658 - LINQ extension method dot-access on nullable receiver`` () = + FSharp """module MyLib +open System.Linq +let f (xs: System.Collections.Generic.List | null) = xs.First()""" + |> asLibrary + |> typeCheckWithStrictNullness + |> shouldFail + |> withDiagnostics [ + Error 3261, Line 3, Col 59, Line 3, Col 61, + "Nullness warning: Possible dereference of a null value when accessing member 'First' on the nullable value 'xs' of type 'int seq | null'." + ] + [] let ``Issue 19658 - nullness warning on complex receiver omits binding name`` () = FSharp """module MyLib From c626f20aca506815e93884e36106478875f8c723 Mon Sep 17 00:00:00 2001 From: Tomas Grosup Date: Thu, 28 May 2026 10:52:40 +0200 Subject: [PATCH 18/18] Remove dead range parameters left over from Entity.Typars cleanup MemberAccessOnNullable now carries ObjArgInfo directly instead of re-allocating a 3-tuple. Dead _m: range parameters removed from TryFindMetadataInfoOfExternalEntityRef, ReparentSlotSigToUseMethodTypars, GetFormalTyparsOfDeclaringType, GetTyparInst, IsTyconRefUsedForCSharpStyleExtensionMembers, CanAutoOpenTyconRef, ResolveNestedTypeThroughAbbreviation, and cascading callers (GetXmlDocSigOfEntityRef, GetXmlDocSigOfEvent, GetXmlDocSigOfILFieldInfo, IsTypeUsedForCSharpStyleExtensionMembers). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Compiler/Checking/ConstraintSolver.fs | 16 ++++++------ src/Compiler/Checking/ConstraintSolver.fsi | 4 +-- .../Checking/Expressions/CheckExpressions.fs | 2 +- src/Compiler/Checking/InfoReader.fs | 18 ++++++------- src/Compiler/Checking/InfoReader.fsi | 9 +++---- src/Compiler/Checking/MethodOverrides.fs | 4 +-- src/Compiler/Checking/NameResolution.fs | 26 +++++++++---------- src/Compiler/Checking/NicePrint.fs | 6 ++--- src/Compiler/Checking/infos.fs | 8 +++--- src/Compiler/Checking/infos.fsi | 6 ++--- src/Compiler/Symbols/SymbolHelpers.fs | 12 ++++----- src/Compiler/Symbols/Symbols.fs | 7 +++-- 12 files changed, 58 insertions(+), 60 deletions(-) diff --git a/src/Compiler/Checking/ConstraintSolver.fs b/src/Compiler/Checking/ConstraintSolver.fs index 78688b0c717..be783e68b4a 100644 --- a/src/Compiler/Checking/ConstraintSolver.fs +++ b/src/Compiler/Checking/ConstraintSolver.fs @@ -127,7 +127,7 @@ let FreshMethInst g m fctps tinst tpsorig = FreshenAndFixupTypars g m TyparRigidity.Flexible fctps tinst tpsorig let FreshenMethInfo m (minfo: MethInfo) = - let _, _, tpTys = FreshMethInst minfo.TcGlobals m (minfo.GetFormalTyparsOfDeclaringType m) minfo.DeclaringTypeInst minfo.FormalMethodTypars + let _, _, tpTys = FreshMethInst minfo.TcGlobals m (minfo.GetFormalTyparsOfDeclaringType()) minfo.DeclaringTypeInst minfo.FormalMethodTypars tpTys //------------------------------------------------------------------------- @@ -187,12 +187,12 @@ type ContextInfo = | NullnessCheckOfCapturedArg of range /// Obj-argument type check in a dotted member access on a nullable receiver. - | MemberAccessOnNullable of objExprRange: range * memberName: string * bindingName: string option + | MemberAccessOnNullable of ObjArgInfo /// Receiver information for a dotted member access, used to produce /// targeted nullness warnings (e.g. "Possible dereference of null when /// accessing member 'M' on the nullable value 'x'"). -type ObjArgInfo = +and ObjArgInfo = { ObjExprRange: range MemberName: string BindingName: string option } @@ -1063,7 +1063,7 @@ and shouldWarnUselessNullCheck (csenv:ConstraintSolverEnv) = and getNullnessWarningRange (csenv: ConstraintSolverEnv) = match csenv.eContextInfo with | ContextInfo.NullnessCheckOfCapturedArg capturedArgRange -> capturedArgRange - | ContextInfo.MemberAccessOnNullable (objExprRange, _, _) -> objExprRange + | ContextInfo.MemberAccessOnNullable info -> info.ObjExprRange | _ -> csenv.m // nullness1: actual @@ -1140,8 +1140,8 @@ and SolveNullnessSubsumesNullness (csenv: ConstraintSolverEnv) m2 (trace: Option | NullnessInfo.WithoutNull, NullnessInfo.WithNull -> if csenv.g.checkNullness then match csenv.eContextInfo with - | ContextInfo.MemberAccessOnNullable (objExprRange, memberName, bindingName) -> - WarnD(ConstraintSolverNullnessWarningOnDotAccess(csenv.DisplayEnv, ty2, memberName, bindingName, objExprRange, m2)) + | ContextInfo.MemberAccessOnNullable info -> + WarnD(ConstraintSolverNullnessWarningOnDotAccess(csenv.DisplayEnv, ty2, info.MemberName, info.BindingName, info.ObjExprRange, m2)) | _ -> WarnD(ConstraintSolverNullnessWarningWithTypes(csenv.DisplayEnv, ty1, ty2, n1, n2, getNullnessWarningRange csenv, m2)) else @@ -4009,7 +4009,7 @@ let ResolveOverloadingForCall denv css m objArgInfo methodName callerArgs ad cal | Some info when csenvNoCtx.g.checkNullness -> { csenvNoCtx with eContextInfo = - ContextInfo.MemberAccessOnNullable(info.ObjExprRange, info.MemberName, info.BindingName) } + ContextInfo.MemberAccessOnNullable info } | _ -> csenvNoCtx ResolveOverloading csenv NoTrace methodName 0 None callerArgs ad calledMethGroup permitOptArgs (Some reqdRetTy) @@ -4031,7 +4031,7 @@ let UnifyUniqueOverloading | Some info when csenvNoCtx.g.checkNullness -> { csenvNoCtx with eContextInfo = - ContextInfo.MemberAccessOnNullable(info.ObjExprRange, info.MemberName, info.BindingName) } + ContextInfo.MemberAccessOnNullable info } | _ -> csenvNoCtx let m = csenv.m // See what candidates we have based on name and arity diff --git a/src/Compiler/Checking/ConstraintSolver.fsi b/src/Compiler/Checking/ConstraintSolver.fsi index 3baed64891a..eebd72c2e60 100644 --- a/src/Compiler/Checking/ConstraintSolver.fsi +++ b/src/Compiler/Checking/ConstraintSolver.fsi @@ -67,12 +67,12 @@ type ContextInfo = | NullnessCheckOfCapturedArg of range /// Obj-argument type check in a dotted member access on a nullable receiver. - | MemberAccessOnNullable of objExprRange: range * memberName: string * bindingName: string option + | MemberAccessOnNullable of ObjArgInfo /// Receiver information for a dotted member access, used to produce /// targeted nullness warnings (e.g. "Possible dereference of null when /// accessing member 'M' on the nullable value 'x'"). -type ObjArgInfo = +and ObjArgInfo = { ObjExprRange: range MemberName: string BindingName: string option } diff --git a/src/Compiler/Checking/Expressions/CheckExpressions.fs b/src/Compiler/Checking/Expressions/CheckExpressions.fs index 4d79c9017a7..5be255a0d52 100644 --- a/src/Compiler/Checking/Expressions/CheckExpressions.fs +++ b/src/Compiler/Checking/Expressions/CheckExpressions.fs @@ -1947,7 +1947,7 @@ let FreshenAbstractSlot g amap m synTyparDecls absMethInfo = // If the virtual method is a generic method then copy its type parameters let typarsFromAbsSlot, typarInstFromAbsSlot, _ = - let ttps = absMethInfo.GetFormalTyparsOfDeclaringType m + let ttps = absMethInfo.GetFormalTyparsOfDeclaringType() let ttinst = argsOfAppTy g absMethInfo.ApparentEnclosingType let rigid = if typarsFromAbsSlotAreRigid then TyparRigidity.Rigid else TyparRigidity.Flexible FreshenAndFixupTypars g m rigid ttps ttinst fmtps diff --git a/src/Compiler/Checking/InfoReader.fs b/src/Compiler/Checking/InfoReader.fs index b050ed48cdd..2a4e75135f1 100644 --- a/src/Compiler/Checking/InfoReader.fs +++ b/src/Compiler/Checking/InfoReader.fs @@ -1163,7 +1163,7 @@ let PropTypeOfEventInfo (infoReader: InfoReader) m ad (einfo: EventInfo) = mkIEventType g delTy argsTy /// Try to find the name of the metadata file for this external definition -let TryFindMetadataInfoOfExternalEntityRef (infoReader: InfoReader) (_m: range) eref = +let TryFindMetadataInfoOfExternalEntityRef (infoReader: InfoReader) eref = let g = infoReader.g match eref with | ERefLocal _ -> None @@ -1189,9 +1189,9 @@ let private libFileOfEntityRef x = | ERefLocal _ -> None | ERefNonLocal nlref -> nlref.Ccu.FileName -let GetXmlDocSigOfEntityRef infoReader m (eref: EntityRef) = +let GetXmlDocSigOfEntityRef infoReader (eref: EntityRef) = if eref.IsILTycon then - match TryFindMetadataInfoOfExternalEntityRef infoReader m eref with + match TryFindMetadataInfoOfExternalEntityRef infoReader eref with | None -> None | Some (ccuFileName, _, formalTypeInfo) -> Some(ccuFileName, "T:"+formalTypeInfo.ILTypeRef.FullName) else @@ -1240,7 +1240,7 @@ let rec GetXmlDocSigOfMethInfo (infoReader: InfoReader) m (minfo: MethInfo) = let fmtps = ilminfo.FormalMethodTypars let genericArity = if fmtps.Length=0 then "" else sprintf "``%d" fmtps.Length - match TryFindMetadataInfoOfExternalEntityRef infoReader m ilminfo.DeclaringTyconRef with + match TryFindMetadataInfoOfExternalEntityRef infoReader ilminfo.DeclaringTyconRef with | None -> None | Some (ccuFileName, formalTypars, formalTypeInfo) -> let filminfo = ILMethInfo(g, IlType formalTypeInfo, ilminfo.RawMetadata, fmtps) @@ -1294,23 +1294,23 @@ let GetXmlDocSigOfProp infoReader m (pinfo: PropInfo) = | None -> None | Some vref -> GetXmlDocSigOfScopedValRef g pinfo.DeclaringTyconRef vref | ILProp(ILPropInfo(_, pdef)) -> - match TryFindMetadataInfoOfExternalEntityRef infoReader m pinfo.DeclaringTyconRef with + match TryFindMetadataInfoOfExternalEntityRef infoReader pinfo.DeclaringTyconRef with | Some (ccuFileName, formalTypars, formalTypeInfo) -> let filpinfo = ILPropInfo(formalTypeInfo, pdef) Some (ccuFileName, "P:"+formalTypeInfo.ILTypeRef.FullName+"."+pdef.Name+XmlDocArgsEnc g (formalTypars, []) (filpinfo.GetParamTypes(infoReader.amap, m))) | _ -> None -let GetXmlDocSigOfEvent infoReader m (einfo: EventInfo) = +let GetXmlDocSigOfEvent infoReader (einfo: EventInfo) = match einfo with | ILEvent _ -> - match TryFindMetadataInfoOfExternalEntityRef infoReader m einfo.DeclaringTyconRef with + match TryFindMetadataInfoOfExternalEntityRef infoReader einfo.DeclaringTyconRef with | Some (ccuFileName, _, formalTypeInfo) -> Some(ccuFileName, "E:"+formalTypeInfo.ILTypeRef.FullName+"."+einfo.EventName) | _ -> None | _ -> None -let GetXmlDocSigOfILFieldInfo infoReader m (finfo: ILFieldInfo) = - match TryFindMetadataInfoOfExternalEntityRef infoReader m finfo.DeclaringTyconRef with +let GetXmlDocSigOfILFieldInfo infoReader (finfo: ILFieldInfo) = + match TryFindMetadataInfoOfExternalEntityRef infoReader finfo.DeclaringTyconRef with | Some (ccuFileName, _, formalTypeInfo) -> Some(ccuFileName, "F:"+formalTypeInfo.ILTypeRef.FullName+"."+finfo.FieldName) | _ -> None diff --git a/src/Compiler/Checking/InfoReader.fsi b/src/Compiler/Checking/InfoReader.fsi index ff64d28c41c..bd0a1ebb56d 100644 --- a/src/Compiler/Checking/InfoReader.fsi +++ b/src/Compiler/Checking/InfoReader.fsi @@ -344,13 +344,13 @@ val PropTypeOfEventInfo: infoReader: InfoReader -> m: range -> ad: AccessorDomai /// Try to find the name of the metadata file for this external definition val TryFindMetadataInfoOfExternalEntityRef: - infoReader: InfoReader -> _m: range -> eref: EntityRef -> (string option * Typars * ILTypeInfo) option + infoReader: InfoReader -> eref: EntityRef -> (string option * Typars * ILTypeInfo) option /// Try to find the xml doc associated with the assembly name and metadata key val TryFindXmlDocByAssemblyNameAndSig: infoReader: InfoReader -> assemblyName: string -> xmlDocSig: string -> XmlDoc option -val GetXmlDocSigOfEntityRef: infoReader: InfoReader -> m: range -> eref: EntityRef -> (string option * string) option +val GetXmlDocSigOfEntityRef: infoReader: InfoReader -> eref: EntityRef -> (string option * string) option val GetXmlDocSigOfScopedValRef: TcGlobals -> tcref: TyconRef -> vref: ValRef -> (string option * string) option @@ -364,7 +364,6 @@ val GetXmlDocSigOfValRef: TcGlobals -> vref: ValRef -> (string option * string) val GetXmlDocSigOfProp: infoReader: InfoReader -> m: range -> pinfo: PropInfo -> (string option * string) option -val GetXmlDocSigOfEvent: infoReader: InfoReader -> m: range -> einfo: EventInfo -> (string option * string) option +val GetXmlDocSigOfEvent: infoReader: InfoReader -> einfo: EventInfo -> (string option * string) option -val GetXmlDocSigOfILFieldInfo: - infoReader: InfoReader -> m: range -> finfo: ILFieldInfo -> (string option * string) option +val GetXmlDocSigOfILFieldInfo: infoReader: InfoReader -> finfo: ILFieldInfo -> (string option * string) option diff --git a/src/Compiler/Checking/MethodOverrides.fs b/src/Compiler/Checking/MethodOverrides.fs index 6da48fc0b14..125bed2fdb8 100644 --- a/src/Compiler/Checking/MethodOverrides.fs +++ b/src/Compiler/Checking/MethodOverrides.fs @@ -883,7 +883,7 @@ module DispatchSlotChecking = let overridden = if isFakeEventProperty then let slotsigs = overrideBy.MemberInfo.Value.ImplementedSlotSigs - slotsigs |> List.map (ReparentSlotSigToUseMethodTypars g overrideBy.Range overrideBy) + slotsigs |> List.map (ReparentSlotSigToUseMethodTypars g overrideBy) else [ for (reqdTy, m), SlotImplSet(_dispatchSlots, dispatchSlotsKeyed, _, _) in allImpls do let overrideByInfo = GetTypeMemberOverrideInfo g reqdTy overrideBy @@ -907,7 +907,7 @@ module DispatchSlotChecking = // The slotsig from the overridden method is in terms of the type parameters on the parent type of the overriding method, // Modify map the slotsig so it is in terms of the type parameters for the overriding method - let slotsig = ReparentSlotSigToUseMethodTypars g m overrideBy slotsig + let slotsig = ReparentSlotSigToUseMethodTypars g overrideBy slotsig // Record the slotsig via mutation yield slotsig ] diff --git a/src/Compiler/Checking/NameResolution.fs b/src/Compiler/Checking/NameResolution.fs index 9509dc0e0d8..593ce6c4b64 100644 --- a/src/Compiler/Checking/NameResolution.fs +++ b/src/Compiler/Checking/NameResolution.fs @@ -529,7 +529,7 @@ type ResultCollectionSettings = let NextExtensionMethodPriority() = uint64 (newStamp()) /// Checks if the type is used for C# style extension members. -let IsTyconRefUsedForCSharpStyleExtensionMembers g _m (tcref: TyconRef) = +let IsTyconRefUsedForCSharpStyleExtensionMembers g (tcref: TyconRef) = // Type must be non-generic and have 'Extension' attribute match metadataOfTycon tcref.Deref with | ILTypeMetadata(TILObjectReprData(_, _, tdef)) -> tdef.CanContainExtensionMethods @@ -537,9 +537,9 @@ let IsTyconRefUsedForCSharpStyleExtensionMembers g _m (tcref: TyconRef) = && isNil(tcref.Typars) && TyconRefHasWellKnownAttribute g WellKnownILAttributes.ExtensionAttribute tcref /// Checks if the type is used for C# style extension members. -let IsTypeUsedForCSharpStyleExtensionMembers g m ty = +let IsTypeUsedForCSharpStyleExtensionMembers g ty = match tryTcrefOfAppTy g ty with - | ValueSome tcref -> IsTyconRefUsedForCSharpStyleExtensionMembers g m tcref + | ValueSome tcref -> IsTyconRefUsedForCSharpStyleExtensionMembers g tcref | _ -> false /// A 'plain' method is an extension method not interpreted as an extension method. @@ -591,7 +591,7 @@ let private GetCSharpStyleIndexedExtensionMembersForTyconRef (amap: Import.Impor let g = amap.g let isApplicable = - IsTyconRefUsedForCSharpStyleExtensionMembers g m tcrefOfStaticClass || + IsTyconRefUsedForCSharpStyleExtensionMembers g tcrefOfStaticClass || g.langVersion.SupportsFeature(LanguageFeature.CSharpExtensionAttributeNotRequired) && tcrefOfStaticClass.IsLocalRef && @@ -1167,7 +1167,7 @@ let GetNestedTypesOfType (ad, ncenv: NameResolver, optFilter, staticResInfo, che |> List.map (MakeNestedType ncenv tinst m) let ChooseMethInfosForNameEnv g m ty (minfos: MethInfo list) = - let isExtTy = IsTypeUsedForCSharpStyleExtensionMembers g m ty + let isExtTy = IsTypeUsedForCSharpStyleExtensionMembers g ty minfos |> List.filter (fun minfo -> @@ -1357,7 +1357,7 @@ and private AddStaticPartsOfTyconRefToNameEnv bulkAddMode ownDefinition g amap m eIndexedExtensionMembers = eIndexedExtensionMembers eUnindexedExtensionMembers = eUnindexedExtensionMembers } -and private CanAutoOpenTyconRef (g: TcGlobals) _m (tcref: TyconRef) = +and private CanAutoOpenTyconRef (g: TcGlobals) (tcref: TyconRef) = g.langVersion.SupportsFeature LanguageFeature.OpenTypeDeclaration && not tcref.IsILTycon && EntityHasWellKnownAttribute g WellKnownEntityAttributes.AutoOpenAttribute tcref.Deref && @@ -1398,7 +1398,7 @@ and private AddPartsOfTyconRefToNameEnv bulkAddMode ownDefinition (g: TcGlobals) let nenv = AddStaticPartsOfTyconRefToNameEnv bulkAddMode ownDefinition g amap m nenv None tcref let nenv = - if CanAutoOpenTyconRef g m tcref then + if CanAutoOpenTyconRef g tcref then let ty = generalizedTyconRef g tcref AddStaticContentOfTypeToNameEnv g amap ad m nenv ty else @@ -1673,7 +1673,7 @@ let FreshenTypars g m tpsorig = tpTys let FreshenMethInfo m (minfo: MethInfo) = - let _, _, tpTys = FreshMethInst minfo.TcGlobals m (minfo.GetFormalTyparsOfDeclaringType m) minfo.DeclaringTypeInst minfo.FormalMethodTypars + let _, _, tpTys = FreshMethInst minfo.TcGlobals m (minfo.GetFormalTyparsOfDeclaringType()) minfo.DeclaringTypeInst minfo.FormalMethodTypars tpTys /// This must be called after fetching unqualified items that may need to be freshened @@ -3599,7 +3599,7 @@ let ResolvePatternLongIdent sink (ncenv: NameResolver) warnOnUpper newDef m ad n // // X.ListEnumerator // does not resolve // -let ResolveNestedTypeThroughAbbreviation (ncenv: NameResolver) (tcref: TyconRef) _m = +let ResolveNestedTypeThroughAbbreviation (ncenv: NameResolver) (tcref: TyconRef) = if tcref.IsTypeAbbrev && tcref.Typars.IsEmpty then match tryAppTy ncenv.g tcref.TypeAbbrev.Value with | ValueSome (abbrevTcref, []) -> abbrevTcref @@ -3609,7 +3609,7 @@ let ResolveNestedTypeThroughAbbreviation (ncenv: NameResolver) (tcref: TyconRef) /// Resolve a long identifier representing a type name let rec ResolveTypeLongIdentInTyconRefPrim (ncenv: NameResolver) (typeNameResInfo: TypeNameResolutionInfo) ad resInfo genOk depth m (tcref: TyconRef) (id: Ident) (rest: Ident list) = - let tcref = ResolveNestedTypeThroughAbbreviation ncenv tcref m + let tcref = ResolveNestedTypeThroughAbbreviation ncenv tcref match rest with | [] -> #if !NO_TYPEPROVIDERS @@ -4963,7 +4963,7 @@ let TryToResolveLongIdentAsType (ncenv: NameResolver) (nenv: NameResolutionEnv) LookupTypeNameInEnvNoArity OpenQualified id nenv |> List.tryHead |> Option.map (fun tcref -> - let tcref = ResolveNestedTypeThroughAbbreviation ncenv tcref m + let tcref = ResolveNestedTypeThroughAbbreviation ncenv tcref FreshenTycon ncenv m tcref) | _ -> None @@ -5058,7 +5058,7 @@ let rec ResolvePartialLongIdentPrim (ncenv: NameResolver) (nenv: NameResolutionE [ if not isItemVal then // type.lookup: lookup a static something in a type for tcref in LookupTypeNameInEnvNoArity OpenQualified id nenv do - let tcref = ResolveNestedTypeThroughAbbreviation ncenv tcref m + let tcref = ResolveNestedTypeThroughAbbreviation ncenv tcref let ty = FreshenTycon ncenv m tcref yield! ResolvePartialLongIdentInType ncenv nenv isApplicableMeth m ad true rest ty allowObsolete @@ -5596,7 +5596,7 @@ let rec GetCompletionForItem (ncenv: NameResolver) (nenv: NameResolutionEnv) m a | _ -> // type.lookup: lookup a static something in a type for tcref in LookupTypeNameInEnvNoArity OpenQualified id nenv do - let tcref = ResolveNestedTypeThroughAbbreviation ncenv tcref m + let tcref = ResolveNestedTypeThroughAbbreviation ncenv tcref let ty = FreshenTycon ncenv m tcref yield! ResolvePartialLongIdentInTypeForItem ncenv nenv m ad true rest item ty } diff --git a/src/Compiler/Checking/NicePrint.fs b/src/Compiler/Checking/NicePrint.fs index 0fa25a31dfc..d1097a7c416 100644 --- a/src/Compiler/Checking/NicePrint.fs +++ b/src/Compiler/Checking/NicePrint.fs @@ -301,14 +301,14 @@ module internal PrintUtilities = let layoutXmlDocOfEventInfo (denv: DisplayEnv) (infoReader: InfoReader) (einfo: EventInfo) restL = if denv.showDocumentation then - GetXmlDocSigOfEvent infoReader Range.range0 einfo + GetXmlDocSigOfEvent infoReader einfo |> layoutXmlDocFromSig denv infoReader true einfo.XmlDoc restL else restL let layoutXmlDocOfILFieldInfo (denv: DisplayEnv) (infoReader: InfoReader) (finfo: ILFieldInfo) restL = if denv.showDocumentation then - GetXmlDocSigOfILFieldInfo infoReader Range.range0 finfo + GetXmlDocSigOfILFieldInfo infoReader finfo |> layoutXmlDocFromSig denv infoReader true XmlDoc.Empty restL else restL @@ -329,7 +329,7 @@ module internal PrintUtilities = let layoutXmlDocOfEntity (denv: DisplayEnv) (infoReader: InfoReader) (eref: EntityRef) restL = if denv.showDocumentation then - GetXmlDocSigOfEntityRef infoReader Range.range0 eref + GetXmlDocSigOfEntityRef infoReader eref |> layoutXmlDocFromSig denv infoReader true eref.XmlDoc restL else restL diff --git a/src/Compiler/Checking/infos.fs b/src/Compiler/Checking/infos.fs index 2504d5d3eab..d4da136b80a 100644 --- a/src/Compiler/Checking/infos.fs +++ b/src/Compiler/Checking/infos.fs @@ -81,7 +81,7 @@ let GetCompiledReturnTyOfProvidedMethodInfo amap m (mi: Tainted let parentToMemberInst, _ = mkTyparToTyparRenaming (ovByMethValRef.MemberApparentEntity.Typars) enclosingTypars @@ -1451,7 +1451,7 @@ type MethInfo = /// For extension methods, no type parameters are returned, because all the /// type parameters are part of the apparent type, rather the /// declaring type, even for extension methods extending generic types. - member x.GetFormalTyparsOfDeclaringType(_m: range) = + member x.GetFormalTyparsOfDeclaringType() = if x.IsExtensionMember then [] else match x with @@ -1689,7 +1689,7 @@ type UnionCaseInfo = member x.DisplayName = x.UnionCase.DisplayName /// Get the instantiation of the type parameters of the declaring type of the union case - member x.GetTyparInst(_m: range) = mkTyparInst (x.TyconRef.Typars) x.TypeInst + member x.GetTyparInst() = mkTyparInst (x.TyconRef.Typars) x.TypeInst override x.ToString() = x.TyconRef.ToString() + "::" + x.DisplayNameCore @@ -2509,7 +2509,7 @@ let CompiledSigOfMeth g amap m (minfo: MethInfo) = // of the enclosing type. This instantiations can be used to interpret those type parameters let fmtpinst = let parentTyArgs = argsOfAppTy g minfo.ApparentEnclosingAppType - let memberParentTypars = minfo.GetFormalTyparsOfDeclaringType m + let memberParentTypars = minfo.GetFormalTyparsOfDeclaringType() mkTyparInst memberParentTypars parentTyArgs CompiledSig(vargTys, vrty, formalMethTypars, fmtpinst) diff --git a/src/Compiler/Checking/infos.fsi b/src/Compiler/Checking/infos.fsi index 91913793a67..0b30ea6f50c 100644 --- a/src/Compiler/Checking/infos.fsi +++ b/src/Compiler/Checking/infos.fsi @@ -43,7 +43,7 @@ val GetCompiledReturnTyOfProvidedMethodInfo: /// The slotsig returned by methInfo.GetSlotSig is in terms of the type parameters on the parent type of the overriding method. /// Reverse-map the slotsig so it is in terms of the type parameters for the overriding method -val ReparentSlotSigToUseMethodTypars: g: TcGlobals -> _m: range -> ovByMethValRef: ValRef -> slotsig: SlotSig -> SlotSig +val ReparentSlotSigToUseMethodTypars: g: TcGlobals -> ovByMethValRef: ValRef -> slotsig: SlotSig -> SlotSig /// Construct the data representing a parameter in the signature of an abstract method slot val MakeSlotParam: ty: TType * argInfo: ArgReprInfo -> SlotParam @@ -514,7 +514,7 @@ type MethInfo = /// For extension methods, no type parameters are returned, because all the /// type parameters are part of the apparent type, rather the /// declaring type, even for extension methods extending generic types. - member GetFormalTyparsOfDeclaringType: _m: range -> Typar list + member GetFormalTyparsOfDeclaringType: unit -> Typar list /// Get the (zero or one) 'self'/'this'/'object' arguments associated with a method. /// An instance method returns one object argument. @@ -705,7 +705,7 @@ type UnionCaseInfo = member UnionCaseRef: UnionCaseRef /// Get the instantiation of the type parameters of the declaring type of the union case - member GetTyparInst: _m: range -> TyparInstantiation + member GetTyparInst: unit -> TyparInstantiation /// Describes an F# use of a property backed by Abstract IL metadata [] diff --git a/src/Compiler/Symbols/SymbolHelpers.fs b/src/Compiler/Symbols/SymbolHelpers.fs index 742e7640293..8466acf1aae 100644 --- a/src/Compiler/Symbols/SymbolHelpers.fs +++ b/src/Compiler/Symbols/SymbolHelpers.fs @@ -271,7 +271,7 @@ module internal SymbolHelpers = | Item.UnqualifiedType (tcref :: _) | Item.ExnCase tcref -> - mkXmlComment (GetXmlDocSigOfEntityRef infoReader m tcref) + mkXmlComment (GetXmlDocSigOfEntityRef infoReader tcref) | Item.RecdField rfinfo -> mkXmlComment (GetXmlDocSigOfRecdFieldRef rfinfo.RecdFieldRef) @@ -279,13 +279,13 @@ module internal SymbolHelpers = | Item.NewDef _ -> FSharpXmlDoc.None | Item.ILField finfo -> - mkXmlComment (GetXmlDocSigOfILFieldInfo infoReader m finfo) + mkXmlComment (GetXmlDocSigOfILFieldInfo infoReader finfo) | Item.DelegateCtor ty | Item.Types(_, ty :: _) -> match ty with | AbbrevOrAppTy(tcref, _) -> - mkXmlComment (GetXmlDocSigOfEntityRef infoReader m tcref) + mkXmlComment (GetXmlDocSigOfEntityRef infoReader tcref) | _ -> FSharpXmlDoc.None | Item.CustomOperation (_, _, Some minfo) -> @@ -296,13 +296,13 @@ module internal SymbolHelpers = | Item.TypeVar _ -> FSharpXmlDoc.None | Item.ModuleOrNamespaces(modref :: _) -> - mkXmlComment (GetXmlDocSigOfEntityRef infoReader m modref) + mkXmlComment (GetXmlDocSigOfEntityRef infoReader modref) | Item.Property(info = pinfo :: _) -> mkXmlComment (GetXmlDocSigOfProp infoReader m pinfo) | Item.Event einfo -> - mkXmlComment (GetXmlDocSigOfEvent infoReader m einfo) + mkXmlComment (GetXmlDocSigOfEvent infoReader einfo) | Item.MethodGroup(_, minfo :: _, _) -> mkXmlComment (GetXmlDocSigOfMethInfo infoReader m minfo) @@ -313,7 +313,7 @@ module internal SymbolHelpers = | Item.OtherName(container = Some argContainer) -> match argContainer with | ArgumentContainer.Method minfo -> mkXmlComment (GetXmlDocSigOfMethInfo infoReader m minfo) - | ArgumentContainer.Type tcref -> mkXmlComment (GetXmlDocSigOfEntityRef infoReader m tcref) + | ArgumentContainer.Type tcref -> mkXmlComment (GetXmlDocSigOfEntityRef infoReader tcref) | Item.UnionCaseField (ucinfo, _) -> mkXmlComment (GetXmlDocSigOfUnionCaseRef ucinfo.UnionCaseRef) diff --git a/src/Compiler/Symbols/Symbols.fs b/src/Compiler/Symbols/Symbols.fs index 772440f126f..3f309c91a8e 100644 --- a/src/Compiler/Symbols/Symbols.fs +++ b/src/Compiler/Symbols/Symbols.fs @@ -198,7 +198,7 @@ module Impl = let getXmlDocSigForEntity (cenv: SymbolEnv) (ent:EntityRef)= - match GetXmlDocSigOfEntityRef cenv.infoReader ent.Range ent with + match GetXmlDocSigOfEntityRef cenv.infoReader ent with | Some (_, docsig) -> docsig | _ -> "" @@ -1200,7 +1200,7 @@ type FSharpField(cenv: SymbolEnv, d: FSharpFieldData) = let unionCase = UnionCaseInfo(generalizeTypars v.TyconRef.Typars, v) GetXmlDocSigOfUnionCaseRef unionCase.UnionCaseRef | ILField f -> - GetXmlDocSigOfILFieldInfo cenv.infoReader range0 f + GetXmlDocSigOfILFieldInfo cenv.infoReader f | AnonField _ -> None match xmlsig with | Some (_, docsig) -> docsig @@ -2083,8 +2083,7 @@ type FSharpMemberOrFunctionOrValue(cenv, d:FSharpMemberOrValData, item) = match d with | E e -> - let range = defaultArg sym.DeclarationLocationOpt range0 - match GetXmlDocSigOfEvent cenv.infoReader range e with + match GetXmlDocSigOfEvent cenv.infoReader e with | Some (_, docsig) -> docsig | _ -> "" | P p ->