Skip to content

Commit 57026b8

Browse files
authored
Merge pull request #17398 from dotnet/feature-nullness-variance
Feature nullness :: Subsume nullness for contravariant type parameters (such as the one in IEqualityComparer<in T>)
2 parents 3752733 + 5f6b7da commit 57026b8

File tree

7 files changed

+85
-12
lines changed

7 files changed

+85
-12
lines changed

src/Compiler/Checking/ConstraintSolver.fs

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1411,6 +1411,36 @@ and SolveTypeEqualsTypeEqns csenv ndeep m2 trace cxsln origl1 origl2 =
14111411
ErrorD(ConstraintSolverTupleDiffLengths(csenv.DisplayEnv, csenv.eContextInfo, origl1, origl2, csenv.m, m2))
14121412
loop origl1 origl2
14131413

1414+
and SolveTypeEqualsTypeWithContravarianceEqns (csenv:ConstraintSolverEnv) ndeep m2 trace cxsln origl1 origl2 typars =
1415+
let isContravariant (t:Typar) =
1416+
t.typar_opt_data
1417+
|> Option.map (fun d -> d.typar_is_contravariant)
1418+
|> Option.defaultValue(false)
1419+
1420+
match origl1, origl2, typars with
1421+
| [], [], [] -> CompleteD
1422+
| _ ->
1423+
// We unwind Iterate2D by hand here for performance reasons.
1424+
let rec loop l1 l2 tps =
1425+
match l1, l2, tps with
1426+
| [], [], [] -> CompleteD
1427+
| h1 :: t1, h2 :: t2, hTp :: tTps when t1.Length = t2.Length && t1.Length = tTps.Length ->
1428+
trackErrors {
1429+
let h1 =
1430+
// For contravariant typars (`<in T> in C#'), if the required type is WithNull, the actual type can have any nullness it wants
1431+
// Without this added logic, their nullness would be forced to be equal.
1432+
if isContravariant hTp && (nullnessOfTy csenv.g h2).TryEvaluate() = ValueSome NullnessInfo.WithNull then
1433+
replaceNullnessOfTy csenv.g.knownWithNull h1
1434+
else
1435+
h1
1436+
1437+
do! SolveTypeEqualsTypeKeepAbbrevsWithCxsln csenv ndeep m2 trace cxsln h1 h2
1438+
do! loop t1 t2 tTps
1439+
}
1440+
| _ ->
1441+
ErrorD(ConstraintSolverTupleDiffLengths(csenv.DisplayEnv, csenv.eContextInfo, origl1, origl2, csenv.m, m2))
1442+
loop origl1 origl2 typars
1443+
14141444
and SolveFunTypeEqn csenv ndeep m2 trace cxsln domainTy1 domainTy2 rangeTy1 rangeTy2 =
14151445
trackErrors {
14161446
let g = csenv.g
@@ -1503,11 +1533,11 @@ and SolveTypeSubsumesType (csenv: ConstraintSolverEnv) ndeep m2 (trace: Optional
15031533
(tyconRefEq g tagc1 g.byrefkind_In_tcr || tyconRefEq g tagc1 g.byrefkind_Out_tcr) ) -> ()
15041534
| _ -> return! SolveTypeEqualsType csenv ndeep m2 trace cxsln tag1 tag2
15051535
}
1506-
| _ -> SolveTypeEqualsTypeEqns csenv ndeep m2 trace cxsln l1 l2
1536+
| _ -> SolveTypeEqualsTypeWithContravarianceEqns csenv ndeep m2 trace cxsln l1 l2 tc1.TyparsNoRange
15071537

