Skip to content

Commit 3ff020f

Browse files
authored
Merge pull request #14333 from dotnet/backport/pr-14319-to-release/dev17.4
[release/dev17.4] Prefer nullable over other conversions
2 parents 8c28cf4 + 1617183 commit 3ff020f

File tree

5 files changed

+88
-17
lines changed

5 files changed

+88
-17
lines changed

src/Compiler/Checking/CheckExpressions.fs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -453,7 +453,7 @@ let UnifyOverallType (cenv: cenv) (env: TcEnv) m overallTy actualTy =
453453
| None -> ()
454454

455455
match usesTDC with
456-
| TypeDirectedConversionUsed.Yes(warn, _) -> warning(warn env.DisplayEnv)
456+
| TypeDirectedConversionUsed.Yes(warn, _, _) -> warning(warn env.DisplayEnv)
457457
| TypeDirectedConversionUsed.No -> ()
458458

459459
if AddCxTypeMustSubsumeTypeUndoIfFailed env.DisplayEnv cenv.css m reqdTy2 actualTy then

src/Compiler/Checking/ConstraintSolver.fs

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2738,7 +2738,7 @@ and ArgsMustSubsumeOrConvert
27382738
msg csenv.DisplayEnv
27392739
| None -> ()
27402740
match usesTDC with
2741-
| TypeDirectedConversionUsed.Yes(warn, _) -> do! WarnD(warn csenv.DisplayEnv)
2741+
| TypeDirectedConversionUsed.Yes(warn, _, _) -> do! WarnD(warn csenv.DisplayEnv)
27422742
| TypeDirectedConversionUsed.No -> ()
27432743
do! SolveTypeSubsumesTypeWithReport csenv ndeep m trace cxsln (Some calledArg.CalledArgumentType) calledArgTy callerArg.CallerArgumentType
27442744
if calledArg.IsParamArray && isArray1DTy g calledArgTy && not (isArray1DTy g callerArg.CallerArgumentType) then
@@ -2769,7 +2769,7 @@ and ArgsMustSubsumeOrConvertWithContextualReport
27692769
msg csenv.DisplayEnv
27702770
| None -> ()
27712771
match usesTDC with
2772-
| TypeDirectedConversionUsed.Yes(warn, _) -> do! WarnD(warn csenv.DisplayEnv)
2772+
| TypeDirectedConversionUsed.Yes(warn, _, _) -> do! WarnD(warn csenv.DisplayEnv)
27732773
| TypeDirectedConversionUsed.No -> ()
27742774
do! SolveTypeSubsumesTypeWithWrappedContextualReport csenv ndeep m trace cxsln (Some calledArg.CalledArgumentType) calledArgTy callerArgTy (fun e -> ArgDoesNotMatchError(e :?> _, calledMeth, calledArg, callerArg))
27752775
return usesTDC
@@ -2796,7 +2796,7 @@ and ReturnTypesMustSubsumeOrConvert (csenv: ConstraintSolverEnv) ad ndeep trace
27962796
msg csenv.DisplayEnv
27972797
| None -> ()
27982798
match usesTDC with
2799-
| TypeDirectedConversionUsed.Yes(warn, _) -> do! WarnD(warn csenv.DisplayEnv)
2799+
| TypeDirectedConversionUsed.Yes(warn, _, _) -> do! WarnD(warn csenv.DisplayEnv)
28002800
| TypeDirectedConversionUsed.No -> ()
28012801
do! SolveTypeSubsumesTypeWithReport csenv ndeep m trace cxsln None reqdTy actualTy
28022802
return usesTDC
@@ -2813,7 +2813,7 @@ and ArgsEquivOrConvert (csenv: ConstraintSolverEnv) ad ndeep trace cxsln isConst
28132813
msg csenv.DisplayEnv
28142814
| None -> ()
28152815
match usesTDC with
2816-
| TypeDirectedConversionUsed.Yes(warn, _) -> do! WarnD(warn csenv.DisplayEnv)
2816+
| TypeDirectedConversionUsed.Yes(warn, _, _) -> do! WarnD(warn csenv.DisplayEnv)
28172817
| TypeDirectedConversionUsed.No -> ()
28182818
if not (typeEquiv csenv.g calledArgTy callerArgTy) then
28192819
return! ErrorD(Error(FSComp.SR.csArgumentTypesDoNotMatch(), m))
@@ -3225,7 +3225,11 @@ and GetMostApplicableOverload csenv ndeep candidates applicableMeths calledMethG
32253225
if c <> 0 then c else
32263226

