From 4f479e2ba609be8f47a014c777d017762c6f1e3e Mon Sep 17 00:00:00 2001 From: Don Syme Date: Thu, 28 Apr 2022 23:22:25 +1000 Subject: [PATCH 1/3] Initial fix for https://github.com/dotnet/fsharp/issues/12860 --- src/fsharp/CheckExpressions.fs | 10 ++- src/fsharp/ConstraintSolver.fs | 79 +++++++++++------ src/fsharp/MethodCalls.fs | 151 ++++++++++++++++++++++++--------- src/fsharp/MethodCalls.fsi | 47 ++++++---- 4 files changed, 200 insertions(+), 87 deletions(-) diff --git a/src/fsharp/CheckExpressions.fs b/src/fsharp/CheckExpressions.fs index c098df997eb..83880e33535 100644 --- a/src/fsharp/CheckExpressions.fs +++ b/src/fsharp/CheckExpressions.fs @@ -484,15 +484,19 @@ let UnifyOverallType cenv (env: TcEnv) m overallTy actualTy = () else // try adhoc type-directed conversions - let reqdTy2, usesTDC, eqn = AdjustRequiredTypeForTypeDirectedConversions cenv.infoReader env.eAccessRights isMethodArg false reqdTy actualTy m + let (AdjustedRequiredTypeInfo(reqdTy2, usesTDC, eqn)) = + AdjustRequiredTypeForTypeDirectedConversions cenv.infoReader env.eAccessRights isMethodArg false reqdTy actualTy m + match eqn with | Some (ty1, ty2, msg) -> UnifyTypes cenv env m ty1 ty2 msg env.DisplayEnv | None -> () + match usesTDC with - | TypeDirectedConversionUsed.Yes warn -> warning(warn env.DisplayEnv) - | TypeDirectedConversionUsed.No -> () + | ConversionUsed.TypeDirected warn -> warning(warn env.DisplayEnv) + | _ -> () + if AddCxTypeMustSubsumeTypeUndoIfFailed env.DisplayEnv cenv.css m reqdTy2 actualTy then let reqdTyText, actualTyText, _cxs = NicePrint.minimalStringsOfTwoTypes env.DisplayEnv reqdTy actualTy warning (Error(FSComp.SR.tcSubsumptionImplicitConversionUsed(actualTyText, reqdTyText), m)) diff --git a/src/fsharp/ConstraintSolver.fs b/src/fsharp/ConstraintSolver.fs index 0cae4b6a5b0..05974b9e077 100644 --- a/src/fsharp/ConstraintSolver.fs +++ b/src/fsharp/ConstraintSolver.fs @@ -2407,15 +2407,15 @@ and CanMemberSigsMatchUpToCheck alwaysCheckReturn // Used to equate the formal method instantiation with the actual method instantiation // for a generic method, and the return types - (unifyTypes: TType -> TType -> OperationResult) + (unifyTypes: TType -> TType -> OperationResult) // Used to compare the "obj" type - (subsumeTypes: TType -> TType -> OperationResult) + (subsumeTypes: TType -> TType -> OperationResult) // Used to convert the "return" for MustConvertTo - (subsumeOrConvertTypes: bool -> TType -> TType -> OperationResult) + (subsumeOrConvertTypes: bool -> TType -> TType -> OperationResult) // Used to convert the arguments - (subsumeOrConvertArg: CalledArg -> CallerArg<_> -> OperationResult) + (subsumeOrConvertArg: CalledArg -> CallerArg<_> -> OperationResult) (reqdRetTyOpt: OverallTy option) - (calledMeth: CalledMeth<_>): OperationResult = + (calledMeth: CalledMeth<_>): OperationResult = trackErrors { let g = csenv.g let amap = csenv.amap @@ -2473,10 +2473,10 @@ and CanMemberSigsMatchUpToCheck ) - | _ -> ResultD TypeDirectedConversionUsed.No + | _ -> ResultD ConversionUsed.NoneOrOther else - ResultD TypeDirectedConversionUsed.No - | _ -> ResultD TypeDirectedConversionUsed.No + ResultD ConversionUsed.NoneOrOther + | _ -> ResultD ConversionUsed.NoneOrOther let! usesTDC5 = calledMeth.ArgSets |> MapCombineTDCD (fun argSet -> @@ -2511,7 +2511,7 @@ and CanMemberSigsMatchUpToCheck let! usesTDC7 = match reqdRetTyOpt with | Some _ when ( (* minfo.IsConstructor || *) not alwaysCheckReturn && isNil unnamedCalledOutArgs) -> - ResultD TypeDirectedConversionUsed.No + ResultD ConversionUsed.NoneOrOther | Some (MustConvertTo(isMethodArg, reqdTy)) when g.langVersion.SupportsFeature LanguageFeature.AdditionalTypeDirectedConversions -> let methodRetTy = calledMeth.CalledReturnTypeAfterOutArgTupling subsumeOrConvertTypes isMethodArg reqdTy methodRetTy @@ -2519,8 +2519,8 @@ and CanMemberSigsMatchUpToCheck let methodRetTy = calledMeth.CalledReturnTypeAfterOutArgTupling unifyTypes reqdRetTy.Commit methodRetTy | _ -> - ResultD TypeDirectedConversionUsed.No - return Array.reduce TypeDirectedConversionUsed.Combine [| usesTDC1; usesTDC2; usesTDC3; usesTDC4; usesTDC5; usesTDC6; usesTDC7 |] + ResultD ConversionUsed.NoneOrOther + return Array.reduce ConversionUsed.Combine [| usesTDC1; usesTDC2; usesTDC3; usesTDC4; usesTDC5; usesTDC6; usesTDC7 |] } // Wrap an ErrorsFromAddingSubsumptionConstraint error around any failure @@ -2582,15 +2582,20 @@ and ArgsMustSubsumeOrConvert let g = csenv.g let m = callerArg.Range - let calledArgTy, usesTDC, eqn = AdjustCalledArgType csenv.InfoReader ad isConstraint enforceNullableOptionalsKnownTypes calledArg callerArg + + let (AdjustedRequiredTypeInfo(calledArgTy, usesTDC, eqn)) = + AdjustCalledArgType csenv.InfoReader ad isConstraint enforceNullableOptionalsKnownTypes calledArg callerArg + match eqn with | Some (ty1, ty2, msg) -> do! SolveTypeEqualsTypeWithReport csenv ndeep m trace cxsln ty1 ty2 msg csenv.DisplayEnv | None -> () + match usesTDC with - | TypeDirectedConversionUsed.Yes warn -> do! WarnD(warn csenv.DisplayEnv) - | TypeDirectedConversionUsed.No -> () + | ConversionUsed.TypeDirected warn -> do! WarnD(warn csenv.DisplayEnv) + | _ -> () + do! SolveTypeSubsumesTypeWithReport csenv ndeep m trace cxsln calledArgTy callerArg.CallerArgumentType if calledArg.IsParamArray && isArray1DTy g calledArgTy && not (isArray1DTy g callerArg.CallerArgumentType) then return! ErrorD(Error(FSComp.SR.csMethodExpectsParams(), m)) @@ -2613,43 +2618,54 @@ and ArgsMustSubsumeOrConvertWithContextualReport trackErrors { let callerArgTy = callerArg.CallerArgumentType let m = callerArg.Range - let calledArgTy, usesTDC, eqn = AdjustCalledArgType csenv.InfoReader ad isConstraint true calledArg callerArg + + let (AdjustedRequiredTypeInfo(calledArgTy, usesTDC, eqn)) = + AdjustCalledArgType csenv.InfoReader ad isConstraint true calledArg callerArg + match eqn with | Some (ty1, ty2, msg) -> do! SolveTypeEqualsType csenv ndeep m trace cxsln ty1 ty2 msg csenv.DisplayEnv | None -> () + match usesTDC with - | TypeDirectedConversionUsed.Yes warn -> do! WarnD(warn csenv.DisplayEnv) - | TypeDirectedConversionUsed.No -> () + | ConversionUsed.TypeDirected warn -> do! WarnD(warn csenv.DisplayEnv) + | _ -> () + do! SolveTypeSubsumesTypeWithWrappedContextualReport csenv ndeep m trace cxsln calledArgTy callerArgTy (fun e -> ArgDoesNotMatchError(e :?> _, calledMeth, calledArg, callerArg)) + return usesTDC } and TypesEquiv csenv ndeep trace cxsln ty1 ty2 = trackErrors { do! SolveTypeEqualsTypeWithReport csenv ndeep csenv.m trace cxsln ty1 ty2 - return TypeDirectedConversionUsed.No + return ConversionUsed.NoneOrOther } and TypesMustSubsume (csenv: ConstraintSolverEnv) ndeep trace cxsln m calledArgTy callerArgTy = trackErrors { do! SolveTypeSubsumesTypeWithReport csenv ndeep m trace cxsln calledArgTy callerArgTy - return TypeDirectedConversionUsed.No + return ConversionUsed.NoneOrOther } and ReturnTypesMustSubsumeOrConvert (csenv: ConstraintSolverEnv) ad ndeep trace cxsln isConstraint m isMethodArg reqdTy actualTy = trackErrors { - let reqdTy, usesTDC, eqn = AdjustRequiredTypeForTypeDirectedConversions csenv.InfoReader ad isMethodArg isConstraint reqdTy actualTy m + let (AdjustedRequiredTypeInfo(reqdTy, usesTDC, eqn)) = + AdjustRequiredTypeForTypeDirectedConversions csenv.InfoReader ad isMethodArg isConstraint reqdTy actualTy m + match eqn with | Some (ty1, ty2, msg) -> do! SolveTypeEqualsType csenv ndeep m trace cxsln ty1 ty2 msg csenv.DisplayEnv | None -> () + match usesTDC with - | TypeDirectedConversionUsed.Yes warn -> do! WarnD(warn csenv.DisplayEnv) - | TypeDirectedConversionUsed.No -> () + | ConversionUsed.TypeDirected warn -> do! WarnD(warn csenv.DisplayEnv) + | _ -> () + do! SolveTypeSubsumesTypeWithReport csenv ndeep m trace cxsln reqdTy actualTy + return usesTDC } @@ -2657,15 +2673,19 @@ and ArgsEquivOrConvert (csenv: ConstraintSolverEnv) ad ndeep trace cxsln isConst trackErrors { let callerArgTy = callerArg.CallerArgumentType let m = callerArg.Range - let calledArgTy, usesTDC, eqn = AdjustCalledArgType csenv.InfoReader ad isConstraint true calledArg callerArg + let (AdjustedRequiredTypeInfo(calledArgTy, usesTDC, eqn)) = + AdjustCalledArgType csenv.InfoReader ad isConstraint true calledArg callerArg + match eqn with | Some (ty1, ty2, msg) -> do! SolveTypeEqualsType csenv ndeep m trace cxsln ty1 ty2 msg csenv.DisplayEnv | None -> () + match usesTDC with - | TypeDirectedConversionUsed.Yes warn -> do! WarnD(warn csenv.DisplayEnv) - | TypeDirectedConversionUsed.No -> () + | ConversionUsed.TypeDirected warn -> do! WarnD(warn csenv.DisplayEnv) + | _ -> () + if not (typeEquiv csenv.g calledArgTy callerArgTy) then return! ErrorD(Error(FSComp.SR.csArgumentTypesDoNotMatch(), m)) else @@ -2713,7 +2733,8 @@ and ReportNoCandidatesError (csenv: ConstraintSolverEnv) (nUnnamedCallerArgs, nN ErrorWithSuggestions((msgNum, FSComp.SR.csCtorHasNoArgumentOrReturnProperty(methodName, id.idText, msgText)), id.idRange, id.idText, suggestFields) else Error((msgNum, FSComp.SR.csMemberHasNoArgumentOrReturnProperty(methodName, id.idText, msgText)), id.idRange) - | [] -> Error((msgNum, msgText), m) + | [] -> + Error((msgNum, msgText), m) // One method, incorrect number of arguments provided by the user | _, _, ([], [cmeth]), _, _ when not cmeth.HasCorrectArity -> @@ -3005,7 +3026,7 @@ and ResolveOverloading let otherWarnCount = List.length otherWarnings // Prefer methods that don't use type-directed conversion - let c = compare (match usesTDC1 with TypeDirectedConversionUsed.No -> 1 | _ -> 0) (match usesTDC2 with TypeDirectedConversionUsed.No -> 1 | _ -> 0) + let c = compare (match usesTDC1 with ConversionUsed.TypeDirected _ -> 0 | _ -> 1) (match usesTDC2 with ConversionUsed.TypeDirected _ -> 0 | _ -> 1) if c <> 0 then c else // Prefer methods that don't give "this code is less generic" warnings @@ -3108,6 +3129,10 @@ and ResolveOverloading 0 if c <> 0 then c else + // Prefer methods that don't use either nullable adjustment or type-directed conversion + let c = compare (match usesTDC1 with ConversionUsed.NoneOrOther -> 1 | _ -> 0) (match usesTDC2 with ConversionUsed.NoneOrOther -> 1 | _ -> 0) + if c <> 0 then c else + 0 let bestMethods = diff --git a/src/fsharp/MethodCalls.fs b/src/fsharp/MethodCalls.fs index a41fd0dbbaa..c3b3bc0c194 100644 --- a/src/fsharp/MethodCalls.fs +++ b/src/fsharp/MethodCalls.fs @@ -230,19 +230,45 @@ type TypeDirectedConversion = | Implicit of MethInfo [] -type TypeDirectedConversionUsed = - | Yes of (DisplayEnv -> exn) - | No +type ConversionUsed = + | TypeDirected of (DisplayEnv -> exn) + | NullableAdjustment + | NoneOrOther + static member Combine a b = - match a with - | Yes _ -> a - | No -> b + match a, b with + | TypeDirected _, _ -> a + | _, TypeDirected _ -> b + | NullableAdjustment _, _ -> a + | _, NullableAdjustment _ -> b + | NoneOrOther, NoneOrOther -> a + + member tdc.WithNullableAdjustment() = + ConversionUsed.Combine tdc ConversionUsed.NullableAdjustment + +type AdjustedRequiredTypeInfo = + | AdjustedRequiredTypeInfo of + adjustedRequiredTy: TType * + tdcInfo: ConversionUsed * + eqnInfo: (TType * TType * (DisplayEnv -> unit)) option + + static member Unadjusted reqdTy = AdjustedRequiredTypeInfo(reqdTy, ConversionUsed.NoneOrOther, None) + + static member Adjusted reqdTy = AdjustedRequiredTypeInfo(reqdTy, ConversionUsed.NoneOrOther, None) + + static member BuiltInTypeDirectedConversion warn reqdTy = AdjustedRequiredTypeInfo(reqdTy, ConversionUsed.TypeDirected(warn TypeDirectedConversion.BuiltIn), None) + + static member ImplictTypeDirectedConversion warn reqdTy minfo eqn = AdjustedRequiredTypeInfo(reqdTy, ConversionUsed.TypeDirected(warn (TypeDirectedConversion.Implicit minfo)), Some eqn) + + member adj.WithNullableAdjustment() = + let (AdjustedRequiredTypeInfo(adjustedTy, tdc, info)) = adj + AdjustedRequiredTypeInfo(adjustedTy, tdc.WithNullableAdjustment(), info) let MapCombineTDCD mapper xs = - MapReduceD mapper TypeDirectedConversionUsed.No TypeDirectedConversionUsed.Combine xs + MapReduceD mapper ConversionUsed.NoneOrOther ConversionUsed.Combine xs let MapCombineTDC2D mapper xs ys = - MapReduce2D mapper TypeDirectedConversionUsed.No TypeDirectedConversionUsed.Combine xs ys + MapReduce2D mapper ConversionUsed.NoneOrOther ConversionUsed.Combine xs ys let rec AdjustRequiredTypeForTypeDirectedConversions (infoReader: InfoReader) ad isMethodArg isConstraint (reqdTy: TType) actualTy m = let g = infoReader.g @@ -260,12 +286,13 @@ let rec AdjustRequiredTypeForTypeDirectedConversions (infoReader: InfoReader) ad Error(FSComp.SR.tcImplicitConversionUsedForNonMethodArg(methText, actualTyText, reqdTyText), m) if isConstraint then - reqdTy, TypeDirectedConversionUsed.No, None + AdjustedRequiredTypeInfo.Unadjusted reqdTy else // Delegate --> function if isDelegateTy g reqdTy && isFunTy g actualTy then - AdjustDelegateTy infoReader actualTy reqdTy m, TypeDirectedConversionUsed.No, None + let adjustedTy = AdjustDelegateTy infoReader actualTy reqdTy m + AdjustedRequiredTypeInfo.Adjusted adjustedTy // (T -> U) --> Expression U> LINQ-style quotation elif isLinqExpressionTy g reqdTy && isDelegateTy g (destLinqExpressionTy g reqdTy) && isFunTy g actualTy then @@ -274,24 +301,27 @@ let rec AdjustRequiredTypeForTypeDirectedConversions (infoReader: InfoReader) ad // Adhoc int32 --> int64 elif g.langVersion.SupportsFeature LanguageFeature.AdditionalTypeDirectedConversions && typeEquiv g g.int64_ty reqdTy && typeEquiv g g.int32_ty actualTy then - g.int32_ty, TypeDirectedConversionUsed.Yes(warn TypeDirectedConversion.BuiltIn), None + AdjustedRequiredTypeInfo.BuiltInTypeDirectedConversion warn g.int32_ty // Adhoc int32 --> nativeint elif g.langVersion.SupportsFeature LanguageFeature.AdditionalTypeDirectedConversions && typeEquiv g g.nativeint_ty reqdTy && typeEquiv g g.int32_ty actualTy then - g.int32_ty, TypeDirectedConversionUsed.Yes(warn TypeDirectedConversion.BuiltIn), None + AdjustedRequiredTypeInfo.BuiltInTypeDirectedConversion warn g.int32_ty // Adhoc int32 --> float64 elif g.langVersion.SupportsFeature LanguageFeature.AdditionalTypeDirectedConversions && typeEquiv g g.float_ty reqdTy && typeEquiv g g.int32_ty actualTy then - g.int32_ty, TypeDirectedConversionUsed.Yes(warn TypeDirectedConversion.BuiltIn), None + AdjustedRequiredTypeInfo.BuiltInTypeDirectedConversion warn g.int32_ty // Adhoc based on op_Implicit, perhaps returing a new equational type constraint to // eliminate articifical constrained type variables. elif g.langVersion.SupportsFeature LanguageFeature.AdditionalTypeDirectedConversions then match TryFindRelevantImplicitConversion infoReader ad reqdTy actualTy m with - | Some (minfo, eqn) -> actualTy, TypeDirectedConversionUsed.Yes(warn (TypeDirectedConversion.Implicit minfo)), Some eqn - | None -> reqdTy, TypeDirectedConversionUsed.No, None + | Some (minfo, eqn) -> + AdjustedRequiredTypeInfo.ImplictTypeDirectedConversion warn actualTy minfo eqn + | None -> + AdjustedRequiredTypeInfo.Unadjusted reqdTy - else reqdTy, TypeDirectedConversionUsed.No, None + else + AdjustedRequiredTypeInfo.Unadjusted reqdTy // If the called method argument is a delegate type, and the caller is known to be a function type, then the caller may provide a function // If the called method argument is an Expression type, and the caller is known to be a function type, then the caller may provide a T @@ -300,7 +330,8 @@ let AdjustCalledArgTypeForTypeDirectedConversionsAndAutoQuote (infoReader: InfoR let g = infoReader.g if calledArg.ReflArgInfo.AutoQuote && isQuotedExprTy g calledArgTy && not (isQuotedExprTy g callerArgTy) then - destQuotedExprTy g calledArgTy, TypeDirectedConversionUsed.No, None + let adjustedTy = destQuotedExprTy g calledArgTy + AdjustedRequiredTypeInfo.Adjusted adjustedTy else AdjustRequiredTypeForTypeDirectedConversions infoReader ad true false calledArgTy callerArgTy m @@ -316,58 +347,93 @@ let AdjustCalledArgTypeForOptionals (infoReader: InfoReader) ad enforceNullableO | CallerSide _ -> if g.langVersion.SupportsFeature LanguageFeature.NullableOptionalInterop then if isNullableTy g calledArgTy then - mkOptionTy g (destNullableTy g calledArgTy), TypeDirectedConversionUsed.No, None + let adjustedTy = mkOptionTy g (destNullableTy g calledArgTy) + AdjustedRequiredTypeInfo.Adjusted adjustedTy else - mkOptionTy g calledArgTy, TypeDirectedConversionUsed.No, None + let adjustedTy = mkOptionTy g calledArgTy + AdjustedRequiredTypeInfo.Adjusted adjustedTy else - calledArgTy, TypeDirectedConversionUsed.No, None + AdjustedRequiredTypeInfo.Unadjusted calledArgTy // FSharpMethod(?x = arg), optional F#-style argument | CalleeSide -> // In this case, the called argument will already have option type - calledArgTy, TypeDirectedConversionUsed.No, None + AdjustedRequiredTypeInfo.Unadjusted calledArgTy | NotOptional -> // This condition represents an error but the error is raised in later processing AdjustCalledArgTypeForTypeDirectedConversionsAndAutoQuote infoReader ad callerArgTy calledArgTy calledArg m else match calledArg.OptArgInfo with - // CSharpMethod(x = arg), non-optional C#-style argument, may have type Nullable. + // Pre F#-5.0 - SomeMethod(x = arg), non-optional method argument, may have type Nullable. | NotOptional when not (g.langVersion.SupportsFeature LanguageFeature.NullableOptionalInterop) -> AdjustCalledArgTypeForTypeDirectedConversionsAndAutoQuote infoReader ad callerArgTy calledArgTy calledArg m - // The arg should have type ty. However for backwards compat, we also allow arg to have type Nullable + // F#-5.0+ SomeMethod(x = arg), non-optional method argument, may have type Nullable. + // SomeMethod(x = arg), C#-style optional method argument, may have type Nullable. | NotOptional - // CSharpMethod(x = arg), optional C#-style argument, may have type Nullable. | CallerSide _ -> + // Check if the called argument type is a Nullable if isNullableTy g calledArgTy && g.langVersion.SupportsFeature LanguageFeature.NullableOptionalInterop then - // If inference has worked out it's a nullable then use this + // If + // - the called argument type is a Nullable, and + // - inference has worked out the caller type of the argument is Nullable + // then use Nullable as the expected type, with no conversion. if isNullableTy g callerArgTy then - calledArgTy, TypeDirectedConversionUsed.No, None + AdjustedRequiredTypeInfo.Unadjusted calledArgTy - // If inference has worked out it's a struct (e.g. an int) then use this + // If + // - the called argument type is a Nullable, and + // - inference has worked out the caller type of the argument is a non-Nullable struct type + // then use SomeTy as the expected type, and note a nullable conversion. elif isStructTy g callerArgTy then let calledArgTy2 = destNullableTy g calledArgTy - AdjustRequiredTypeForTypeDirectedConversions infoReader ad true false calledArgTy2 callerArgTy m - - // If neither and we are at the end of overload resolution then use the Nullable + let info = AdjustRequiredTypeForTypeDirectedConversions infoReader ad true false calledArgTy2 callerArgTy m + info.WithNullableAdjustment() + + // If + // - the called argument type is a Nullable, and + // - inference has worked out the caller type of the argument is a reference type + // then use Nullable as the expected type. Note there is no valid + // conversion from the reference type to either the nullable type (which is a struct) + // nor its argument type (which must also be a struct). This means an error will result, + // but another overload may be relevant. + elif isRefTy g callerArgTy then + AdjustedRequiredTypeInfo.Unadjusted calledArgTy + + // If + // - the called argument type is a Nullable, and + // - inference hasn't worked out anything about the argument type (i.e. it's a variable type), and + // - we are at the end of overload resolution + // then use Nullable as the expected type. elif enforceNullableOptionalsKnownTypes then - calledArgTy, TypeDirectedConversionUsed.No, None + AdjustedRequiredTypeInfo.Unadjusted calledArgTy - // If at the beginning of inference then use a type variable. else match calledArg.OptArgInfo with - // If inference has not solved the kind of Nullable on the called arg and is not optional then use this. + // If + // - the called argument type is a Nullable, and + // - inference hasn't worked out anything about the caller argument type (i.e. it's a variable type), and + // - the argument is not optional, and + // - we're at the beginning of overload resolution + // then use Nullable as the required type, without a conversion. | NotOptional when isTyparTy g (destNullableTy g calledArgTy) -> - calledArgTy, TypeDirectedConversionUsed.No, None + AdjustedRequiredTypeInfo.Unadjusted calledArgTy | _ -> + // If + // - the called argument type is a Nullable, and + // - inference hasn't worked out anything about the caller argument type (i.e. it's a variable type), and + // - we're at the beginning of overload resolution, and + // + // then use a fresh type variable as the required type, without a conversion, + // because we don't yet know if a nullable conversion will apply. let compgenId = mkSynId range0 unassignedTyparName let tp = mkTyparTy (Construct.NewTypar (TyparKind.Type, TyparRigidity.Flexible, SynTypar(compgenId, TyparStaticReq.None, true), false, TyparDynamicReq.No, [], false, false)) - tp, TypeDirectedConversionUsed.No, None + AdjustedRequiredTypeInfo.Adjusted tp else AdjustCalledArgTypeForTypeDirectedConversionsAndAutoQuote infoReader ad callerArgTy calledArgTy calledArg m - // FSharpMethod(x = arg), optional F#-style argument, should have option type + // SomeMethod(x = arg), optional F#-style argument, should have option type | CalleeSide -> let calledArgTy2 = if isOptionTy g calledArgTy then @@ -402,7 +468,7 @@ let AdjustCalledArgType (infoReader: InfoReader) ad isConstraint enforceNullable let calledArgTy = calledArg.CalledArgumentType let callerArgTy = callerArg.CallerArgumentType if isConstraint then - calledArgTy, TypeDirectedConversionUsed.No, None + AdjustedRequiredTypeInfo.Unadjusted calledArgTy else // If the called method argument is an inref type, then the caller may provide a byref or value @@ -413,15 +479,16 @@ let AdjustCalledArgType (infoReader: InfoReader) ad isConstraint enforceNullable else destByrefTy g calledArgTy #else - calledArgTy, TypeDirectedConversionUsed.No, None + AdjustedRequiredTypeInfo.Unadjusted calledArgTy #endif // If the called method argument is a (non inref) byref type, then the caller may provide a byref or ref. elif isByrefTy g calledArgTy then if isByrefTy g callerArgTy then - calledArgTy, TypeDirectedConversionUsed.No, None + AdjustedRequiredTypeInfo.Unadjusted calledArgTy else - mkRefCellTy g (destByrefTy g calledArgTy), TypeDirectedConversionUsed.No, None + let adjustedTy = mkRefCellTy g (destByrefTy g calledArgTy) + AdjustedRequiredTypeInfo.Adjusted adjustedTy else AdjustCalledArgTypeForOptionals infoReader ad enforceNullableOptionalsKnownTypes calledArg calledArgTy callerArg @@ -822,7 +889,9 @@ let ExamineArgumentForLambdaPropagation (infoReader: InfoReader) ad noEagerConst let countOfCallerLambdaArg = InferLambdaArgsForLambdaPropagation argExpr // Adjust for Expression<_>, Func<_, _>, ... - let adjustedCalledArgTy, _, _ = AdjustCalledArgType infoReader ad false false arg.CalledArg arg.CallerArg + let (AdjustedRequiredTypeInfo(adjustedCalledArgTy, _, _)) = + AdjustCalledArgType infoReader ad false false arg.CalledArg arg.CallerArg + if countOfCallerLambdaArg > 0 then // Decompose the explicit function type of the target let calledLambdaArgTys, _calledLambdaRetTy = stripFunTy g adjustedCalledArgTy diff --git a/src/fsharp/MethodCalls.fsi b/src/fsharp/MethodCalls.fsi index de41eda0548..a528d54f954 100644 --- a/src/fsharp/MethodCalls.fsi +++ b/src/fsharp/MethodCalls.fsi @@ -104,21 +104,36 @@ type CallerArgs<'T> = static member Empty: CallerArgs<'T> -/// Indicates whether a type directed conversion (e.g. int32 to int64, or op_Implicit) -/// has been used in F# code +/// Indicates whether a conversion (e.g. int32 to int64, or op_Implicit) +/// has been used in F# code, particularly for method arguments. [] -type TypeDirectedConversionUsed = - | Yes of (DisplayEnv -> exn) - | No - static member Combine: TypeDirectedConversionUsed -> TypeDirectedConversionUsed -> TypeDirectedConversionUsed +type ConversionUsed = + /// Indicates a type-directed conversion, added in F# 6.0. + | TypeDirected of (DisplayEnv -> exn) -/// Performs a set of constraint solver operations returning TypeDirectedConversionUsed and + /// Indicates, for example, an int --> Nullable conversion, added in F# 5.0. + /// These were implemented before the more general mechanism of type-directed conversions + /// and for compatibility are treated slightly differently for method overloading. + | NullableAdjustment + + /// Indicates either some other conversion that is not relevant to overload resolution logic, or no conversion at all + | NoneOrOther + + static member Combine: ConversionUsed -> ConversionUsed -> ConversionUsed + +type AdjustedRequiredTypeInfo = + | AdjustedRequiredTypeInfo of + adjustedRequiredTy: TType * + tdcInfo: ConversionUsed * + eqnInfo: (TType * TType * (DisplayEnv -> unit)) option + +/// Performs a set of constraint solver operations returning ConversionUsed and /// combines their results. -val MapCombineTDCD: mapper:('a -> OperationResult) -> xs:'a list -> OperationResult +val MapCombineTDCD: mapper:('T -> OperationResult) -> xs:'T list -> OperationResult -/// Performs a set of constraint solver operations returning TypeDirectedConversionUsed and +/// Performs a set of constraint solver operations returning ConversionUsed and /// combines their results. -val MapCombineTDC2D: mapper:('a -> 'b -> OperationResult) -> xs:'a list -> ys:'b list -> OperationResult +val MapCombineTDC2D: mapper:('T -> 'b -> OperationResult) -> xs:'T list -> ys:'b list -> OperationResult /// F# supports some adhoc conversions to make expression fit known overall type val AdjustRequiredTypeForTypeDirectedConversions: @@ -129,7 +144,7 @@ val AdjustRequiredTypeForTypeDirectedConversions: reqdTy: TType -> actualTy:TType -> m: range - -> TType * TypeDirectedConversionUsed * (TType * TType * (DisplayEnv -> unit)) option + -> AdjustedRequiredTypeInfo /// F# supports some adhoc conversions to make expression fit known overall type val AdjustCalledArgType: @@ -138,8 +153,8 @@ val AdjustCalledArgType: isConstraint:bool -> enforceNullableOptionalsKnownTypes:bool -> calledArg:CalledArg -> - callerArg:CallerArg<'a> - -> TType * TypeDirectedConversionUsed * (TType * TType * (DisplayEnv -> unit)) option + callerArg:CallerArg<'T> + -> AdjustedRequiredTypeInfo type CalledMethArgSet<'T> = { /// The called arguments corresponding to "unnamed" arguments @@ -356,7 +371,7 @@ val AdjustCallerArgExpr: callerArgTy:TType -> m:range -> callerArgExpr:Expr -> - 'a option * Expr + 'T option * Expr /// Build the argument list for a method call. Adjust for param array, optional arguments, byref arguments and coercions. /// For example, if you pass an F# reference cell to a byref then we must get the address of the @@ -369,7 +384,7 @@ val AdjustCallerArgs: ad:AccessorDomain -> calledMeth:CalledMeth -> objArgs:Expr list -> - lambdaVars:'a option -> + lambdaVars:'T option -> mItem:range -> mMethExpr:range -> (Expr -> Expr) * Expr list * @@ -384,7 +399,7 @@ val ILFieldStaticChecks: g:TcGlobals -> amap:ImportMap -> infoReader:InfoReader val ILFieldInstanceChecks: g:TcGlobals -> amap:ImportMap -> ad:AccessorDomain -> m:range -> finfo:ILFieldInfo -> unit -val MethInfoChecks: g:TcGlobals -> amap:ImportMap -> isInstance:bool -> tyargsOpt:'a option -> objArgs:Expr list -> ad:AccessorDomain -> m:range -> minfo:MethInfo -> unit +val MethInfoChecks: g:TcGlobals -> amap:ImportMap -> isInstance:bool -> tyargsOpt:'T option -> objArgs:Expr list -> ad:AccessorDomain -> m:range -> minfo:MethInfo -> unit exception FieldNotMutable of TypedTreeOps.DisplayEnv * RecdFieldRef * range From f048753caa5553bf62b2f45e9c0c9ab686882bf9 Mon Sep 17 00:00:00 2001 From: Don Syme Date: Thu, 28 Apr 2022 23:29:21 +1000 Subject: [PATCH 2/3] Initial fix for https://github.com/dotnet/fsharp/issues/12860 --- src/fsharp/CheckExpressions.fs | 2 +- src/fsharp/ConstraintSolver.fs | 8 ++--- src/fsharp/MethodCalls.fs | 64 +++++++++++++++++----------------- src/fsharp/MethodCalls.fsi | 16 ++++----- 4 files changed, 45 insertions(+), 45 deletions(-) diff --git a/src/fsharp/CheckExpressions.fs b/src/fsharp/CheckExpressions.fs index 83880e33535..e83c343ad6d 100644 --- a/src/fsharp/CheckExpressions.fs +++ b/src/fsharp/CheckExpressions.fs @@ -484,7 +484,7 @@ let UnifyOverallType cenv (env: TcEnv) m overallTy actualTy = () else // try adhoc type-directed conversions - let (AdjustedRequiredTypeInfo(reqdTy2, usesTDC, eqn)) = + let (TypeAdjustmentInfo(reqdTy2, usesTDC, eqn)) = AdjustRequiredTypeForTypeDirectedConversions cenv.infoReader env.eAccessRights isMethodArg false reqdTy actualTy m match eqn with diff --git a/src/fsharp/ConstraintSolver.fs b/src/fsharp/ConstraintSolver.fs index 05974b9e077..835a269388e 100644 --- a/src/fsharp/ConstraintSolver.fs +++ b/src/fsharp/ConstraintSolver.fs @@ -2583,7 +2583,7 @@ and ArgsMustSubsumeOrConvert let g = csenv.g let m = callerArg.Range - let (AdjustedRequiredTypeInfo(calledArgTy, usesTDC, eqn)) = + let (TypeAdjustmentInfo(calledArgTy, usesTDC, eqn)) = AdjustCalledArgType csenv.InfoReader ad isConstraint enforceNullableOptionalsKnownTypes calledArg callerArg match eqn with @@ -2619,7 +2619,7 @@ and ArgsMustSubsumeOrConvertWithContextualReport let callerArgTy = callerArg.CallerArgumentType let m = callerArg.Range - let (AdjustedRequiredTypeInfo(calledArgTy, usesTDC, eqn)) = + let (TypeAdjustmentInfo(calledArgTy, usesTDC, eqn)) = AdjustCalledArgType csenv.InfoReader ad isConstraint true calledArg callerArg match eqn with @@ -2651,7 +2651,7 @@ and TypesMustSubsume (csenv: ConstraintSolverEnv) ndeep trace cxsln m calledArgT and ReturnTypesMustSubsumeOrConvert (csenv: ConstraintSolverEnv) ad ndeep trace cxsln isConstraint m isMethodArg reqdTy actualTy = trackErrors { - let (AdjustedRequiredTypeInfo(reqdTy, usesTDC, eqn)) = + let (TypeAdjustmentInfo(reqdTy, usesTDC, eqn)) = AdjustRequiredTypeForTypeDirectedConversions csenv.InfoReader ad isMethodArg isConstraint reqdTy actualTy m match eqn with @@ -2673,7 +2673,7 @@ and ArgsEquivOrConvert (csenv: ConstraintSolverEnv) ad ndeep trace cxsln isConst trackErrors { let callerArgTy = callerArg.CallerArgumentType let m = callerArg.Range - let (AdjustedRequiredTypeInfo(calledArgTy, usesTDC, eqn)) = + let (TypeAdjustmentInfo(calledArgTy, usesTDC, eqn)) = AdjustCalledArgType csenv.InfoReader ad isConstraint true calledArg callerArg match eqn with diff --git a/src/fsharp/MethodCalls.fs b/src/fsharp/MethodCalls.fs index c3b3bc0c194..0919d06ab90 100644 --- a/src/fsharp/MethodCalls.fs +++ b/src/fsharp/MethodCalls.fs @@ -246,23 +246,23 @@ type ConversionUsed = member tdc.WithNullableAdjustment() = ConversionUsed.Combine tdc ConversionUsed.NullableAdjustment -type AdjustedRequiredTypeInfo = - | AdjustedRequiredTypeInfo of +type TypeAdjustmentInfo = + | TypeAdjustmentInfo of adjustedRequiredTy: TType * - tdcInfo: ConversionUsed * + conversionInfo: ConversionUsed * eqnInfo: (TType * TType * (DisplayEnv -> unit)) option - static member Unadjusted reqdTy = AdjustedRequiredTypeInfo(reqdTy, ConversionUsed.NoneOrOther, None) + static member Unadjusted reqdTy = TypeAdjustmentInfo(reqdTy, ConversionUsed.NoneOrOther, None) - static member Adjusted reqdTy = AdjustedRequiredTypeInfo(reqdTy, ConversionUsed.NoneOrOther, None) + static member Adjusted reqdTy = TypeAdjustmentInfo(reqdTy, ConversionUsed.NoneOrOther, None) - static member BuiltInTypeDirectedConversion warn reqdTy = AdjustedRequiredTypeInfo(reqdTy, ConversionUsed.TypeDirected(warn TypeDirectedConversion.BuiltIn), None) + static member BuiltInTypeDirectedConversion warn reqdTy = TypeAdjustmentInfo(reqdTy, ConversionUsed.TypeDirected(warn TypeDirectedConversion.BuiltIn), None) - static member ImplictTypeDirectedConversion warn reqdTy minfo eqn = AdjustedRequiredTypeInfo(reqdTy, ConversionUsed.TypeDirected(warn (TypeDirectedConversion.Implicit minfo)), Some eqn) + static member ImplictTypeDirectedConversion warn reqdTy minfo eqn = TypeAdjustmentInfo(reqdTy, ConversionUsed.TypeDirected(warn (TypeDirectedConversion.Implicit minfo)), Some eqn) member adj.WithNullableAdjustment() = - let (AdjustedRequiredTypeInfo(adjustedTy, tdc, info)) = adj - AdjustedRequiredTypeInfo(adjustedTy, tdc.WithNullableAdjustment(), info) + let (TypeAdjustmentInfo(adjustedTy, tdc, info)) = adj + TypeAdjustmentInfo(adjustedTy, tdc.WithNullableAdjustment(), info) let MapCombineTDCD mapper xs = MapReduceD mapper ConversionUsed.NoneOrOther ConversionUsed.Combine xs @@ -286,13 +286,13 @@ let rec AdjustRequiredTypeForTypeDirectedConversions (infoReader: InfoReader) ad Error(FSComp.SR.tcImplicitConversionUsedForNonMethodArg(methText, actualTyText, reqdTyText), m) if isConstraint then - AdjustedRequiredTypeInfo.Unadjusted reqdTy + TypeAdjustmentInfo.Unadjusted reqdTy else // Delegate --> function if isDelegateTy g reqdTy && isFunTy g actualTy then let adjustedTy = AdjustDelegateTy infoReader actualTy reqdTy m - AdjustedRequiredTypeInfo.Adjusted adjustedTy + TypeAdjustmentInfo.Adjusted adjustedTy // (T -> U) --> Expression U> LINQ-style quotation elif isLinqExpressionTy g reqdTy && isDelegateTy g (destLinqExpressionTy g reqdTy) && isFunTy g actualTy then @@ -301,27 +301,27 @@ let rec AdjustRequiredTypeForTypeDirectedConversions (infoReader: InfoReader) ad // Adhoc int32 --> int64 elif g.langVersion.SupportsFeature LanguageFeature.AdditionalTypeDirectedConversions && typeEquiv g g.int64_ty reqdTy && typeEquiv g g.int32_ty actualTy then - AdjustedRequiredTypeInfo.BuiltInTypeDirectedConversion warn g.int32_ty + TypeAdjustmentInfo.BuiltInTypeDirectedConversion warn g.int32_ty // Adhoc int32 --> nativeint elif g.langVersion.SupportsFeature LanguageFeature.AdditionalTypeDirectedConversions && typeEquiv g g.nativeint_ty reqdTy && typeEquiv g g.int32_ty actualTy then - AdjustedRequiredTypeInfo.BuiltInTypeDirectedConversion warn g.int32_ty + TypeAdjustmentInfo.BuiltInTypeDirectedConversion warn g.int32_ty // Adhoc int32 --> float64 elif g.langVersion.SupportsFeature LanguageFeature.AdditionalTypeDirectedConversions && typeEquiv g g.float_ty reqdTy && typeEquiv g g.int32_ty actualTy then - AdjustedRequiredTypeInfo.BuiltInTypeDirectedConversion warn g.int32_ty + TypeAdjustmentInfo.BuiltInTypeDirectedConversion warn g.int32_ty // Adhoc based on op_Implicit, perhaps returing a new equational type constraint to // eliminate articifical constrained type variables. elif g.langVersion.SupportsFeature LanguageFeature.AdditionalTypeDirectedConversions then match TryFindRelevantImplicitConversion infoReader ad reqdTy actualTy m with | Some (minfo, eqn) -> - AdjustedRequiredTypeInfo.ImplictTypeDirectedConversion warn actualTy minfo eqn + TypeAdjustmentInfo.ImplictTypeDirectedConversion warn actualTy minfo eqn | None -> - AdjustedRequiredTypeInfo.Unadjusted reqdTy + TypeAdjustmentInfo.Unadjusted reqdTy else - AdjustedRequiredTypeInfo.Unadjusted reqdTy + TypeAdjustmentInfo.Unadjusted reqdTy // If the called method argument is a delegate type, and the caller is known to be a function type, then the caller may provide a function // If the called method argument is an Expression type, and the caller is known to be a function type, then the caller may provide a T @@ -331,7 +331,7 @@ let AdjustCalledArgTypeForTypeDirectedConversionsAndAutoQuote (infoReader: InfoR if calledArg.ReflArgInfo.AutoQuote && isQuotedExprTy g calledArgTy && not (isQuotedExprTy g callerArgTy) then let adjustedTy = destQuotedExprTy g calledArgTy - AdjustedRequiredTypeInfo.Adjusted adjustedTy + TypeAdjustmentInfo.Adjusted adjustedTy else AdjustRequiredTypeForTypeDirectedConversions infoReader ad true false calledArgTy callerArgTy m @@ -348,17 +348,17 @@ let AdjustCalledArgTypeForOptionals (infoReader: InfoReader) ad enforceNullableO if g.langVersion.SupportsFeature LanguageFeature.NullableOptionalInterop then if isNullableTy g calledArgTy then let adjustedTy = mkOptionTy g (destNullableTy g calledArgTy) - AdjustedRequiredTypeInfo.Adjusted adjustedTy + TypeAdjustmentInfo.Adjusted adjustedTy else let adjustedTy = mkOptionTy g calledArgTy - AdjustedRequiredTypeInfo.Adjusted adjustedTy + TypeAdjustmentInfo.Adjusted adjustedTy else - AdjustedRequiredTypeInfo.Unadjusted calledArgTy + TypeAdjustmentInfo.Unadjusted calledArgTy // FSharpMethod(?x = arg), optional F#-style argument | CalleeSide -> // In this case, the called argument will already have option type - AdjustedRequiredTypeInfo.Unadjusted calledArgTy + TypeAdjustmentInfo.Unadjusted calledArgTy | NotOptional -> // This condition represents an error but the error is raised in later processing @@ -380,7 +380,7 @@ let AdjustCalledArgTypeForOptionals (infoReader: InfoReader) ad enforceNullableO // - inference has worked out the caller type of the argument is Nullable // then use Nullable as the expected type, with no conversion. if isNullableTy g callerArgTy then - AdjustedRequiredTypeInfo.Unadjusted calledArgTy + TypeAdjustmentInfo.Unadjusted calledArgTy // If // - the called argument type is a Nullable, and @@ -399,7 +399,7 @@ let AdjustCalledArgTypeForOptionals (infoReader: InfoReader) ad enforceNullableO // nor its argument type (which must also be a struct). This means an error will result, // but another overload may be relevant. elif isRefTy g callerArgTy then - AdjustedRequiredTypeInfo.Unadjusted calledArgTy + TypeAdjustmentInfo.Unadjusted calledArgTy // If // - the called argument type is a Nullable, and @@ -407,7 +407,7 @@ let AdjustCalledArgTypeForOptionals (infoReader: InfoReader) ad enforceNullableO // - we are at the end of overload resolution // then use Nullable as the expected type. elif enforceNullableOptionalsKnownTypes then - AdjustedRequiredTypeInfo.Unadjusted calledArgTy + TypeAdjustmentInfo.Unadjusted calledArgTy else match calledArg.OptArgInfo with @@ -418,7 +418,7 @@ let AdjustCalledArgTypeForOptionals (infoReader: InfoReader) ad enforceNullableO // - we're at the beginning of overload resolution // then use Nullable as the required type, without a conversion. | NotOptional when isTyparTy g (destNullableTy g calledArgTy) -> - AdjustedRequiredTypeInfo.Unadjusted calledArgTy + TypeAdjustmentInfo.Unadjusted calledArgTy | _ -> // If // - the called argument type is a Nullable, and @@ -429,7 +429,7 @@ let AdjustCalledArgTypeForOptionals (infoReader: InfoReader) ad enforceNullableO // because we don't yet know if a nullable conversion will apply. let compgenId = mkSynId range0 unassignedTyparName let tp = mkTyparTy (Construct.NewTypar (TyparKind.Type, TyparRigidity.Flexible, SynTypar(compgenId, TyparStaticReq.None, true), false, TyparDynamicReq.No, [], false, false)) - AdjustedRequiredTypeInfo.Adjusted tp + TypeAdjustmentInfo.Adjusted tp else AdjustCalledArgTypeForTypeDirectedConversionsAndAutoQuote infoReader ad callerArgTy calledArgTy calledArg m @@ -468,7 +468,7 @@ let AdjustCalledArgType (infoReader: InfoReader) ad isConstraint enforceNullable let calledArgTy = calledArg.CalledArgumentType let callerArgTy = callerArg.CallerArgumentType if isConstraint then - AdjustedRequiredTypeInfo.Unadjusted calledArgTy + TypeAdjustmentInfo.Unadjusted calledArgTy else // If the called method argument is an inref type, then the caller may provide a byref or value @@ -479,16 +479,16 @@ let AdjustCalledArgType (infoReader: InfoReader) ad isConstraint enforceNullable else destByrefTy g calledArgTy #else - AdjustedRequiredTypeInfo.Unadjusted calledArgTy + TypeAdjustmentInfo.Unadjusted calledArgTy #endif // If the called method argument is a (non inref) byref type, then the caller may provide a byref or ref. elif isByrefTy g calledArgTy then if isByrefTy g callerArgTy then - AdjustedRequiredTypeInfo.Unadjusted calledArgTy + TypeAdjustmentInfo.Unadjusted calledArgTy else let adjustedTy = mkRefCellTy g (destByrefTy g calledArgTy) - AdjustedRequiredTypeInfo.Adjusted adjustedTy + TypeAdjustmentInfo.Adjusted adjustedTy else AdjustCalledArgTypeForOptionals infoReader ad enforceNullableOptionalsKnownTypes calledArg calledArgTy callerArg @@ -889,7 +889,7 @@ let ExamineArgumentForLambdaPropagation (infoReader: InfoReader) ad noEagerConst let countOfCallerLambdaArg = InferLambdaArgsForLambdaPropagation argExpr // Adjust for Expression<_>, Func<_, _>, ... - let (AdjustedRequiredTypeInfo(adjustedCalledArgTy, _, _)) = + let (TypeAdjustmentInfo(adjustedCalledArgTy, _, _)) = AdjustCalledArgType infoReader ad false false arg.CalledArg arg.CallerArg if countOfCallerLambdaArg > 0 then diff --git a/src/fsharp/MethodCalls.fsi b/src/fsharp/MethodCalls.fsi index a528d54f954..0670b0da463 100644 --- a/src/fsharp/MethodCalls.fsi +++ b/src/fsharp/MethodCalls.fsi @@ -121,12 +121,6 @@ type ConversionUsed = static member Combine: ConversionUsed -> ConversionUsed -> ConversionUsed -type AdjustedRequiredTypeInfo = - | AdjustedRequiredTypeInfo of - adjustedRequiredTy: TType * - tdcInfo: ConversionUsed * - eqnInfo: (TType * TType * (DisplayEnv -> unit)) option - /// Performs a set of constraint solver operations returning ConversionUsed and /// combines their results. val MapCombineTDCD: mapper:('T -> OperationResult) -> xs:'T list -> OperationResult @@ -135,6 +129,12 @@ val MapCombineTDCD: mapper:('T -> OperationResult) -> xs:'T list /// combines their results. val MapCombineTDC2D: mapper:('T -> 'b -> OperationResult) -> xs:'T list -> ys:'b list -> OperationResult +type TypeAdjustmentInfo = + | TypeAdjustmentInfo of + adjustedRequiredTy: TType * + conversionInfo: ConversionUsed * + eqnInfo: (TType * TType * (DisplayEnv -> unit)) option + /// F# supports some adhoc conversions to make expression fit known overall type val AdjustRequiredTypeForTypeDirectedConversions: infoReader:InfoReader -> @@ -144,7 +144,7 @@ val AdjustRequiredTypeForTypeDirectedConversions: reqdTy: TType -> actualTy:TType -> m: range - -> AdjustedRequiredTypeInfo + -> TypeAdjustmentInfo /// F# supports some adhoc conversions to make expression fit known overall type val AdjustCalledArgType: @@ -154,7 +154,7 @@ val AdjustCalledArgType: enforceNullableOptionalsKnownTypes:bool -> calledArg:CalledArg -> callerArg:CallerArg<'T> - -> AdjustedRequiredTypeInfo + -> TypeAdjustmentInfo type CalledMethArgSet<'T> = { /// The called arguments corresponding to "unnamed" arguments From 69ac91ca54833589233724279219050c32331f61 Mon Sep 17 00:00:00 2001 From: Don Syme Date: Thu, 28 Apr 2022 23:33:36 +1000 Subject: [PATCH 3/3] Initial fix for https://github.com/dotnet/fsharp/issues/12860 --- src/fsharp/CheckExpressions.fs | 2 +- src/fsharp/ConstraintSolver.fs | 38 +++++++++++++++++----------------- src/fsharp/MethodCalls.fs | 18 ++++++++-------- src/fsharp/MethodCalls.fsi | 14 ++++++------- 4 files changed, 36 insertions(+), 36 deletions(-) diff --git a/src/fsharp/CheckExpressions.fs b/src/fsharp/CheckExpressions.fs index e83c343ad6d..f28e2cc65b6 100644 --- a/src/fsharp/CheckExpressions.fs +++ b/src/fsharp/CheckExpressions.fs @@ -494,7 +494,7 @@ let UnifyOverallType cenv (env: TcEnv) m overallTy actualTy = | None -> () match usesTDC with - | ConversionUsed.TypeDirected warn -> warning(warn env.DisplayEnv) + | ConversionInfo.TypeDirected warn -> warning(warn env.DisplayEnv) | _ -> () if AddCxTypeMustSubsumeTypeUndoIfFailed env.DisplayEnv cenv.css m reqdTy2 actualTy then diff --git a/src/fsharp/ConstraintSolver.fs b/src/fsharp/ConstraintSolver.fs index 835a269388e..af5e317555b 100644 --- a/src/fsharp/ConstraintSolver.fs +++ b/src/fsharp/ConstraintSolver.fs @@ -2407,15 +2407,15 @@ and CanMemberSigsMatchUpToCheck alwaysCheckReturn // Used to equate the formal method instantiation with the actual method instantiation // for a generic method, and the return types - (unifyTypes: TType -> TType -> OperationResult) + (unifyTypes: TType -> TType -> OperationResult) // Used to compare the "obj" type - (subsumeTypes: TType -> TType -> OperationResult) + (subsumeTypes: TType -> TType -> OperationResult) // Used to convert the "return" for MustConvertTo - (subsumeOrConvertTypes: bool -> TType -> TType -> OperationResult) + (subsumeOrConvertTypes: bool -> TType -> TType -> OperationResult) // Used to convert the arguments - (subsumeOrConvertArg: CalledArg -> CallerArg<_> -> OperationResult) + (subsumeOrConvertArg: CalledArg -> CallerArg<_> -> OperationResult) (reqdRetTyOpt: OverallTy option) - (calledMeth: CalledMeth<_>): OperationResult = + (calledMeth: CalledMeth<_>): OperationResult = trackErrors { let g = csenv.g let amap = csenv.amap @@ -2473,10 +2473,10 @@ and CanMemberSigsMatchUpToCheck ) - | _ -> ResultD ConversionUsed.NoneOrOther + | _ -> ResultD ConversionInfo.NoneOrOther else - ResultD ConversionUsed.NoneOrOther - | _ -> ResultD ConversionUsed.NoneOrOther + ResultD ConversionInfo.NoneOrOther + | _ -> ResultD ConversionInfo.NoneOrOther let! usesTDC5 = calledMeth.ArgSets |> MapCombineTDCD (fun argSet -> @@ -2511,7 +2511,7 @@ and CanMemberSigsMatchUpToCheck let! usesTDC7 = match reqdRetTyOpt with | Some _ when ( (* minfo.IsConstructor || *) not alwaysCheckReturn && isNil unnamedCalledOutArgs) -> - ResultD ConversionUsed.NoneOrOther + ResultD ConversionInfo.NoneOrOther | Some (MustConvertTo(isMethodArg, reqdTy)) when g.langVersion.SupportsFeature LanguageFeature.AdditionalTypeDirectedConversions -> let methodRetTy = calledMeth.CalledReturnTypeAfterOutArgTupling subsumeOrConvertTypes isMethodArg reqdTy methodRetTy @@ -2519,8 +2519,8 @@ and CanMemberSigsMatchUpToCheck let methodRetTy = calledMeth.CalledReturnTypeAfterOutArgTupling unifyTypes reqdRetTy.Commit methodRetTy | _ -> - ResultD ConversionUsed.NoneOrOther - return Array.reduce ConversionUsed.Combine [| usesTDC1; usesTDC2; usesTDC3; usesTDC4; usesTDC5; usesTDC6; usesTDC7 |] + ResultD ConversionInfo.NoneOrOther + return Array.reduce ConversionInfo.Combine [| usesTDC1; usesTDC2; usesTDC3; usesTDC4; usesTDC5; usesTDC6; usesTDC7 |] } // Wrap an ErrorsFromAddingSubsumptionConstraint error around any failure @@ -2593,7 +2593,7 @@ and ArgsMustSubsumeOrConvert | None -> () match usesTDC with - | ConversionUsed.TypeDirected warn -> do! WarnD(warn csenv.DisplayEnv) + | ConversionInfo.TypeDirected warn -> do! WarnD(warn csenv.DisplayEnv) | _ -> () do! SolveTypeSubsumesTypeWithReport csenv ndeep m trace cxsln calledArgTy callerArg.CallerArgumentType @@ -2629,7 +2629,7 @@ and ArgsMustSubsumeOrConvertWithContextualReport | None -> () match usesTDC with - | ConversionUsed.TypeDirected warn -> do! WarnD(warn csenv.DisplayEnv) + | ConversionInfo.TypeDirected warn -> do! WarnD(warn csenv.DisplayEnv) | _ -> () do! SolveTypeSubsumesTypeWithWrappedContextualReport csenv ndeep m trace cxsln calledArgTy callerArgTy (fun e -> ArgDoesNotMatchError(e :?> _, calledMeth, calledArg, callerArg)) @@ -2640,13 +2640,13 @@ and ArgsMustSubsumeOrConvertWithContextualReport and TypesEquiv csenv ndeep trace cxsln ty1 ty2 = trackErrors { do! SolveTypeEqualsTypeWithReport csenv ndeep csenv.m trace cxsln ty1 ty2 - return ConversionUsed.NoneOrOther + return ConversionInfo.NoneOrOther } and TypesMustSubsume (csenv: ConstraintSolverEnv) ndeep trace cxsln m calledArgTy callerArgTy = trackErrors { do! SolveTypeSubsumesTypeWithReport csenv ndeep m trace cxsln calledArgTy callerArgTy - return ConversionUsed.NoneOrOther + return ConversionInfo.NoneOrOther } and ReturnTypesMustSubsumeOrConvert (csenv: ConstraintSolverEnv) ad ndeep trace cxsln isConstraint m isMethodArg reqdTy actualTy = @@ -2661,7 +2661,7 @@ and ReturnTypesMustSubsumeOrConvert (csenv: ConstraintSolverEnv) ad ndeep trace | None -> () match usesTDC with - | ConversionUsed.TypeDirected warn -> do! WarnD(warn csenv.DisplayEnv) + | ConversionInfo.TypeDirected warn -> do! WarnD(warn csenv.DisplayEnv) | _ -> () do! SolveTypeSubsumesTypeWithReport csenv ndeep m trace cxsln reqdTy actualTy @@ -2683,7 +2683,7 @@ and ArgsEquivOrConvert (csenv: ConstraintSolverEnv) ad ndeep trace cxsln isConst | None -> () match usesTDC with - | ConversionUsed.TypeDirected warn -> do! WarnD(warn csenv.DisplayEnv) + | ConversionInfo.TypeDirected warn -> do! WarnD(warn csenv.DisplayEnv) | _ -> () if not (typeEquiv csenv.g calledArgTy callerArgTy) then @@ -3026,7 +3026,7 @@ and ResolveOverloading let otherWarnCount = List.length otherWarnings // Prefer methods that don't use type-directed conversion - let c = compare (match usesTDC1 with ConversionUsed.TypeDirected _ -> 0 | _ -> 1) (match usesTDC2 with ConversionUsed.TypeDirected _ -> 0 | _ -> 1) + let c = compare (match usesTDC1 with ConversionInfo.TypeDirected _ -> 0 | _ -> 1) (match usesTDC2 with ConversionInfo.TypeDirected _ -> 0 | _ -> 1) if c <> 0 then c else // Prefer methods that don't give "this code is less generic" warnings @@ -3130,7 +3130,7 @@ and ResolveOverloading if c <> 0 then c else // Prefer methods that don't use either nullable adjustment or type-directed conversion - let c = compare (match usesTDC1 with ConversionUsed.NoneOrOther -> 1 | _ -> 0) (match usesTDC2 with ConversionUsed.NoneOrOther -> 1 | _ -> 0) + let c = compare (match usesTDC1 with ConversionInfo.NoneOrOther -> 1 | _ -> 0) (match usesTDC2 with ConversionInfo.NoneOrOther -> 1 | _ -> 0) if c <> 0 then c else 0 diff --git a/src/fsharp/MethodCalls.fs b/src/fsharp/MethodCalls.fs index 0919d06ab90..6f05a1729c2 100644 --- a/src/fsharp/MethodCalls.fs +++ b/src/fsharp/MethodCalls.fs @@ -230,7 +230,7 @@ type TypeDirectedConversion = | Implicit of MethInfo [] -type ConversionUsed = +type ConversionInfo = | TypeDirected of (DisplayEnv -> exn) | NullableAdjustment | NoneOrOther @@ -244,31 +244,31 @@ type ConversionUsed = | NoneOrOther, NoneOrOther -> a member tdc.WithNullableAdjustment() = - ConversionUsed.Combine tdc ConversionUsed.NullableAdjustment + ConversionInfo.Combine tdc ConversionInfo.NullableAdjustment type TypeAdjustmentInfo = | TypeAdjustmentInfo of adjustedRequiredTy: TType * - conversionInfo: ConversionUsed * + conversionInfo: ConversionInfo * eqnInfo: (TType * TType * (DisplayEnv -> unit)) option - static member Unadjusted reqdTy = TypeAdjustmentInfo(reqdTy, ConversionUsed.NoneOrOther, None) + static member Unadjusted reqdTy = TypeAdjustmentInfo(reqdTy, ConversionInfo.NoneOrOther, None) - static member Adjusted reqdTy = TypeAdjustmentInfo(reqdTy, ConversionUsed.NoneOrOther, None) + static member Adjusted reqdTy = TypeAdjustmentInfo(reqdTy, ConversionInfo.NoneOrOther, None) - static member BuiltInTypeDirectedConversion warn reqdTy = TypeAdjustmentInfo(reqdTy, ConversionUsed.TypeDirected(warn TypeDirectedConversion.BuiltIn), None) + static member BuiltInTypeDirectedConversion warn reqdTy = TypeAdjustmentInfo(reqdTy, ConversionInfo.TypeDirected(warn TypeDirectedConversion.BuiltIn), None) - static member ImplictTypeDirectedConversion warn reqdTy minfo eqn = TypeAdjustmentInfo(reqdTy, ConversionUsed.TypeDirected(warn (TypeDirectedConversion.Implicit minfo)), Some eqn) + static member ImplictTypeDirectedConversion warn reqdTy minfo eqn = TypeAdjustmentInfo(reqdTy, ConversionInfo.TypeDirected(warn (TypeDirectedConversion.Implicit minfo)), Some eqn) member adj.WithNullableAdjustment() = let (TypeAdjustmentInfo(adjustedTy, tdc, info)) = adj TypeAdjustmentInfo(adjustedTy, tdc.WithNullableAdjustment(), info) let MapCombineTDCD mapper xs = - MapReduceD mapper ConversionUsed.NoneOrOther ConversionUsed.Combine xs + MapReduceD mapper ConversionInfo.NoneOrOther ConversionInfo.Combine xs let MapCombineTDC2D mapper xs ys = - MapReduce2D mapper ConversionUsed.NoneOrOther ConversionUsed.Combine xs ys + MapReduce2D mapper ConversionInfo.NoneOrOther ConversionInfo.Combine xs ys let rec AdjustRequiredTypeForTypeDirectedConversions (infoReader: InfoReader) ad isMethodArg isConstraint (reqdTy: TType) actualTy m = let g = infoReader.g diff --git a/src/fsharp/MethodCalls.fsi b/src/fsharp/MethodCalls.fsi index 0670b0da463..8b88ec86cb0 100644 --- a/src/fsharp/MethodCalls.fsi +++ b/src/fsharp/MethodCalls.fsi @@ -107,7 +107,7 @@ type CallerArgs<'T> = /// Indicates whether a conversion (e.g. int32 to int64, or op_Implicit) /// has been used in F# code, particularly for method arguments. [] -type ConversionUsed = +type ConversionInfo = /// Indicates a type-directed conversion, added in F# 6.0. | TypeDirected of (DisplayEnv -> exn) @@ -119,20 +119,20 @@ type ConversionUsed = /// Indicates either some other conversion that is not relevant to overload resolution logic, or no conversion at all | NoneOrOther - static member Combine: ConversionUsed -> ConversionUsed -> ConversionUsed + static member Combine: ConversionInfo -> ConversionInfo -> ConversionInfo -/// Performs a set of constraint solver operations returning ConversionUsed and +/// Performs a set of constraint solver operations returning ConversionInfo and /// combines their results. -val MapCombineTDCD: mapper:('T -> OperationResult) -> xs:'T list -> OperationResult +val MapCombineTDCD: mapper:('T -> OperationResult) -> xs:'T list -> OperationResult -/// Performs a set of constraint solver operations returning ConversionUsed and +/// Performs a set of constraint solver operations returning ConversionInfo and /// combines their results. -val MapCombineTDC2D: mapper:('T -> 'b -> OperationResult) -> xs:'T list -> ys:'b list -> OperationResult +val MapCombineTDC2D: mapper:('T -> 'b -> OperationResult) -> xs:'T list -> ys:'b list -> OperationResult type TypeAdjustmentInfo = | TypeAdjustmentInfo of adjustedRequiredTy: TType * - conversionInfo: ConversionUsed * + conversionInfo: ConversionInfo * eqnInfo: (TType * TType * (DisplayEnv -> unit)) option /// F# supports some adhoc conversions to make expression fit known overall type