15081538
| TType_app (tc1, l1, _) , TType_app (tc2, l2, _) when tyconRefEq g tc1 tc2 ->
1509-
trackErrors {
1510-
do! SolveTypeEqualsTypeEqns csenv ndeep m2 trace cxsln l1 l2
1539+
trackErrors {
1540+
do! SolveTypeEqualsTypeWithContravarianceEqns csenv ndeep m2 trace cxsln l1 l2 tc1.TyparsNoRange
15111541
do! SolveNullnessSubsumesNullness csenv m2 trace ty1 ty2 (nullnessOfTy g sty1) (nullnessOfTy g sty2)
15121542
}
15131543

src/Compiler/Checking/import.fs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -624,6 +624,8 @@ let ImportILGenericParameters amap m scoref tinst (nullableFallback:Nullness.Nul
624624
let tptys = tps |> List.map mkTyparTy
625625
let importInst = tinst@tptys
626626
(tps, gps) ||> List.iter2 (fun tp gp ->
627+
if gp.Variance = ILGenericVariance.ContraVariant then
628+
tp.MarkAsContravariant()
627629
let constraints =
628630
[
629631
if amap.g.langFeatureNullness && amap.g.checkNullness then

src/Compiler/TypedTree/TypedTree.fs

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2254,6 +2254,9 @@ type TyparOptionalData =
22542254

22552255
/// The declared attributes of the type parameter. Empty for type inference variables.
22562256
mutable typar_attribs: Attribs
2257+
2258+
/// Set to true if the typar is contravariant, i.e. declared as <in T> in C#
2259+
mutable typar_is_contravariant: bool
22572260
}
22582261

22592262
[<DebuggerBrowsable(DebuggerBrowsableState.Never)>]
@@ -2355,10 +2358,10 @@ type Typar =
23552358
member x.SetAttribs attribs =
23562359
match attribs, x.typar_opt_data with
23572360
| [], None -> ()
2358-
| [], Some { typar_il_name = None; typar_xmldoc = doc; typar_constraints = [] } when doc.IsEmpty ->
2361+
| [], Some { typar_il_name = None; typar_xmldoc = doc; typar_constraints = []; typar_is_contravariant = false } when doc.IsEmpty ->
23592362
x.typar_opt_data <- None
23602363
| _, Some optData -> optData.typar_attribs <- attribs
2361-
| _ -> x.typar_opt_data <- Some { typar_il_name = None; typar_xmldoc = XmlDoc.Empty; typar_constraints = []; typar_attribs = attribs }
2364+
| _ -> x.typar_opt_data <- Some { typar_il_name = None; typar_xmldoc = XmlDoc.Empty; typar_constraints = []; typar_attribs = attribs; typar_is_contravariant = false }
23622365

23632366
/// Get the XML documetnation for the type parameter
23642367
member x.XmlDoc =
@@ -2376,7 +2379,7 @@ type Typar =
23762379
member x.SetILName il_name =
23772380
match x.typar_opt_data with
23782381
| Some optData -> optData.typar_il_name <- il_name
2379-
| _ -> x.typar_opt_data <- Some { typar_il_name = il_name; typar_xmldoc = XmlDoc.Empty; typar_constraints = []; typar_attribs = [] }
2382+
| _ -> x.typar_opt_data <- Some { typar_il_name = il_name; typar_xmldoc = XmlDoc.Empty; typar_constraints = []; typar_attribs = []; typar_is_contravariant = false }
23802383

23812384
/// Indicates the display name of a type variable
23822385
member x.DisplayName = if x.Name = "?" then "?"+string x.Stamp else x.Name
@@ -2385,10 +2388,17 @@ type Typar =
23852388
member x.SetConstraints cs =
23862389
match cs, x.typar_opt_data with
23872390
| [], None -> ()
2388-
| [], Some { typar_il_name = None; typar_xmldoc = doc; typar_attribs = [] } when doc.IsEmpty ->
2391+
| [], Some { typar_il_name = None; typar_xmldoc = doc; typar_attribs = [];typar_is_contravariant = false } when doc.IsEmpty ->
23892392
x.typar_opt_data <- None
23902393
| _, Some optData -> optData.typar_constraints <- cs
2391-
| _ -> x.typar_opt_data <- Some { typar_il_name = None; typar_xmldoc = XmlDoc.Empty; typar_constraints = cs; typar_attribs = [] }
2394+
| _ -> x.typar_opt_data <- Some { typar_il_name = None; typar_xmldoc = XmlDoc.Empty; typar_constraints = cs; typar_attribs = []; typar_is_contravariant = false }
2395+
2396+
/// Marks the typar as being contravariant
2397+
member x.MarkAsContravariant() =
2398+
match x.typar_opt_data with
2399+
| Some optData -> optData.typar_is_contravariant <- true
2400+
| _ ->
2401+
x.typar_opt_data <- Some { typar_il_name = None; typar_xmldoc = XmlDoc.Empty; typar_constraints = []; typar_attribs = []; typar_is_contravariant = true }
23922402

23932403
/// Creates a type variable that contains empty data, and is not yet linked. Only used during unpickling of F# metadata.
23942404
static member NewUnlinked() : Typar =
@@ -2410,7 +2420,7 @@ type Typar =
24102420
x.typar_solution <- tg.typar_solution
24112421
match tg.typar_opt_data with
24122422
| Some tg ->
2413-
let optData = { typar_il_name = tg.typar_il_name; typar_xmldoc = tg.typar_xmldoc; typar_constraints = tg.typar_constraints; typar_attribs = tg.typar_attribs }
2423+
let optData = { typar_il_name = tg.typar_il_name; typar_xmldoc = tg.typar_xmldoc; typar_constraints = tg.typar_constraints; typar_attribs = tg.typar_attribs; typar_is_contravariant = tg.typar_is_contravariant }
24142424
x.typar_opt_data <- Some optData
24152425
| None -> ()
24162426

@@ -6142,7 +6152,7 @@ type Construct() =
61426152
typar_opt_data =
61436153
match attribs with
61446154
| [] -> None
6145-
| _ -> Some { typar_il_name = None; typar_xmldoc = XmlDoc.Empty; typar_constraints = []; typar_attribs = attribs } }
6155+
| _ -> Some { typar_il_name = None; typar_xmldoc = XmlDoc.Empty; typar_constraints = []; typar_attribs = attribs; typar_is_contravariant = false } }
61466156

61476157
/// Create a new type parameter node for a declared type parameter
61486158
static member NewRigidTypar nm m =

src/Compiler/TypedTree/TypedTree.fsi

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1485,6 +1485,9 @@ type TyparOptionalData =
14851485

14861486
/// The declared attributes of the type parameter. Empty for type inference variables.
14871487
mutable typar_attribs: Attribs
1488+
1489+
/// Set to true if the typar is contravariant, i.e. declared as <in T> in C#
1490+
mutable typar_is_contravariant: bool
14881491
}
14891492