32273227
// Prefer methods that need less type-directed conversion
3228-
let c = compare (match usesTDC1 with TypeDirectedConversionUsed.Yes(_, false) -> 1 | _ -> 0) (match usesTDC2 with TypeDirectedConversionUsed.Yes(_, false) -> 1 | _ -> 0)
3228+
let c = compare (match usesTDC1 with TypeDirectedConversionUsed.Yes(_, false, _) -> 1 | _ -> 0) (match usesTDC2 with TypeDirectedConversionUsed.Yes(_, false, _) -> 1 | _ -> 0)
3229+
if c <> 0 then c else
3230+
3231+
// Prefer methods that only have nullable type-directed conversions
3232+
let c = compare (match usesTDC1 with TypeDirectedConversionUsed.Yes(_, _, true) -> 1 | _ -> 0) (match usesTDC2 with TypeDirectedConversionUsed.Yes(_, _, true) -> 1 | _ -> 0)
32293233
if c <> 0 then c else
32303234

32313235
// Prefer methods that don't give "this code is less generic" warnings

src/Compiler/Checking/MethodCalls.fs

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -236,12 +236,16 @@ type TypeDirectedConversion =
236236

237237
[<RequireQualifiedAccess>]
238238
type TypeDirectedConversionUsed =
239-
| Yes of (DisplayEnv -> exn) * isTwoStepConversion: bool
239+
| Yes of (DisplayEnv -> exn) * isTwoStepConversion: bool * isNullable: bool
240240
| No
241241
static member Combine a b =
242242
match a, b with
243-
| Yes(_,true), _ -> a
244-
| _, Yes(_,true) -> b
243+
// We want to know which candidates have one or more nullable conversions exclusively
244+
// If one of the values is false we flow false for both.
245+
| Yes(_, true, false), _ -> a
246+
| _, Yes(_, true, false) -> b
247+
| Yes(_, true, _), _ -> a
248+
| _, Yes(_, true, _) -> b
245249
| Yes _, _ -> a
246250
| _, Yes _ -> b
247251
| No, No -> a
@@ -282,33 +286,33 @@ let rec AdjustRequiredTypeForTypeDirectedConversions (infoReader: InfoReader) ad
282286

283287
// Adhoc int32 --> int64
284288
elif g.langVersion.SupportsFeature LanguageFeature.AdditionalTypeDirectedConversions && typeEquiv g g.int64_ty reqdTy && typeEquiv g g.int32_ty actualTy then
285-
g.int32_ty, TypeDirectedConversionUsed.Yes(warn TypeDirectedConversion.BuiltIn, false), None
289+
g.int32_ty, TypeDirectedConversionUsed.Yes(warn TypeDirectedConversion.BuiltIn, false, false), None
286290

287291
// Adhoc int32 --> nativeint
288292
elif g.langVersion.SupportsFeature LanguageFeature.AdditionalTypeDirectedConversions && typeEquiv g g.nativeint_ty reqdTy && typeEquiv g g.int32_ty actualTy then
289-
g.int32_ty, TypeDirectedConversionUsed.Yes(warn TypeDirectedConversion.BuiltIn, false), None
293+
g.int32_ty, TypeDirectedConversionUsed.Yes(warn TypeDirectedConversion.BuiltIn, false, false), None
290294

291295
// Adhoc int32 --> float64
292296
elif g.langVersion.SupportsFeature LanguageFeature.AdditionalTypeDirectedConversions && typeEquiv g g.float_ty reqdTy && typeEquiv g g.int32_ty actualTy then
293-
g.int32_ty, TypeDirectedConversionUsed.Yes(warn TypeDirectedConversion.BuiltIn, false), None
297+
g.int32_ty, TypeDirectedConversionUsed.Yes(warn TypeDirectedConversion.BuiltIn, false, false), None
294298

295299
elif g.langVersion.SupportsFeature LanguageFeature.NullableOptionalInterop && isMethodArg && isNullableTy g reqdTy && not (isNullableTy g actualTy) then
296300
let underlyingTy = destNullableTy g reqdTy
297301
// shortcut
298302
if typeEquiv g underlyingTy actualTy then
299-
actualTy, TypeDirectedConversionUsed.Yes(warn TypeDirectedConversion.BuiltIn, false), None
303+
actualTy, TypeDirectedConversionUsed.Yes(warn TypeDirectedConversion.BuiltIn, false, true), None
300304
else
301305
let adjustedTy, _, _ = AdjustRequiredTypeForTypeDirectedConversions infoReader ad isMethodArg isConstraint underlyingTy actualTy m
302306
if typeEquiv g adjustedTy actualTy then
303-
actualTy, TypeDirectedConversionUsed.Yes(warn TypeDirectedConversion.BuiltIn, true), None
307+
actualTy, TypeDirectedConversionUsed.Yes(warn TypeDirectedConversion.BuiltIn, true, true), None
304308
else
305309
reqdTy, TypeDirectedConversionUsed.No, None
306310

