Skip to content

Commit ec04e47

Browse files
authored
Additional comments and testing related to SRTP resolution (#3967)
* Fix Oddities in statically resolved method constraints and method overloading * no return type needed before proceeding to overload resolution * fix tests * unwind changes * revert to be cleanup only * revert to be cleanup only * revert to be cleanup only * revert to be cleanup only * adjsut test * test doesn't pass peverify * test doesn't pass peverify * apply code review
1 parent 7376e22 commit ec04e47

File tree

6 files changed

+133
-33
lines changed

6 files changed

+133
-33
lines changed

src/fsharp/ConstraintSolver.fs

Lines changed: 55 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1004,10 +1004,22 @@ and SolveDimensionlessNumericType (csenv:ConstraintSolverEnv) ndeep m2 trace ty
10041004
| None ->
10051005
CompleteD
10061006

1007-
/// We do a bunch of fakery to pretend that primitive types have certain members.
1008-
/// We pretend int and other types support a number of operators. In the actual IL for mscorlib they
1009-
/// don't, however the type-directed static optimization rules in the library code that makes use of this
1010-
/// will deal with the problem.
1007+
/// Attempt to solve a statically resolved member constraint.
1008+
///
1009+
/// 1. We do a bunch of fakery to pretend that primitive types have certain members.
1010+
/// We pretend int and other types support a number of operators. In the actual IL for mscorlib they
1011+
/// don't. The type-directed static optimization rules in the library code that makes use of this
1012+
/// will deal with the problem.
1013+
///
1014+
/// 2. Some additional solutions are forced prior to generalization (permitWeakResolution=true). These are, roughly speaking, rules
1015+
/// for binary-operand constraints arising from constructs such as "1.0 + x" where "x" is an unknown type. THe constraint here
1016+
/// involves two type parameters - one for the left, and one for the right. The left is already known to be Double.
1017+
/// In this situation (and in the absence of other evidence prior to generalization), constraint solving forces an assumption that
1018+
/// the right is also Double - this is "weak" because there is only weak evidence for it.
1019+
///
1020+
/// permitWeakResolution also applies to resolutions of multi-type-variable constraints via method overloads. Method overloading gets applied even if
1021+
/// only one of the two type variables is known
1022+
///
10111023
and SolveMemberConstraint (csenv:ConstraintSolverEnv) ignoreUnresolvedOverload permitWeakResolution ndeep m2 trace (TTrait(tys, nm, memFlags, argtys, rty, sln)): OperationResult<bool> = trackErrors {
10121024
// Do not re-solve if already solved
10131025
if sln.Value.IsSome then return true else
@@ -1065,7 +1077,7 @@ and SolveMemberConstraint (csenv:ConstraintSolverEnv) ignoreUnresolvedOverload p
10651077
// The rule is triggered by these sorts of inputs when permitWeakResolution=true
10661078
// float * 'a
10671079
// 'a * float
1068-
// decimal<'u> * 'a <---
1080+
// decimal<'u> * 'a
10691081
(let checkRuleAppliesInPreferenceToMethods argty1 argty2 =
10701082
// Check that at least one of the argument types is numeric
10711083
(IsNumericOrIntegralEnumType g argty1) &&
@@ -1314,7 +1326,7 @@ and SolveMemberConstraint (csenv:ConstraintSolverEnv) ignoreUnresolvedOverload p
13141326

13151327
// Now check if there are no feasible solutions at all
13161328
match minfos, recdPropSearch, anonRecdPropSearch with
1317-
| [], None, None when not (tys |> List.exists (isAnyParTy g)) ->
1329+
| [], None, None when MemberConstraintIsReadyForStrongResolution csenv traitInfo ->
13181330
if tys |> List.exists (isFunTy g) then
13191331
return! ErrorD (ConstraintSolverError(FSComp.SR.csExpectTypeWithOperatorButGivenFunction(DecompileOpName nm), m, m2))
13201332
elif tys |> List.exists (isAnyTupleTy g) then
@@ -1387,14 +1399,21 @@ and SolveMemberConstraint (csenv:ConstraintSolverEnv) ignoreUnresolvedOverload p
13871399
| _ ->
13881400
let support = GetSupportOfMemberConstraint csenv traitInfo
13891401
let frees = GetFreeTyparsOfMemberConstraint csenv traitInfo
1390-
// If there's nothing left to learn then raise the errors
1391-
if (permitWeakResolution && isNil support) || isNil frees then do! errors
1402+
1403+
// If there's nothing left to learn then raise the errors.
1404+
// Note: we should likely call MemberConstraintIsReadyForResolution here when permitWeakResolution=false but for stability
1405+
// reasons we use the more restrictive isNil frees.
1406+
if (permitWeakResolution && MemberConstraintIsReadyForWeakResolution csenv traitInfo) || isNil frees then
1407+
do! errors
13921408
// Otherwise re-record the trait waiting for canonicalization
1393-
else do! AddMemberConstraint csenv ndeep m2 trace traitInfo support frees
1394-
return!
1395-
match errors with
1396-
| ErrorResult (_, UnresolvedOverloading _) when not ignoreUnresolvedOverload && (not (nm = "op_Explicit" || nm = "op_Implicit")) -> ErrorD LocallyAbortOperationThatFailsToResolveOverload
1397-
| _ -> ResultD TTraitUnsolved
1409+
else
1410+
do! AddMemberConstraint csenv ndeep m2 trace traitInfo support frees
1411+
1412+
match errors with
1413+
| ErrorResult (_, UnresolvedOverloading _) when not ignoreUnresolvedOverload && (not (nm = "op_Explicit" || nm = "op_Implicit")) ->
1414+
return! ErrorD LocallyAbortOperationThatFailsToResolveOverload
1415+
| _ ->
1416+
return TTraitUnsolved
13981417
}
13991418
return! RecordMemberConstraintSolution csenv.SolverState m trace traitInfo res
14001419
}
@@ -1475,21 +1494,17 @@ and TransactMemberConstraintSolution traitInfo (trace:OptionalTrace) sln =
14751494
/// That is, don't perform resolution if more nominal information may influence the set of available overloads
14761495
and GetRelevantMethodsForTrait (csenv:ConstraintSolverEnv) permitWeakResolution nm (TTrait(tys, _, memFlags, argtys, rty, soln) as traitInfo): MethInfo list =
14771496
let results =
1478-
if permitWeakResolution || isNil (GetSupportOfMemberConstraint csenv traitInfo) then
1497+
if permitWeakResolution || MemberConstraintSupportIsReadyForDeterminingOverloads csenv traitInfo then
14791498
let m = csenv.m
14801499
let minfos =
14811500
match memFlags.MemberKind with
14821501
| MemberKind.Constructor ->
14831502
tys |> List.map (GetIntrinsicConstructorInfosOfType csenv.SolverState.InfoReader m)
14841503
| _ ->
14851504
tys |> List.map (GetIntrinsicMethInfosOfType csenv.SolverState.InfoReader (Some nm, AccessibleFromSomeFSharpCode, AllowMultiIntfInstantiations.Yes) IgnoreOverrides m)
1486-
/// Merge the sets so we don't get the same minfo from each side
1487-
/// We merge based on whether minfos use identical metadata or not.
14881505

1489-
/// REVIEW: Consider the pathological cases where this may cause a loss of distinction
1490-
/// between potential overloads because a generic instantiation derived from the left hand type differs
1491-
/// to a generic instantiation for an operator based on the right hand type.
1492-
1506+
// Merge the sets so we don't get the same minfo from each side
1507+
// We merge based on whether minfos use identical metadata or not.
14931508
let minfos = List.reduce (ListSet.unionFavourLeft MethInfo.MethInfosUseIdenticalDefinitions) minfos
14941509

14951510
/// Check that the available members aren't hiding a member from the parent (depth 1 only)
@@ -1503,7 +1518,7 @@ and GetRelevantMethodsForTrait (csenv:ConstraintSolverEnv) permitWeakResolution
15031518
[]
15041519
// The trait name "op_Explicit" also covers "op_Implicit", so look for that one too.
15051520
if nm = "op_Explicit" then
1506-
results @ GetRelevantMethodsForTrait (csenv:ConstraintSolverEnv) permitWeakResolution "op_Implicit" (TTrait(tys, "op_Implicit", memFlags, argtys, rty, soln))
1521+
results @ GetRelevantMethodsForTrait csenv permitWeakResolution "op_Implicit" (TTrait(tys, "op_Implicit", memFlags, argtys, rty, soln))
15071522
else
15081523
results
15091524

@@ -1512,10 +1527,28 @@ and GetRelevantMethodsForTrait (csenv:ConstraintSolverEnv) permitWeakResolution
15121527
and GetSupportOfMemberConstraint (csenv:ConstraintSolverEnv) (TTrait(tys, _, _, _, _, _)) =
15131528
tys |> List.choose (tryAnyParTyOption csenv.g)
15141529

1515-
/// All the typars relevant to the member constraint *)
1530+
/// Check if the support is fully solved.
1531+
and SupportOfMemberConstraintIsFullySolved (csenv:ConstraintSolverEnv) (TTrait(tys, _, _, _, _, _)) =
1532+
tys |> List.forall (isAnyParTy csenv.g >> not)
1533+
1534+
// This may be relevant to future bug fixes, see https://github.com/Microsoft/visualfsharp/issues/3814
1535+
// /// Check if some part of the support is solved.
1536+
// and SupportOfMemberConstraintIsPartiallySolved (csenv:ConstraintSolverEnv) (TTrait(tys, _, _, _, _, _)) =
1537+
// tys |> List.exists (isAnyParTy csenv.g >> not)
1538+
1539+
/// Get all the unsolved typars (statically resolved or not) relevant to the member constraint
15161540
and GetFreeTyparsOfMemberConstraint (csenv:ConstraintSolverEnv) (TTrait(tys, _, _, argtys, rty, _)) =
15171541
freeInTypesLeftToRightSkippingConstraints csenv.g (tys@argtys@ Option.toList rty)
15181542

1543+
and MemberConstraintIsReadyForWeakResolution csenv traitInfo =
1544+
SupportOfMemberConstraintIsFullySolved csenv traitInfo
1545+
1546+
and MemberConstraintIsReadyForStrongResolution csenv traitInfo =
1547+
SupportOfMemberConstraintIsFullySolved csenv traitInfo
1548+
1549+
and MemberConstraintSupportIsReadyForDeterminingOverloads csenv traitInfo =
1550+
SupportOfMemberConstraintIsFullySolved csenv traitInfo
1551+
15191552
/// Re-solve the global constraints involving any of the given type variables.
15201553
/// Trait constraints can't always be solved using the pessimistic rules. We only canonicalize
15211554
/// them forcefully (permitWeakResolution=true) prior to generalization.

tests/fsharp/core/members/basics-hw-mutrec/test.fs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5549,7 +5549,7 @@ module Devdiv2_Bug_5385 =
55495549
g "1" |> ignore; // note, use of non-generic 'g' within a generic, generalized memoized function
55505550
2
55515551

5552-
and g : string -> int = memoize f // note, computed function value using generic “f” at an instance
5552+
and g : string -> int = memoize f // note, computed function value using generic �f� at an instance
55535553
g "1"
55545554

55555555
let res = test3e()

tests/fsharp/core/members/ops-mutrec/test.fs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -217,16 +217,15 @@ module BasicOverloadTests =
217217
// This gets type DateTime -> DateTime -> TimeSpan, through non-conservative resolution.
218218
let f6 x1 (x2:System.DateTime) = x1 - x2
219219

220-
// This gets type TimeSpan -> TimeSpan -> TimeSpan, through non-conservative resolution.
220+
// This gets type TimeSpan -> TimeSpan -> TimeSpan, through default type propagation
221221
let f7 x1 (x2:System.TimeSpan) = x1 - x2
222222

223-
// This gets type TimeSpan -> TimeSpan -> TimeSpan, through non-conservative resolution.
223+
// This gets type TimeSpan -> TimeSpan -> TimeSpan, through default type propagation
224224
let f8 x1 (x2:System.TimeSpan) = x2 - x1
225225

226-
// This gets type TimeSpan -> TimeSpan -> TimeSpan, through non-conservative resolution.
226+
// This gets type TimeSpan -> TimeSpan -> TimeSpan, through default type propagation
227227
let f9 (x1:System.TimeSpan) x2 = x1 - x2
228228

229-
230229
// This gets type TimeSpan -> TimeSpan -> TimeSpan
231230
let f10 x1 (x2:System.TimeSpan) = x1 + x2
232231

tests/fsharp/core/members/ops/test.fsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -219,16 +219,16 @@ module BasicOverloadTests =
219219
// This gets type int -> int
220220
let f5 x = 1 - x
221221

222-
// This gets type DateTime -> DateTime -> TimeSpan, through non-conservative resolution.
223-
let f6 x1 (x2:System.DateTime) = x1 - x2
222+
// // This gets type DateTime -> DateTime -> TimeSpan, through non-conservative resolution.
223+
// let f6 x1 (x2:System.DateTime) = x1 - x2
224224

225-
// This gets type TimeSpan -> TimeSpan -> TimeSpan, through non-conservative resolution.
225+
// This gets type TimeSpan -> TimeSpan -> TimeSpan, through default type propagation
226226
let f7 x1 (x2:System.TimeSpan) = x1 - x2
227227

228-
// This gets type TimeSpan -> TimeSpan -> TimeSpan, through non-conservative resolution.
228+
// This gets type TimeSpan -> TimeSpan -> TimeSpan, through default type propagation
229229
let f8 x1 (x2:System.TimeSpan) = x2 - x1
230230

231-
// This gets type TimeSpan -> TimeSpan -> TimeSpan, through non-conservative resolution.
231+
// This gets type TimeSpan -> TimeSpan -> TimeSpan, through default type propagation
232232
let f9 (x1:System.TimeSpan) x2 = x1 - x2
233233

234234

tests/fsharp/core/subtype/test.fsx

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,8 +232,31 @@ module SomeRandomOperatorConstraints = begin
232232

233233
let f2 x : float = x * x
234234
let f3 x (y:float) = x * y
235+
235236
//let neg4 x (y:System.DateTime) = x + y
237+
238+
// This example resolves the type of "y" to "TimeSpam". It checks that a single "+" overload between
239+
// two different types DateTime and TimeSpan get resolved via
240+
// via weak SRTP resolution using a DateTime constraint alone.
236241
let f5 (x:DateTime) y = x + y
242+
243+
// This example checks a use of TimeSpan/DateTime overloads
244+
let f5b (x:DateTime) (y:DateTime) = (x - y)
245+
246+
247+
// This example checks a use of TimeSpan/DateTime overloads
248+
let f5b2 (x:DateTime) (y:TimeSpan) = (x - y)
249+
250+
// This example coincidentally checks that the return type is not taken into account before the list of method overloads
251+
// is prepared in SRTP resolution. That is the type of (a - b) is immediately known (and we can use it for
252+
// dot-notation name resolution of .TotalSeconds) _immediately_ that the types of a and b are
253+
// known and _prior_ to generalization.
254+
let f5c (x: DateTime) (y:DateTime) =
255+
(x - y).TotalSeconds |> int
256+
257+
let f5c2 (x: DateTime) (y:TimeSpan) =
258+
(x - y).Second |> int
259+
237260
let f6 (x:int64) y = x + y
238261
let f7 x y : int64 = x + y
239262
let f8 x = Seq.reduce (+) x
@@ -1744,6 +1767,51 @@ module GenericPropertyConstraintSolvedByRecord =
17441767

17451768
let v = print_foo_memb { foo=1 }
17461769

1770+
1771+
/// In this case, the presence of the Method(obj) overload meant overload resolution was being applied and resolving to that
1772+
/// overload, even before the full signature of the trait constraint was known.
1773+
module MethodOverloadingForTraitConstraintsIsNotDeterminedUntilSignatureIsKnnown =
1774+
type X =
1775+
static member Method (a: obj) = 1
1776+
static member Method (a: int) = 2
1777+
static member Method (a: int64) = 3
1778+
1779+
1780+
let inline Test< ^t, ^a when ^t: (static member Method: ^a -> int)> (value: ^a) =
1781+
( ^t: (static member Method: ^a -> int)(value))
1782+
1783+
let inline Test2< ^t> a = Test<X, ^t> a
1784+
1785+
// NOTE, this is seen to be a bug, see https://github.com/Microsoft/visualfsharp/issues/3814
1786+
// The result should be 2.
1787+
// This test has been added to pin down current behaviour pending a future bug fix.
1788+
check "slvde0vver90u1" (Test2<int> 0) 1
1789+
check "slvde0vver90u2" (Test2<int64> 0L) 1
1790+
1791+
/// In this case, the presence of the "Equals" method on System.Object was causing method overloading to be resolved too
1792+
/// early, when ^t was not yet known. The underlying problem was that we were proceeding with weak resolution
1793+
/// even for a single-support-type trait constraint.
1794+
module MethodOverloadingForTraitConstraintsWhereSomeMethodsComeFromObjectTypeIsNotDeterminedTooEarly =
1795+
type Test() =
1796+
member __.Equals (_: Test) = true
1797+
1798+
//let inline Equals(a: obj) (b: ^t) =
1799+
// match a with
1800+
// | :? ^t as x -> (^t: (member Equals: ^t -> bool) (b, x))
1801+
// | _-> false
1802+
1803+
let a = Test()
1804+
let b = Test()
1805+
1806+
// NOTE, this is seen to be a bug, see https://github.com/Microsoft/visualfsharp/issues/3814
1807+
//
1808+
// The result should be true.
1809+
//
1810+
// This test should be added to pin down current behaviour pending a future bug fix.
1811+
//
1812+
// However the code generated fails peverify.exe so even the pin-down test has been removed for now.
1813+
//check "cewjewcwec09ew" (Equals a b) false
1814+
17471815
module SRTPFix =
17481816

17491817
open System

tests/fsharp/typecheck/sigs/neg99.bsl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,4 @@ neg99.fs(19,16,19,64): typecheck error FS0077: Member constraints with the name
33

44
neg99.fs(22,18,22,64): typecheck error FS0077: Member constraints with the name 'op_Explicit' are given special status by the F# compiler as certain .NET types are implicitly augmented with this member. This may result in runtime failures if you attempt to invoke the member constraint from your own code.
55

6-
neg99.fs(25,39,25,43): typecheck error FS0043: The type 'CrashFSC.OhOh.MyByte' does not support a conversion to the type 'CrashFSC.OhOh.MyByte'
6+
neg99.fs(25,39,25,43): typecheck error FS0043: The type 'CrashFSC.OhOh.MyByte' does not support a conversion to the type 'CrashFSC.OhOh.MyByte'

0 commit comments

Comments
 (0)