14901493
override ToString: unit -> string
@@ -1541,6 +1544,9 @@ type Typar =
15411544
/// Adjusts the constraints associated with a type variable
15421545
member SetConstraints: cs: TyparConstraint list -> unit
15431546

1547+
/// Marks the typar as being contravariant
1548+
member MarkAsContravariant: unit -> unit
1549+
15441550
/// Sets whether a type variable is required at runtime
15451551
member SetDynamicReq: b: TyparDynamicReq -> unit
15461552

src/Compiler/TypedTree/TypedTreeBasics.fs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,7 @@ let mkTyparTy (tp:Typar) =
198198
// For fresh type variables clear the StaticReq when copying because the requirement will be re-established through the
199199
// process of type inference.
200200
let copyTypar clearStaticReq (tp: Typar) =
201-
let optData = tp.typar_opt_data |> Option.map (fun tg -> { typar_il_name = tg.typar_il_name; typar_xmldoc = tg.typar_xmldoc; typar_constraints = tg.typar_constraints; typar_attribs = tg.typar_attribs })
201+
let optData = tp.typar_opt_data |> Option.map (fun tg -> { typar_il_name = tg.typar_il_name; typar_xmldoc = tg.typar_xmldoc; typar_constraints = tg.typar_constraints; typar_attribs = tg.typar_attribs; typar_is_contravariant = tg.typar_is_contravariant })
202202
let flags = if clearStaticReq then tp.typar_flags.WithStaticReq(TyparStaticReq.None) else tp.typar_flags
203203
Typar.New { typar_id = tp.typar_id
204204
typar_flags = flags

src/Compiler/TypedTree/TypedTreePickle.fs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1683,7 +1683,7 @@ let u_tyar_spec_data st =
16831683
typar_opt_data=
16841684
match g, e, c with
16851685
| doc, [], [] when doc.IsEmpty -> None
1686-
| _ -> Some { typar_il_name = None; typar_xmldoc = g; typar_constraints = e; typar_attribs = c } }
1686+
| _ -> Some { typar_il_name = None; typar_xmldoc = g; typar_constraints = e; typar_attribs = c;typar_is_contravariant = false } }
16871687

16881688
let u_tyar_spec st =
16891689
u_osgn_decl st.itypars u_tyar_spec_data st

tests/FSharp.Compiler.ComponentTests/Language/Nullness/NullableReferenceTypesTests.fs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -849,6 +849,31 @@ let mappableFunc =
849849
|> typeCheckWithStrictNullness
850850
|> shouldSucceed
851851

852+
[<Fact>]
853+
let ``Importing and processing contravariant interfaces`` () =
854+
855+
FSharp """module MyLibrary
856+
857+
open System
858+
open System.Collections.Concurrent
859+
open System.Collections.Generic
860+
861+
862+
let cmp1 : IEqualityComparer<string> = StringComparer.Ordinal
863+
let cmp2 : IEqualityComparer<string | null> = StringComparer.Ordinal
864+
let stringHash = cmp2.GetHashCode("abc")
865+
let nullHash = cmp2.GetHashCode(null)
866+
let nullEquals = cmp2.Equals("abc", null)
867+
868+
let dict = ConcurrentDictionary<string, int> (StringComparer.Ordinal)
869+
dict["ok"] <- 42
870+
871+
"""
872+
|> asLibrary
873+
|> typeCheckWithStrictNullness
874+
|> shouldSucceed
875+
876+
852877
[<Fact>]
853878
let ``Notnull constraint and inline annotated value`` () =
854879
FSharp """module MyLibrary

0 commit comments

Comments
 (0)