Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bugfix :: Fix 'Type inference problem too complicated' for SRTP with "T:null and T:struct" dummy constraint #18345

Merged
merged 5 commits into from
Mar 11, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/release-notes/.FSharp.Compiler.Service/9.0.300.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
* Fix NRE when accessing nullable fields of types within their equals/hash/compare methods ([PR #18296](https://github.com/dotnet/fsharp/pull/18296))
* 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))
* 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))
* 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))
* Fix for missing parse diagnostics in TransparentCompiler.ParseAndCheckProject ([PR #18366](https://github.com/dotnet/fsharp/pull/18366))

### Added
Expand Down
8 changes: 1 addition & 7 deletions src/Compiler/Checking/AugmentWithHashCompare.fs
Original file line number Diff line number Diff line change
Expand Up @@ -1647,13 +1647,7 @@ let rec TypeDefinitelyHasEquality g ty =
match appTy with
| ValueSome(tcref, _) when HasFSharpAttribute g g.attrib_NoEqualityAttribute tcref.Attribs -> false
| _ ->
if
isTyparTy g ty
&& (destTyparTy g ty).Constraints
|> List.exists (function
| TyparConstraint.SupportsEquality _ -> true
| _ -> false)
then
if ty |> IsTyparTyWithConstraint g _.IsSupportsEquality then
true
else
match ty with
Expand Down
19 changes: 6 additions & 13 deletions src/Compiler/Checking/CheckDeclarations.fs
Original file line number Diff line number Diff line change
Expand Up @@ -2138,17 +2138,13 @@ module TyconConstraintInference =

// Is the field type a type parameter?
match tryDestTyparTy g ty with
| ValueSome tp ->
// Look for an explicit 'comparison' constraint
if tp.Constraints |> List.exists (function TyparConstraint.SupportsComparison _ -> true | _ -> false) then
true

| ValueSome tp when tp |> HasConstraint _.IsSupportsComparison -> true
| 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.
elif tycon.TyparsNoRange |> List.exists (fun tp2 -> typarRefEq tp tp2) then
if tycon.TyparsNoRange |> List.exists (fun tp2 -> typarRefEq tp tp2) then
assumedTyparsAcc <- assumedTyparsAcc.Add(tp.Stamp)
true

true
else
false
| _ ->
Expand Down Expand Up @@ -2267,14 +2263,11 @@ module TyconConstraintInference =
// and type parameters.
let rec checkIfFieldTypeSupportsEquality (tycon: Tycon) (ty: TType) =
match tryDestTyparTy g ty with
| ValueSome tp when tp |> HasConstraint _.IsSupportsEquality -> true
| ValueSome tp ->
// Look for an explicit 'equality' constraint
if tp.Constraints |> List.exists (function TyparConstraint.SupportsEquality _ -> true | _ -> false) then
true

// Within structural types, type parameters can be optimistically assumed to have equality
// We record the ones for which we have made this assumption.
elif tycon.Typars(tycon.Range) |> List.exists (fun tp2 -> typarRefEq tp tp2) then
if tycon.Typars(tycon.Range) |> List.exists (fun tp2 -> typarRefEq tp tp2) then
assumedTyparsAcc <- assumedTyparsAcc.Add(tp.Stamp)
true
else
Expand Down
38 changes: 20 additions & 18 deletions src/Compiler/Checking/ConstraintSolver.fs
Original file line number Diff line number Diff line change
Expand Up @@ -958,13 +958,6 @@ let rec SolveTyparEqualsTypePart1 (csenv: ConstraintSolverEnv) m2 (trace: Option
// Record the solution before we solve the constraints, since
// We may need to make use of the equation when solving the constraints.
// Record a entry in the undo trace if one is provided

//let ty1AllowsNull = r.Constraints |> List.exists (function | TyparConstraint.SupportsNull _ -> true | _ -> false )
//let tyAllowsNull() = TypeNullIsExtraValueNew csenv.g m2 ty
//if ty1AllowsNull && not (tyAllowsNull()) then
// trace.Exec (fun () -> r.typar_solution <- Some (ty |> replaceNullnessOfTy csenv.g.knownWithNull)) (fun () -> r.typar_solution <- None)
//else
// trace.Exec (fun () -> r.typar_solution <- Some ty) (fun () -> r.typar_solution <- None)
trace.Exec (fun () -> r.typar_solution <- Some ty) (fun () -> r.typar_solution <- None)
}

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

and DepthCheck ndeep m =
if ndeep > 300 then
error(Error(FSComp.SR.csTypeInferenceMaxDepth(), m))
else
and DepthCheck ndeep m =
if ndeep > 300 then
error(Error(FSComp.SR.csTypeInferenceMaxDepth(), m))
else
CompleteD

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

| TyparConstraint.SupportsComparison _, TyparConstraint.IsDelegate _
| TyparConstraint.IsDelegate _, TyparConstraint.SupportsComparison _
| TyparConstraint.IsDelegate _, TyparConstraint.SupportsComparison _ ->
return! ErrorD (Error(FSComp.SR.csComparisonDelegateConstraintInconsistent(), m))

| TyparConstraint.IsNonNullableStruct _, TyparConstraint.IsReferenceType _
| TyparConstraint.IsReferenceType _, TyparConstraint.IsNonNullableStruct _ ->
return! ErrorD (Error(FSComp.SR.csStructConstraintInconsistent(), m))

| TyparConstraint.SupportsNull _, TyparConstraint.NotSupportsNull _
| TyparConstraint.NotSupportsNull _, TyparConstraint.SupportsNull _ ->
return! ErrorD (Error(FSComp.SR.csNullNotNullConstraintInconsistent(), m))

| TyparConstraint.SupportsNull _, TyparConstraint.IsNonNullableStruct _
| TyparConstraint.IsNonNullableStruct _, TyparConstraint.SupportsNull _ ->
()
//return! WarnD (Error(FSComp.SR.csNullStructConstraintInconsistent(), m))

| TyparConstraint.IsUnmanaged _, TyparConstraint.IsReferenceType _
| TyparConstraint.IsReferenceType _, TyparConstraint.IsUnmanaged _ ->
Expand Down Expand Up @@ -2640,7 +2641,7 @@ and SolveTypeUseSupportsNull (csenv: ConstraintSolverEnv) ndeep m2 trace ty =
| ValueSome NullnessInfo.WithoutNull ->
return! AddConstraint csenv ndeep m2 trace tp (TyparConstraint.SupportsNull m)
| _ ->
if tp.Constraints |> List.exists (function | TyparConstraint.IsReferenceType _ -> true | _ -> false) |> not then
if not (tp |> HasConstraint _.IsIsReferenceType) then
do! AddConstraint csenv ndeep m2 trace tp (TyparConstraint.IsReferenceType m)
return! SolveNullnessSupportsNull csenv ndeep m2 trace ty nullness
| _ ->
Expand Down Expand Up @@ -2737,7 +2738,7 @@ and SolveTypeCanCarryNullness (csenv: ConstraintSolverEnv) ty nullness =
let strippedTy = stripTyEqnsA g true ty
match tryAddNullnessToTy nullness strippedTy with
| Some _ ->
if isTyparTy g strippedTy && not (isReferenceTyparTy g strippedTy) then
if isTyparTy g strippedTy && not (IsReferenceTyparTy g strippedTy) then
return! AddConstraint csenv 0 m NoTrace (destTyparTy g strippedTy) (TyparConstraint.IsReferenceType m)
| None ->
let tyString = NicePrint.minimalStringOfType csenv.DisplayEnv strippedTy
Expand Down Expand Up @@ -2978,10 +2979,11 @@ and SolveTypeRequiresDefaultValue (csenv: ConstraintSolverEnv) ndeep m2 trace or
let g = csenv.g
let m = csenv.m
let ty = stripTyEqnsAndMeasureEqns g origTy

if isTyparTy g ty then
if isNonNullableStructTyparTy g ty then
if IsNonNullableStructTyparTy g ty then
SolveTypeRequiresDefaultConstructor csenv ndeep m2 trace ty
elif isReferenceTyparTy g ty then
elif IsReferenceTyparTy g ty then
SolveTypeUseSupportsNull csenv ndeep m2 trace ty
else
ErrorD (ConstraintSolverError(FSComp.SR.csGenericConstructRequiresStructOrReferenceConstraint(), m, m2))
Expand Down
2 changes: 1 addition & 1 deletion src/Compiler/Checking/Expressions/CheckExpressions.fs
Original file line number Diff line number Diff line change
Expand Up @@ -2194,7 +2194,7 @@ module GeneralizationHelpers =

let relevantUniqueSubtypeConstraint (tp: Typar) =
// Find a single subtype constraint
match tp.Constraints |> List.partition (function TyparConstraint.CoercesTo _ -> true | _ -> false) with
match tp.Constraints |> List.partition _.IsCoercesTo with
| [TyparConstraint.CoercesTo(tgtTy, _)], others ->
// Throw away null constraints if they are implied
if others |> List.exists (function TyparConstraint.SupportsNull _ -> not (TypeNullIsExtraValue g m tgtTy) | _ -> true)
Expand Down
36 changes: 6 additions & 30 deletions src/Compiler/CodeGen/IlxGen.fs
Original file line number Diff line number Diff line change
Expand Up @@ -5676,32 +5676,14 @@ and GenGenericParam cenv eenv (tp: Typar) =
|> List.map (GenTypeAux cenv tp.Range eenv.tyenv VoidNotOK PtrTypesNotOK)

let refTypeConstraint =
tp.Constraints
|> List.exists (function
| TyparConstraint.IsReferenceType _
// 'null' automatically implies 'not struct'
| TyparConstraint.SupportsNull _ -> true
| _ -> false)
tp |> HasConstraint(fun tc -> tc.IsIsReferenceType || tc.IsSupportsNull) // `null` implies not struct

let notNullableValueTypeConstraint =
tp.Constraints
|> List.exists (function
| TyparConstraint.IsNonNullableStruct _ -> true
| _ -> false)
let notNullableValueTypeConstraint = tp |> HasConstraint _.IsIsNonNullableStruct

let nullnessOfTypar =
if g.langFeatureNullness && g.checkNullness then
let hasNotSupportsNull =
tp.Constraints
|> List.exists (function
| TyparConstraint.NotSupportsNull _ -> true
| _ -> false)

let hasSupportsNull () =
tp.Constraints
|> List.exists (function
| TyparConstraint.SupportsNull _ -> true
| _ -> false)
let hasNotSupportsNull = tp |> HasConstraint _.IsNotSupportsNull
let hasSupportsNull () = tp |> HasConstraint _.IsSupportsNull

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

let defaultConstructorConstraint =
tp.Constraints
|> List.exists (function
| TyparConstraint.RequiresDefaultConstructor _ -> true
| _ -> false)
tp |> HasConstraint _.IsRequiresDefaultConstructor

let emitUnmanagedInIlOutput =
cenv.g.langVersion.SupportsFeature(LanguageFeature.UnmanagedConstraintCsharpInterop)
&& tp.Constraints
|> List.exists (function
| TyparConstraint.IsUnmanaged _ -> true
| _ -> false)
&& tp |> HasConstraint _.IsIsUnmanaged

let tpName =
// use the CompiledName if given
Expand Down
2 changes: 2 additions & 0 deletions src/Compiler/FSComp.txt
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,8 @@ csMethodFoundButIsStatic,"The type '%s' has a method '%s' (full name '%s'), but
csMethodFoundButIsNotStatic,"The type '%s' has a method '%s' (full name '%s'), but the method is not static"
472,csStructConstraintInconsistent,"The constraints 'struct' and 'not struct' are inconsistent"
473,csUnmanagedConstraintInconsistent,"The constraints 'unmanaged' and 'not struct' are inconsistent"
474,csComparisonDelegateConstraintInconsistent,"The constraints 'comparison' and 'delegate' are inconsistent"
475,csNullStructConstraintInconsistent,"The constraints 'struct' and 'null' are inconsistent"
csTypeDoesNotHaveNull,"The type '%s' does not have 'null' as a proper value"
csNullableTypeDoesNotHaveNull,"The type '%s' does not have 'null' as a proper value. To create a null value for a Nullable type use 'System.Nullable()'."
csTypeDoesNotSupportComparison1,"The type '%s' does not support the 'comparison' constraint because it has the 'NoComparison' attribute"
Expand Down
50 changes: 22 additions & 28 deletions src/Compiler/TypedTree/TypedTreeOps.fs
Original file line number Diff line number Diff line change
Expand Up @@ -9150,41 +9150,34 @@ let IsUnionTypeWithNullAsTrueValue (g: TcGlobals) (tycon: Tycon) =
let TyconCompilesInstanceMembersAsStatic g tycon = IsUnionTypeWithNullAsTrueValue g tycon
let TcrefCompilesInstanceMembersAsStatic g (tcref: TyconRef) = TyconCompilesInstanceMembersAsStatic g tcref.Deref

let inline HasConstraint ([<InlineIfLambda>] predicate) (tp:Typar) =
tp.Constraints |> List.exists predicate

let inline tryGetTyparTyWithConstraint g ([<InlineIfLambda>] predicate) ty =
match tryDestTyparTy g ty with
| ValueSome tp as x when HasConstraint predicate tp -> x
| _ -> ValueNone

let inline IsTyparTyWithConstraint g ([<InlineIfLambda>] predicate) ty =
match tryDestTyparTy g ty with
| ValueSome tp -> HasConstraint predicate tp
| ValueNone -> false

// Note, isStructTy does not include type parameters with the ': struct' constraint
// This predicate is used to detect those type parameters.
let isNonNullableStructTyparTy g ty =
match tryDestTyparTy g ty with
| ValueSome tp ->
tp.Constraints |> List.exists (function TyparConstraint.IsNonNullableStruct _ -> true | _ -> false)
| ValueNone ->
false
let IsNonNullableStructTyparTy g ty = ty |> IsTyparTyWithConstraint g _.IsIsNonNullableStruct

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

let GetTyparTyIfSupportsNull g ty =
if isReferenceTyparTy g ty then
let tp = destTyparTy g ty
if tp.Constraints |> List.exists (function TyparConstraint.SupportsNull _ -> true | _ -> false) then
ValueSome tp
else ValueNone
else
ValueNone
let GetTyparTyIfSupportsNull g ty = ty |> tryGetTyparTyWithConstraint g _.IsSupportsNull

let TypeNullNever g ty =
let underlyingTy = stripTyEqnsAndMeasureEqns g ty
isStructTy g underlyingTy ||
isByrefTy g underlyingTy ||
isNonNullableStructTyparTy g ty
IsNonNullableStructTyparTy g ty

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

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

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

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

// Does a type support a given interface?
type Entity with
Expand Down
9 changes: 7 additions & 2 deletions src/Compiler/TypedTree/TypedTreeOps.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -1734,13 +1734,18 @@ val isStructOrEnumTyconTy: TcGlobals -> TType -> bool
///
/// Note, isStructTy does not include type parameters with the ': struct' constraint
/// This predicate is used to detect those type parameters.
val isNonNullableStructTyparTy: TcGlobals -> TType -> bool
val IsNonNullableStructTyparTy: TcGlobals -> TType -> bool

val inline HasConstraint: [<InlineIfLambda>] predicate: (TyparConstraint -> bool) -> Typar -> bool

val inline IsTyparTyWithConstraint:
TcGlobals -> [<InlineIfLambda>] predicate: (TyparConstraint -> bool) -> TType -> bool

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

/// Determine if a type is an unmanaged type
val isUnmanagedTy: TcGlobals -> TType -> bool
Expand Down
Loading