Skip to content

Commit 88f8041

Browse files
authored
Bugfix :: Fix 'Type inference problem too complicated' for SRTP with "T:null and T:struct" dummy constraint (#18345)
1 parent a9003e8 commit 88f8041

24 files changed

+264
-100
lines changed

docs/release-notes/.FSharp.Compiler.Service/9.0.300.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
* Fix NRE when accessing nullable fields of types within their equals/hash/compare methods ([PR #18296](https://github.com/dotnet/fsharp/pull/18296))
1414
* Fix nullness warning for overrides of generic code with nullable type instance ([Issue #17988](https://github.com/dotnet/fsharp/issues/17988), [PR #18337](https://github.com/dotnet/fsharp/pull/18337))
1515
* Unsafe downcast from `obj` to generic `T` no longer requires `not null` constraint on `T`([Issue #18275](https://github.com/dotnet/fsharp/issues/18275), [PR #18343](https://github.com/dotnet/fsharp/pull/18343))
16+
* Fix "type inference problem too complicated" for SRTP with T:null and T:struct dummy constraint([Issue #18288](https://github.com/dotnet/fsharp/issues/18288), [PR #18345](https://github.com/dotnet/fsharp/pull/18345))
1617
* Fix for missing parse diagnostics in TransparentCompiler.ParseAndCheckProject ([PR #18366](https://github.com/dotnet/fsharp/pull/18366))
1718
* Miscellanous parentheses analyzer fixes. ([PR #18350](https://github.com/dotnet/fsharp/pull/18350))
1819

src/Compiler/Checking/AugmentWithHashCompare.fs

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1647,13 +1647,7 @@ let rec TypeDefinitelyHasEquality g ty =
16471647
match appTy with
16481648
| ValueSome(tcref, _) when HasFSharpAttribute g g.attrib_NoEqualityAttribute tcref.Attribs -> false
16491649
| _ ->
1650-
if
1651-
isTyparTy g ty
1652-
&& (destTyparTy g ty).Constraints
1653-
|> List.exists (function
1654-
| TyparConstraint.SupportsEquality _ -> true
1655-
| _ -> false)
1656-
then
1650+
if ty |> IsTyparTyWithConstraint g _.IsSupportsEquality then
16571651
true
16581652
else
16591653
match ty with

src/Compiler/Checking/CheckDeclarations.fs

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2138,17 +2138,13 @@ module TyconConstraintInference =
21382138

21392139
// Is the field type a type parameter?
21402140
match tryDestTyparTy g ty with
2141-
| ValueSome tp ->
2142-
// Look for an explicit 'comparison' constraint
2143-
if tp.Constraints |> List.exists (function TyparConstraint.SupportsComparison _ -> true | _ -> false) then
2144-
true
2145-
2141+
| ValueSome tp when tp |> HasConstraint _.IsSupportsComparison -> true
2142+
| ValueSome tp ->
21462143
// Within structural types, type parameters can be optimistically assumed to have comparison
21472144
// We record the ones for which we have made this assumption.
2148-
elif tycon.TyparsNoRange |> List.exists (fun tp2 -> typarRefEq tp tp2) then
2145+
if tycon.TyparsNoRange |> List.exists (fun tp2 -> typarRefEq tp tp2) then
21492146
assumedTyparsAcc <- assumedTyparsAcc.Add(tp.Stamp)
2150-
true
2151-
2147+
true
21522148
else
21532149
false
21542150
| _ ->
@@ -2267,14 +2263,11 @@ module TyconConstraintInference =
22672263
// and type parameters.
22682264
let rec checkIfFieldTypeSupportsEquality (tycon: Tycon) (ty: TType) =
22692265
match tryDestTyparTy g ty with
2266+
| ValueSome tp when tp |> HasConstraint _.IsSupportsEquality -> true
22702267
| ValueSome tp ->
2271-
// Look for an explicit 'equality' constraint
2272-
if tp.Constraints |> List.exists (function TyparConstraint.SupportsEquality _ -> true | _ -> false) then
2273-
true
2274-
22752268
// Within structural types, type parameters can be optimistically assumed to have equality
22762269
// We record the ones for which we have made this assumption.
2277-
elif tycon.Typars(tycon.Range) |> List.exists (fun tp2 -> typarRefEq tp tp2) then
2270+
if tycon.Typars(tycon.Range) |> List.exists (fun tp2 -> typarRefEq tp tp2) then
22782271
assumedTyparsAcc <- assumedTyparsAcc.Add(tp.Stamp)
22792272
true
22802273
else

src/Compiler/Checking/ConstraintSolver.fs

Lines changed: 20 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -958,13 +958,6 @@ let rec SolveTyparEqualsTypePart1 (csenv: ConstraintSolverEnv) m2 (trace: Option
958958
// Record the solution before we solve the constraints, since
959959
// We may need to make use of the equation when solving the constraints.
960960
// Record a entry in the undo trace if one is provided
961-
962-
//let ty1AllowsNull = r.Constraints |> List.exists (function | TyparConstraint.SupportsNull _ -> true | _ -> false )
963-
//let tyAllowsNull() = TypeNullIsExtraValueNew csenv.g m2 ty
964-
//if ty1AllowsNull && not (tyAllowsNull()) then
965-
// trace.Exec (fun () -> r.typar_solution <- Some (ty |> replaceNullnessOfTy csenv.g.knownWithNull)) (fun () -> r.typar_solution <- None)
966-
//else
967-
// trace.Exec (fun () -> r.typar_solution <- Some ty) (fun () -> r.typar_solution <- None)
968961
trace.Exec (fun () -> r.typar_solution <- Some ty) (fun () -> r.typar_solution <- None)
969962
}
970963

@@ -1295,8 +1288,9 @@ and SolveTypeEqualsType (csenv: ConstraintSolverEnv) ndeep m2 (trace: OptionalTr
12951288
SolveTyparEqualsType csenv ndeep m2 trace sty1 (replaceNullnessOfTy g.knownWithoutNull sty2)
12961289
| ValueSome NullnessInfo.WithoutNull, ValueSome NullnessInfo.WithoutNull when
12971290
csenv.IsSupportsNullFlex &&
1298-
isAppTy g sty2 &&
1299-
tp1.Constraints |> List.exists (function TyparConstraint.SupportsNull _ -> true | _ -> false) ->
1291+
isAppTy g sty2 &&
1292+
tp1 |> HasConstraint _.IsSupportsNull &&
1293+
not(tp1 |> HasConstraint _.IsIsNonNullableStruct)->
13001294
let tpNew = NewCompGenTypar(TyparKind.Type, TyparRigidity.Flexible, TyparStaticReq.None, TyparDynamicReq.No, false)
13011295
trackErrors {
13021296
do! SolveTypeEqualsType csenv ndeep m2 trace cxsln (TType_var(tpNew, g.knownWithoutNull)) sty2
@@ -1614,10 +1608,10 @@ and SolveTyparSubtypeOfType (csenv: ConstraintSolverEnv) ndeep m2 trace tp ty1 =
16141608
else
16151609
AddConstraint csenv ndeep m2 trace tp (TyparConstraint.CoercesTo(ty1, csenv.m))
16161610

1617-
and DepthCheck ndeep m =
1618-
if ndeep > 300 then
1619-
error(Error(FSComp.SR.csTypeInferenceMaxDepth(), m))
1620-
else
1611+
and DepthCheck ndeep m =
1612+
if ndeep > 300 then
1613+
error(Error(FSComp.SR.csTypeInferenceMaxDepth(), m))
1614+
else
16211615
CompleteD
16221616

16231617
// If this is a type that's parameterized on a unit-of-measure (expected to be numeric), unify its measure with 1
@@ -2426,14 +2420,21 @@ and EnforceConstraintConsistency (csenv: ConstraintSolverEnv) ndeep m2 trace ret
24262420
return! SolveTypeEqualsTypeKeepAbbrevs csenv ndeep m2 trace retTy1 retTy2
24272421

24282422
| TyparConstraint.SupportsComparison _, TyparConstraint.IsDelegate _
2429-
| TyparConstraint.IsDelegate _, TyparConstraint.SupportsComparison _
2423+
| TyparConstraint.IsDelegate _, TyparConstraint.SupportsComparison _ ->
2424+
return! ErrorD (Error(FSComp.SR.csComparisonDelegateConstraintInconsistent(), m))
2425+
24302426
| TyparConstraint.IsNonNullableStruct _, TyparConstraint.IsReferenceType _
24312427
| TyparConstraint.IsReferenceType _, TyparConstraint.IsNonNullableStruct _ ->
24322428
return! ErrorD (Error(FSComp.SR.csStructConstraintInconsistent(), m))
24332429

24342430
| TyparConstraint.SupportsNull _, TyparConstraint.NotSupportsNull _
24352431
| TyparConstraint.NotSupportsNull _, TyparConstraint.SupportsNull _ ->
24362432
return! ErrorD (Error(FSComp.SR.csNullNotNullConstraintInconsistent(), m))
2433+
2434+
| TyparConstraint.SupportsNull _, TyparConstraint.IsNonNullableStruct _
2435+
| TyparConstraint.IsNonNullableStruct _, TyparConstraint.SupportsNull _ ->
2436+
()
2437+
//return! WarnD (Error(FSComp.SR.csNullStructConstraintInconsistent(), m))
24372438

24382439
| TyparConstraint.IsUnmanaged _, TyparConstraint.IsReferenceType _
24392440
| TyparConstraint.IsReferenceType _, TyparConstraint.IsUnmanaged _ ->
@@ -2640,7 +2641,7 @@ and SolveTypeUseSupportsNull (csenv: ConstraintSolverEnv) ndeep m2 trace ty =
26402641
| ValueSome NullnessInfo.WithoutNull ->
26412642
return! AddConstraint csenv ndeep m2 trace tp (TyparConstraint.SupportsNull m)
26422643
| _ ->
2643-
if tp.Constraints |> List.exists (function | TyparConstraint.IsReferenceType _ -> true | _ -> false) |> not then
2644+
if not (tp |> HasConstraint _.IsIsReferenceType) then
26442645
do! AddConstraint csenv ndeep m2 trace tp (TyparConstraint.IsReferenceType m)
26452646
return! SolveNullnessSupportsNull csenv ndeep m2 trace ty nullness
26462647
| _ ->
@@ -2737,7 +2738,7 @@ and SolveTypeCanCarryNullness (csenv: ConstraintSolverEnv) ty nullness =
27372738
let strippedTy = stripTyEqnsA g true ty
27382739
match tryAddNullnessToTy nullness strippedTy with
27392740
| Some _ ->
2740-
if isTyparTy g strippedTy && not (isReferenceTyparTy g strippedTy) then
2741+
if isTyparTy g strippedTy && not (IsReferenceTyparTy g strippedTy) then
27412742
return! AddConstraint csenv 0 m NoTrace (destTyparTy g strippedTy) (TyparConstraint.IsReferenceType m)
27422743
| None ->
27432744
let tyString = NicePrint.minimalStringOfType csenv.DisplayEnv strippedTy
@@ -2978,10 +2979,11 @@ and SolveTypeRequiresDefaultValue (csenv: ConstraintSolverEnv) ndeep m2 trace or
29782979
let g = csenv.g
29792980
let m = csenv.m
29802981
let ty = stripTyEqnsAndMeasureEqns g origTy
2982+
29812983
if isTyparTy g ty then
2982-
if isNonNullableStructTyparTy g ty then
2984+
if IsNonNullableStructTyparTy g ty then
29832985
SolveTypeRequiresDefaultConstructor csenv ndeep m2 trace ty
2984-
elif isReferenceTyparTy g ty then
2986+
elif IsReferenceTyparTy g ty then
29852987
SolveTypeUseSupportsNull csenv ndeep m2 trace ty
29862988
else
29872989
ErrorD (ConstraintSolverError(FSComp.SR.csGenericConstructRequiresStructOrReferenceConstraint(), m, m2))

src/Compiler/Checking/Expressions/CheckExpressions.fs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2194,7 +2194,7 @@ module GeneralizationHelpers =
21942194

21952195
let relevantUniqueSubtypeConstraint (tp: Typar) =
21962196
// Find a single subtype constraint
2197-
match tp.Constraints |> List.partition (function TyparConstraint.CoercesTo _ -> true | _ -> false) with
2197+
match tp.Constraints |> List.partition _.IsCoercesTo with
21982198
| [TyparConstraint.CoercesTo(tgtTy, _)], others ->
21992199
// Throw away null constraints if they are implied
22002200
if others |> List.exists (function TyparConstraint.SupportsNull _ -> not (TypeNullIsExtraValue g m tgtTy) | _ -> true)

src/Compiler/CodeGen/IlxGen.fs

Lines changed: 6 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -5676,32 +5676,14 @@ and GenGenericParam cenv eenv (tp: Typar) =
56765676
|> List.map (GenTypeAux cenv tp.Range eenv.tyenv VoidNotOK PtrTypesNotOK)
56775677

56785678
let refTypeConstraint =
5679-
tp.Constraints
5680-
|> List.exists (function
5681-
| TyparConstraint.IsReferenceType _
5682-
// 'null' automatically implies 'not struct'
5683-
| TyparConstraint.SupportsNull _ -> true
5684-
| _ -> false)
5679+
tp |> HasConstraint(fun tc -> tc.IsIsReferenceType || tc.IsSupportsNull) // `null` implies not struct
56855680

5686-
let notNullableValueTypeConstraint =
5687-
tp.Constraints
5688-
|> List.exists (function
5689-
| TyparConstraint.IsNonNullableStruct _ -> true
5690-
| _ -> false)
5681+
let notNullableValueTypeConstraint = tp |> HasConstraint _.IsIsNonNullableStruct
56915682

56925683
let nullnessOfTypar =
56935684
if g.langFeatureNullness && g.checkNullness then
5694-
let hasNotSupportsNull =
5695-
tp.Constraints
5696-
|> List.exists (function
5697-
| TyparConstraint.NotSupportsNull _ -> true
5698-
| _ -> false)
5699-
5700-
let hasSupportsNull () =
5701-
tp.Constraints
5702-
|> List.exists (function
5703-
| TyparConstraint.SupportsNull _ -> true
5704-
| _ -> false)
5685+
let hasNotSupportsNull = tp |> HasConstraint _.IsNotSupportsNull
5686+
let hasSupportsNull () = tp |> HasConstraint _.IsSupportsNull
57055687

57065688
if hasNotSupportsNull || notNullableValueTypeConstraint then
57075689
NullnessInfo.WithoutNull
@@ -5714,17 +5696,11 @@ and GenGenericParam cenv eenv (tp: Typar) =
57145696
None
57155697

57165698
let defaultConstructorConstraint =
5717-
tp.Constraints
5718-
|> List.exists (function
5719-
| TyparConstraint.RequiresDefaultConstructor _ -> true
5720-
| _ -> false)
5699+
tp |> HasConstraint _.IsRequiresDefaultConstructor
57215700

57225701
let emitUnmanagedInIlOutput =
57235702
cenv.g.langVersion.SupportsFeature(LanguageFeature.UnmanagedConstraintCsharpInterop)
5724-
&& tp.Constraints
5725-
|> List.exists (function
5726-
| TyparConstraint.IsUnmanaged _ -> true
5727-
| _ -> false)
5703+
&& tp |> HasConstraint _.IsIsUnmanaged
57285704

57295705
let tpName =
57305706
// use the CompiledName if given

src/Compiler/FSComp.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -324,6 +324,8 @@ csMethodFoundButIsStatic,"The type '%s' has a method '%s' (full name '%s'), but
324324
csMethodFoundButIsNotStatic,"The type '%s' has a method '%s' (full name '%s'), but the method is not static"
325325
472,csStructConstraintInconsistent,"The constraints 'struct' and 'not struct' are inconsistent"
326326
473,csUnmanagedConstraintInconsistent,"The constraints 'unmanaged' and 'not struct' are inconsistent"
327+
474,csComparisonDelegateConstraintInconsistent,"The constraints 'comparison' and 'delegate' are inconsistent"
328+
475,csNullStructConstraintInconsistent,"The constraints 'struct' and 'null' are inconsistent"
327329
csTypeDoesNotHaveNull,"The type '%s' does not have 'null' as a proper value"
328330
csNullableTypeDoesNotHaveNull,"The type '%s' does not have 'null' as a proper value. To create a null value for a Nullable type use 'System.Nullable()'."
329331
csTypeDoesNotSupportComparison1,"The type '%s' does not support the 'comparison' constraint because it has the 'NoComparison' attribute"

src/Compiler/TypedTree/TypedTreeOps.fs

Lines changed: 22 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -9150,41 +9150,34 @@ let IsUnionTypeWithNullAsTrueValue (g: TcGlobals) (tycon: Tycon) =
91509150
let TyconCompilesInstanceMembersAsStatic g tycon = IsUnionTypeWithNullAsTrueValue g tycon
91519151
let TcrefCompilesInstanceMembersAsStatic g (tcref: TyconRef) = TyconCompilesInstanceMembersAsStatic g tcref.Deref
91529152

9153+
let inline HasConstraint ([<InlineIfLambda>] predicate) (tp:Typar) =
9154+
tp.Constraints |> List.exists predicate
9155+
9156+
let inline tryGetTyparTyWithConstraint g ([<InlineIfLambda>] predicate) ty =
9157+
match tryDestTyparTy g ty with
9158+
| ValueSome tp as x when HasConstraint predicate tp -> x
9159+
| _ -> ValueNone
9160+
9161+
let inline IsTyparTyWithConstraint g ([<InlineIfLambda>] predicate) ty =
9162+
match tryDestTyparTy g ty with
9163+
| ValueSome tp -> HasConstraint predicate tp
9164+
| ValueNone -> false
9165+
91539166
// Note, isStructTy does not include type parameters with the ': struct' constraint
91549167
// This predicate is used to detect those type parameters.
9155-
let isNonNullableStructTyparTy g ty =
9156-
match tryDestTyparTy g ty with
9157-
| ValueSome tp ->
9158-
tp.Constraints |> List.exists (function TyparConstraint.IsNonNullableStruct _ -> true | _ -> false)
9159-
| ValueNone ->
9160-
false
9168+
let IsNonNullableStructTyparTy g ty = ty |> IsTyparTyWithConstraint g _.IsIsNonNullableStruct
91619169

91629170
// Note, isRefTy does not include type parameters with the ': not struct' or ': null' constraints
91639171
// This predicate is used to detect those type parameters.
9164-
let isReferenceTyparTy g ty =
9165-
match tryDestTyparTy g ty with
9166-
| ValueSome tp ->
9167-
tp.Constraints |> List.exists (function
9168-
| TyparConstraint.IsReferenceType _ -> true
9169-
| TyparConstraint.SupportsNull _ -> true
9170-
| _ -> false)
9171-
| ValueNone ->
9172-
false
9172+
let IsReferenceTyparTy g ty = ty |> IsTyparTyWithConstraint g (fun tc -> tc.IsIsReferenceType || tc.IsSupportsNull)
91739173

9174-
let GetTyparTyIfSupportsNull g ty =
9175-
if isReferenceTyparTy g ty then
9176-
let tp = destTyparTy g ty
9177-
if tp.Constraints |> List.exists (function TyparConstraint.SupportsNull _ -> true | _ -> false) then
9178-
ValueSome tp
9179-
else ValueNone
9180-
else
9181-
ValueNone
9174+
let GetTyparTyIfSupportsNull g ty = ty |> tryGetTyparTyWithConstraint g _.IsSupportsNull
91829175

91839176
let TypeNullNever g ty =
91849177
let underlyingTy = stripTyEqnsAndMeasureEqns g ty
91859178
isStructTy g underlyingTy ||
91869179
isByrefTy g underlyingTy ||
9187-
isNonNullableStructTyparTy g ty
9180+
IsNonNullableStructTyparTy g ty
91889181

91899182
/// The pre-nullness logic about whether a type admits the use of 'null' as a value.
91909183
let TypeNullIsExtraValue g m ty =
@@ -9244,7 +9237,7 @@ let changeWithNullReqTyToVariable g reqTy =
92449237
let reqTyForArgumentNullnessInference g actualTy reqTy =
92459238
// Only change reqd nullness if actualTy is an inference variable
92469239
match tryDestTyparTy g actualTy with
9247-
| ValueSome t when t.IsCompilerGenerated && not(t.Constraints |> List.exists(function | TyparConstraint.SupportsNull _ -> true | _ -> false))->
9240+
| ValueSome t when t.IsCompilerGenerated && not(t |> HasConstraint _.IsSupportsNull) ->
92489241
changeWithNullReqTyToVariable g reqTy
92499242
| _ -> reqTy
92509243

@@ -9366,8 +9359,9 @@ let rec TypeHasDefaultValueAux isNew g m ty =
93669359
true))
93679360
||
93689361
// Check for type variables with the ":struct" and "(new : unit -> 'T)" constraints
9369-
(isNonNullableStructTyparTy g ty &&
9370-
(destTyparTy g ty).Constraints |> List.exists (function TyparConstraint.RequiresDefaultConstructor _ -> true | _ -> false))
9362+
( match ty |> tryGetTyparTyWithConstraint g _.IsIsNonNullableStruct with
9363+
| ValueSome tp -> tp |> HasConstraint _.IsRequiresDefaultConstructor
9364+
| ValueNone -> false)
93719365

93729366
let TypeHasDefaultValue (g: TcGlobals) m ty = TypeHasDefaultValueAux false g m ty
93739367

@@ -9984,7 +9978,7 @@ let isCompiledOrWitnessPassingConstraint (g: TcGlobals) cx =
99849978
// FSharpTypeFunc, but rather bake a "local type function" for each TyLambda abstraction.
99859979
let IsGenericValWithGenericConstraints g (v: Val) =
99869980
isForallTy g v.Type &&
9987-
v.Type |> destForallTy g |> fst |> List.exists (fun tp -> List.exists (isCompiledOrWitnessPassingConstraint g) tp.Constraints)
9981+
v.Type |> destForallTy g |> fst |> List.exists (fun tp -> HasConstraint (isCompiledOrWitnessPassingConstraint g) tp)
99889982

99899983
// Does a type support a given interface?
99909984
type Entity with

src/Compiler/TypedTree/TypedTreeOps.fsi

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1734,13 +1734,18 @@ val isStructOrEnumTyconTy: TcGlobals -> TType -> bool
17341734
///
17351735
/// Note, isStructTy does not include type parameters with the ': struct' constraint
17361736
/// This predicate is used to detect those type parameters.
1737-
val isNonNullableStructTyparTy: TcGlobals -> TType -> bool
1737+
val IsNonNullableStructTyparTy: TcGlobals -> TType -> bool
1738+
1739+
val inline HasConstraint: [<InlineIfLambda>] predicate: (TyparConstraint -> bool) -> Typar -> bool
1740+
1741+
val inline IsTyparTyWithConstraint:
1742+
TcGlobals -> [<InlineIfLambda>] predicate: (TyparConstraint -> bool) -> TType -> bool
17381743

17391744
/// Determine if a type is a variable type with the ': not struct' constraint.
17401745
///
17411746
/// Note, isRefTy does not include type parameters with the ': not struct' constraint
17421747
/// This predicate is used to detect those type parameters.
1743-
val isReferenceTyparTy: TcGlobals -> TType -> bool
1748+
val IsReferenceTyparTy: TcGlobals -> TType -> bool
17441749

17451750
/// Determine if a type is an unmanaged type
17461751
val isUnmanagedTy: TcGlobals -> TType -> bool

0 commit comments

Comments
 (0)