307311
// Adhoc based on op_Implicit, perhaps returing a new equational type constraint to
308312
// eliminate articifical constrained type variables.
309313
elif g.langVersion.SupportsFeature LanguageFeature.AdditionalTypeDirectedConversions then
310314
match TryFindRelevantImplicitConversion infoReader ad reqdTy actualTy m with
311-
| Some (minfo, _staticTy, eqn) -> actualTy, TypeDirectedConversionUsed.Yes(warn (TypeDirectedConversion.Implicit minfo), false), Some eqn
315+
| Some (minfo, _staticTy, eqn) -> actualTy, TypeDirectedConversionUsed.Yes(warn (TypeDirectedConversion.Implicit minfo), false, false), Some eqn
312316
| None -> reqdTy, TypeDirectedConversionUsed.No, None
313317

314318
else reqdTy, TypeDirectedConversionUsed.No, None

src/Compiler/Checking/MethodCalls.fsi

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ type CallerArgs<'T> =
119119
/// has been used in F# code
120120
[<RequireQualifiedAccess>]
121121
type TypeDirectedConversionUsed =
122-
| Yes of (DisplayEnv -> exn) * isTwoStepConversion: bool
122+
| Yes of (DisplayEnv -> exn) * isTwoStepConversion: bool * isNullable: bool
123123
| No
124124

125125
static member Combine: TypeDirectedConversionUsed -> TypeDirectedConversionUsed -> TypeDirectedConversionUsed

tests/fsharp/Compiler/Language/TypeDirectedConversionTests.fs

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -303,7 +303,7 @@ let test(x: 'T) =
303303
(11, 5, 11, 11)
304304
"""This construct causes code to be less generic than indicated by the type annotations. The type variable 'T has been constrained to be type 'int'."""
305305

306-
[<Test>]
306+
[<Test(Description="https://github.com/dotnet/fsharp/issues/13731")>]
307307
let ``Picking overload for typar fails when incompatible types are part of the candidate set``() =
308308
CompilerAssert.TypeCheckWithErrors
309309
"""
@@ -440,3 +440,66 @@ let test() =
440440
441441
if not (test().OtherArgs.Value.Name = "test") then failwith "Unexpected value was returned after setting Name"
442442
""" []
443+
444+
[<Test>]
445+
let ``Prefer nullable conversion only candidate when multiple candidates require conversions``() =
446+
CompilerAssert.RunScript
447+
"""
448+
type M() =
449+
static member A(size: System.DateTime, dtype: System.Nullable<int>) = 1
450+
static member A(size: System.DateTimeOffset, dtype: System.Nullable<int>) = 2
451+
452+
let test() = M.A(System.DateTime.UtcNow, 1)
453+
454+
if test() <> 1 then failwith "Incorrect overload picked"
455+
""" []
456+
457+
[<Test>]
458+
let ``Prefer nullable conversion over numeric conversion``() =
459+
CompilerAssert.RunScript
460+
"""
461+
type M() =
462+
static member A(n: int64) = 1
463+
static member A(n: System.Nullable<int>) = 2
464+
465+
let test() = M.A(0)
466+
467+
if test() <> 2 then failwith "Incorrect overload picked"
468+
""" []
469+
470+
[<Test>]
471+
let ``Prefer nullable conversion over op_Implicit conversion``() =
472+
473+
CompilerAssert.RunScript
474+
"""
475+
type M() =
476+
static member A(n: System.DateTimeOffset) = 1
477+
static member A(n: System.Nullable<System.DateTime>) = 2
478+
479+
let test() = M.A(System.DateTime.UtcNow)
480+
481+
if test() <> 2 then failwith "Incorrect overload picked"
482+
""" []
483+
484+
485+
[<Test(Description="https://github.com/dotnet/fsharp/issues/14318")>]
486+
let ``Picking overload for TDC candidate set fails as ambiguous while one candidate requires more conversions``() =
487+
CompilerAssert.TypeCheckSingleError
488+
"""
489+
type M() =
490+
static member A(m: System.DateTime, n: int64) = 1
491+
static member A(m: System.DateTimeOffset, n: int64) = 2
492+
493+
let test() = M.A(System.DateTime.UtcNow, 1)
494+
"""
495+
FSharpDiagnosticSeverity.Error
496+
41
497+
(6, 14, 6, 44)
498+
"""A unique overload for method 'A' could not be determined based on type information prior to this program point. A type annotation may be needed.
499+
500+
Known types of arguments: System.DateTime * int
501+
502+
Candidates:
503+
- static member M.A: m: System.DateTime * n: int64 -> int
504+
- static member M.A: m: System.DateTimeOffset * n: int64 -> int
505+
- static member M.A: m: System.DateTimeOffset * n: int64 -> int"""

0 commit comments

Comments
 (0)