diff --git a/docs/release-notes/.FSharp.Compiler.Service/10.0.300.md b/docs/release-notes/.FSharp.Compiler.Service/10.0.300.md index 085f73b09d0..fae2577ce34 100644 --- a/docs/release-notes/.FSharp.Compiler.Service/10.0.300.md +++ b/docs/release-notes/.FSharp.Compiler.Service/10.0.300.md @@ -6,6 +6,9 @@ ### Added +* Added "Most Concrete" tiebreaker for overload resolution (`--langversion:preview`). ([PR #19277](https://github.com/dotnet/fsharp/pull/19277)) +* Added support for `OverloadResolutionPriorityAttribute` in overload resolution (`--langversion:preview`). ([PR #19277](https://github.com/dotnet/fsharp/pull/19277)) + ### Changed * Centralized product TFM (Target Framework Moniker) into MSBuild props file `eng/TargetFrameworks.props`. Changing the target framework now only requires editing one file, and it integrates with MSBuild's `--getProperty` for scripts. diff --git a/docs/release-notes/.Language/preview.md b/docs/release-notes/.Language/preview.md index 62dd231d664..b34ecc3f9a5 100644 --- a/docs/release-notes/.Language/preview.md +++ b/docs/release-notes/.Language/preview.md @@ -1,5 +1,7 @@ ### Added +* Added "Most Concrete" tiebreaker for overload resolution. When multiple method overloads match, the overload with more concrete type parameters wins. Requires `--langversion:preview`. ([PR #19277](https://github.com/dotnet/fsharp/pull/19277)) +* Added support for `System.Runtime.CompilerServices.OverloadResolutionPriorityAttribute` (.NET 9). Methods with higher priority values are preferred during overload resolution, matching C# behavior. Requires `--langversion:preview`. ([PR #19277](https://github.com/dotnet/fsharp/pull/19277)) ### Fixed ### Changed \ No newline at end of file diff --git a/docs/rfcs/FS-XXXX-most-concrete-tiebreaker.md b/docs/rfcs/FS-XXXX-most-concrete-tiebreaker.md new file mode 100644 index 00000000000..666fc1380d9 --- /dev/null +++ b/docs/rfcs/FS-XXXX-most-concrete-tiebreaker.md @@ -0,0 +1,167 @@ +# F# RFC FS-XXXX - "Most Concrete" Tiebreaker for Overload Resolution + +The design suggestion [\"Most concrete\" tiebreaker for generic overloads](https://github.com/fsharp/fslang-suggestions/issues/905) has been marked "approved in principle". + +This RFC covers the detailed proposal for this suggestion. + +- [x] [Suggestion](https://github.com/fsharp/fslang-suggestions/issues/905) +- [ ] Approved in principle +- [ ] [Implementation](https://github.com/dotnet/fsharp/pull/19277) +- [ ] Discussion + +# Summary + +This RFC introduces a new tiebreaker rule for F# overload resolution that prefers "more concrete" overloads when choosing between methods with different levels of type specificity. Currently, F# emits `FS0041` ambiguity errors in cases where one overload is clearly more specific than another (e.g., `Option` vs `Option<'t>`), even when the argument types are fully known. This change aligns F# with C#'s overload resolution behavior and eliminates the need for workarounds in common scenarios. + +## Motivation + +### ValueTask Constructor — Real BCL Pain Point + +The .NET `ValueTask<'T>` struct has constructors for both direct values and tasks: + +```fsharp +open System.Threading.Tasks + +// ValueTask(result: 'T) vs ValueTask(task: Task<'T>) +let task = Task.FromResult(42) +let vt = ValueTask(task) +// Current: FS0041 or requires named parameter: ValueTask(task = task) +// Proposed: Resolves automatically — Task is more concrete than 'T +``` + +This pattern affects real code: users must write `ValueTask(task = someTask)` to disambiguate, adding friction that C# users never experience. The same issue impacts: + +- **TaskBuilder.fs**: Uses priority marker types to force resolution +- **FsToolkit.ErrorHandling**: Splits extensions across modules for import ordering +- **.NET BCL**: Many generic vs. concrete overload patterns + +### Basic Example + +```fsharp +type Example = + static member Invoke(value: Option<'t>) = "generic" + static member Invoke(value: Option) = "concrete" + +// Current: Error FS0041 — Proposed: Resolves to Option overload +let result = Example.Invoke(Some([1])) +``` + +## Algorithm Overview + +The algorithm introduces a partial order on types based on "concreteness level." Fully instantiated types (like `int`, `Option`) are more concrete than type variables (`'t`). Generic type applications inherit the minimum concreteness of their type arguments. When comparing two overloads, if one is more concrete in at least one type argument position and not less concrete in any other position (the "dominance rule"), it is preferred. This ensures only cases with a clear winner are resolved—truly ambiguous cases like `Result` vs `Result<'t,string>` remain errors because each is more concrete in a different position. + +## Specification Diff + +Changes to F# Language Specification §14.4 (Method Application Resolution), Step 7: + +```diff + 7. Apply the following rules, in order, until a unique better method M is determined: + 1. Prefer candidates that don't constrain user type annotations + 2. Prefer candidates without ParamArray conversion + 3. Prefer candidates without implicitly supplied arguments + 4. Prefer candidates whose types feasibly subsume competitors + 5. Prefer non-extension methods over extension methods + 6. Prefer more recently opened extension methods + 7. Prefer candidates with explicit argument count match + 8. Prefer non-generic candidates over generic candidates ++ 9. Prefer candidates with more concrete type instantiations. ++ Given two generic candidates where both have non-empty type arguments, ++ prefer the candidate whose parameter types are more concrete as defined ++ by the dominance rule: a type dominates another if it is at least as ++ concrete at every position and strictly more concrete at one or more. +- Report an error if steps 1 through 8 do not result in selection of a +- unique better method. ++ Report an error if steps 1 through 9 do not result in selection of a ++ unique better method. +``` + +### Type Concreteness Comparison + +| Type Form | Concreteness | +|-----------|--------------| +| Concrete types (`int`, `string`) | Highest | +| Generic applications (`Option`) | Inherits from arguments | +| Type variables (`'t`) | Lowest | + +Two types are comparable only if they have the same structural form (same type constructor with same arity). `Option` and `List` are incomparable regardless of concreteness. + +## Diagnostics + +| Code | Message | Default | +|------|---------|---------| +| FS3575 | "Overload resolution selected '%s' based on type concreteness. The more concrete type '%s' was preferred over '%s'. This is an informational message and can be enabled with --warnon:3575." | Off | +| FS3576 | "A more generic overload was bypassed: '%s'. The selected overload '%s' was chosen because it has more concrete type parameters." | Off | + +Enable with `--warnon:3575` or `--warnon:3576` to audit resolution decisions during development. + +### Enhanced Ambiguity Errors + +When the tiebreaker cannot resolve (incomparable types), FS0041 is enhanced: + +``` +error FS0041: A unique overload for method 'Invoke' could not be determined. +Neither candidate is strictly more concrete than the other: + - Invoke(x: Result) is more concrete at position 1 + - Invoke(x: Result<'t, string>) is more concrete at position 2 +``` + +## Compatibility + +**Non-breaking change.** The tiebreaker only applies when: +1. Multiple overloads remain after all existing tiebreakers +2. Current behavior would produce an `FS0041` ambiguity error + +| Aspect | Impact | +|--------|--------| +| Existing code | Compiles identically | +| Previous FS0041 errors | May now compile successfully | +| Binary/IL | No change | +| Feature gate | F# 10.0 / `LangVersion preview` | + +### Portability + +```fsharp +// Works on new compiler: +let result = Api.Call(Some 42) + +// Portable to all versions (add type annotation): +let result = Api.Call(Some 42 : Option) +``` + +## C# Alignment + +This change brings F# closer to C#'s "better function member" rules (ECMA-334 §12.6.4). In C#, after type inference, a generic method with inferred concrete types is compared as if it were a concrete overload. The F# tiebreaker produces the same resolution as C# in common cases, improving interoperability with .NET libraries that rely on overloading patterns. + +## Drawbacks + +- **Silent behavior change**: Code that previously failed with `FS0041` will now compile. Developers who relied on this error as a guardrail forcing explicit annotations may find overload selection happens implicitly. + +- **Adding generic overloads can change resolution**: When a library adds a new, more generic overload, existing call sites may switch to different (now "more concrete" by comparison) overloads. + +- **Learning curve for partial order semantics**: Developers must understand why `Result` vs `Result<'t,string>` remains ambiguous (neither dominates). The dominance rule is mathematically clean but may require explanation. + +# Alternatives + +1. **Do nothing**: Continue requiring explicit type annotations or named arguments for disambiguation. This is the status quo but creates friction, especially when consuming .NET libraries designed with C#'s resolution rules in mind. + +2. **Full C# semantics adoption**: Implement all of C#'s "better function member" rules. This would be a larger change with more risk of breaking existing F# code. The tiebreaker approach is more conservative. + +3. **Attribute-based explicit priority**: Allow library authors to mark overloads with explicit priority (see related RFC for `OverloadResolutionPriorityAttribute`). This is complementary—explicit priority could override implicit concreteness when needed. + +# Prior Art + +- **C# "better function member"** (ECMA-334 §12.6.4): C# prefers more specific overloads after type inference. Our tiebreaker aligns with this for the common cases. + +- **Scala overload resolution**: Scala has similar specificity rules preferring more specific signatures. + +- **Haskell type class resolution**: Uses specificity ordering for instance selection, though the mechanism is different. + +# SRTP Exclusion + +Methods involving statically resolved type parameters (`^T`) are **entirely excluded** from concreteness comparison. If either candidate has SRTP type parameters, SRTP type arguments, or parameter types containing SRTP type variables, the tiebreaker returns 0 (no preference) and defers to existing resolution rules. SRTP uses constraint solving, not type-parameter specificity, and mixing the two would produce incorrect results. + +# Unresolved Questions + +1. **Interaction with OverloadResolutionPriorityAttribute**: When ORPA removes candidates before type-checking, the surviving candidates may have different concreteness relationships than the original set. Should the tiebreaker's concreteness warnings account for ORPA-filtered candidates? + +2. **Rule ordering relative to NullableOptionalInterop**: The concreteness tiebreaker fires before the F# 5.0 NullableOptionalInterop rule (which compares all args including optional/named). A case where concreteness decides before nullable interop gets a chance could produce surprising results for `Nullable` overloads. diff --git a/src/Compiler/Checking/ConstraintSolver.fs b/src/Compiler/Checking/ConstraintSolver.fs index 529c90d6b03..65330031b94 100644 --- a/src/Compiler/Checking/ConstraintSolver.fs +++ b/src/Compiler/Checking/ConstraintSolver.fs @@ -69,6 +69,7 @@ open FSharp.Compiler.TypedTreeBasics open FSharp.Compiler.TypedTreeOps open FSharp.Compiler.TypeHierarchy open FSharp.Compiler.TypeRelations +open FSharp.Compiler.OverloadResolutionRules #if !NO_TYPEPROVIDERS open FSharp.Compiler.TypeProviders @@ -197,7 +198,8 @@ type OverloadResolutionFailure = | PossibleCandidates of methodName: string * candidates: OverloadInformation list * - cx: TraitConstraintInfo option + cx: TraitConstraintInfo option * + incomparableConcreteness: OverloadResolutionRules.IncomparableConcretenessInfo option type OverallTy = /// Each branch of the expression must have the type indicated @@ -3469,7 +3471,10 @@ and ResolveOverloading (methodName = "op_Implicit") // See what candidates we have based on name and arity - let candidates = calledMethGroup |> List.filter (fun cmeth -> cmeth.IsCandidate(m, ad)) + let candidates = + calledMethGroup + |> List.filter (fun cmeth -> cmeth.IsCandidate(m, ad)) + |> filterByOverloadResolutionPriority g (fun cm -> cm.Method) let calledMethOpt, errors, calledMethTrace = match calledMethGroup, candidates with @@ -3577,7 +3582,7 @@ and ResolveOverloading None, ErrorD err, NoTrace | [(calledMeth, warns, t, _usesTDC)] -> - Some calledMeth, OkResult (warns, ()), WithTrace t + Some calledMeth, OkResult(warns, ()), WithTrace t | applicableMeths -> GetMostApplicableOverload csenv ndeep candidates applicableMeths calledMethGroup reqdRetTyOpt isOpConversion callerArgs methodName cx m @@ -3656,178 +3661,66 @@ and FailOverloading csenv calledMethGroup reqdRetTyOpt isOpConversion callerArgs // Otherwise pass the overload resolution failure for error printing in CompileOps UnresolvedOverloading (denv, callerArgs, overloadResolutionFailure, m) -and GetMostApplicableOverload csenv ndeep candidates applicableMeths calledMethGroup reqdRetTyOpt isOpConversion callerArgs methodName cx m = - let g = csenv.g - let infoReader = csenv.InfoReader - /// Compare two things by the given predicate. - /// If the predicate returns true for x1 and false for x2, then x1 > x2 - /// If the predicate returns false for x1 and true for x2, then x1 < x2 - /// Otherwise x1 = x2 - - // Note: Relies on 'compare' respecting true > false - let compareCond (p: 'T -> 'T -> bool) x1 x2 = - compare (p x1 x2) (p x2 x1) - - /// Compare types under the feasibly-subsumes ordering - let compareTypes ty1 ty2 = - (ty1, ty2) ||> compareCond (fun x1 x2 -> TypeFeasiblySubsumesType ndeep csenv.g csenv.amap m x2 CanCoerce x1) - - /// Compare arguments under the feasibly-subsumes ordering and the adhoc Func-is-better-than-other-delegates rule - let compareArg (calledArg1: CalledArg) (calledArg2: CalledArg) = - let c = compareTypes calledArg1.CalledArgumentType calledArg2.CalledArgumentType - if c <> 0 then c else - - let c = - (calledArg1.CalledArgumentType, calledArg2.CalledArgumentType) ||> compareCond (fun ty1 ty2 -> - - // Func<_> is always considered better than any other delegate type - match tryTcrefOfAppTy csenv.g ty1 with - | ValueSome tcref1 when - tcref1.DisplayName = "Func" && - (match tcref1.PublicPath with Some p -> p.EnclosingPath = [| "System" |] | _ -> false) && - isDelegateTy g ty1 && - isDelegateTy g ty2 -> true +and private computeConcretenessWarnings + (cache: System.Collections.Generic.Dictionary) + (applicableMeths: (CalledMeth * exn list * Trace * TypeDirectedConversionUsed) list) + (calledMeth: CalledMeth) + (baseWarns: exn list) + (m: range) + : exn list = + let anyMoreConcreteUsed = + cache.Values + |> Seq.exists (fun v -> match v with ValueSome TiebreakRuleId.MoreConcrete -> true | _ -> false) + + if not anyMoreConcreteUsed then + baseWarns + else + let concretenessWarns = + applicableMeths + |> List.choose (fun loser -> + let (loserMeth, _, _, _) = loser - // T is always better than inref - | _ when isInByrefTy csenv.g ty2 && typeEquiv csenv.g ty1 (destByrefTy csenv.g ty2) -> - true + if System.Object.ReferenceEquals(loserMeth, calledMeth) then + None + else + match cache.TryGetValue(struct(calledMeth :> obj, loserMeth :> obj)) with + | true, ValueSome TiebreakRuleId.MoreConcrete -> + Some(calledMeth.Method.DisplayName, loserMeth.Method.DisplayName) + | _ -> None) + + match concretenessWarns with + | [] -> baseWarns + | (winnerName, loserName) :: _ -> + let warn3575 = + Error(FSComp.SR.tcMoreConcreteTiebreakerUsed (winnerName, winnerName, loserName), m) + let warn3576List = + concretenessWarns + |> List.map (fun (winner, loser) -> Error(FSComp.SR.tcGenericOverloadBypassed (loser, winner), m)) + + warn3575 :: warn3576List @ baseWarns - // T is always better than Nullable from F# 5.0 onwards - | _ when g.langVersion.SupportsFeature(LanguageFeature.NullableOptionalInterop) && - isNullableTy csenv.g ty2 && - typeEquiv csenv.g ty1 (destNullableTy csenv.g ty2) -> - true +and GetMostApplicableOverload csenv ndeep candidates applicableMeths calledMethGroup reqdRetTyOpt isOpConversion callerArgs methodName cx m = + let infoReader = csenv.InfoReader + let moreConcretEnabled = csenv.g.langVersion.SupportsFeature LanguageFeature.MoreConcreteTiebreaker - | _ -> false) + let ctx: OverloadResolutionContext = + { g = csenv.g; amap = csenv.amap; m = m; ndeep = ndeep + paramDataCache = (if moreConcretEnabled then ValueSome(System.Collections.Generic.Dictionary()) else ValueNone) + srtpCache = (if moreConcretEnabled then ValueSome(System.Collections.Generic.Dictionary()) else ValueNone) } - if c <> 0 then c else - 0 + let decidingRuleCache = + if moreConcretEnabled then ValueSome(System.Collections.Generic.Dictionary()) + else ValueNone /// Check whether one overload is better than another - let better (candidate: CalledMeth<_>, candidateWarnings, _, usesTDC1) (other: CalledMeth<_>, otherWarnings, _, usesTDC2) = - let candidateWarnCount = List.length candidateWarnings - 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) - if c <> 0 then c else - - // Prefer methods that need less type-directed conversion - let c = compare (match usesTDC1 with TypeDirectedConversionUsed.Yes(_, false, _) -> 1 | _ -> 0) (match usesTDC2 with TypeDirectedConversionUsed.Yes(_, false, _) -> 1 | _ -> 0) - if c <> 0 then c else - - // Prefer methods that only have nullable type-directed conversions - let c = compare (match usesTDC1 with TypeDirectedConversionUsed.Yes(_, _, true) -> 1 | _ -> 0) (match usesTDC2 with TypeDirectedConversionUsed.Yes(_, _, true) -> 1 | _ -> 0) - if c <> 0 then c else - - // Prefer methods that don't give "this code is less generic" warnings - // Note: Relies on 'compare' respecting true > false - let c = compare (candidateWarnCount = 0) (otherWarnCount = 0) - if c <> 0 then c else - - // Prefer methods that don't use param array arg - // Note: Relies on 'compare' respecting true > false - let c = compare (not candidate.UsesParamArrayConversion) (not other.UsesParamArrayConversion) - if c <> 0 then c else - - // Prefer methods with more precise param array arg type - let c = - if candidate.UsesParamArrayConversion && other.UsesParamArrayConversion then - compareTypes (candidate.GetParamArrayElementType()) (other.GetParamArrayElementType()) - else - 0 - if c <> 0 then c else - - // Prefer methods that don't use out args - // Note: Relies on 'compare' respecting true > false - let c = compare (not candidate.HasOutArgs) (not other.HasOutArgs) - if c <> 0 then c else - - // Prefer methods that don't use optional args - // Note: Relies on 'compare' respecting true > false - let c = compare (not candidate.HasOptionalArgs) (not other.HasOptionalArgs) - if c <> 0 then c else - - // check regular unnamed args. The argument counts will only be different if one is using param args - let c = - if candidate.TotalNumUnnamedCalledArgs = other.TotalNumUnnamedCalledArgs then - // For extension members, we also include the object argument type, if any in the comparison set - // This matches C#, where all extension members are treated and resolved as "static" methods calls - let cs = - (if candidate.Method.IsExtensionMember && other.Method.IsExtensionMember then - let objArgTys1 = candidate.CalledObjArgTys(m) - let objArgTys2 = other.CalledObjArgTys(m) - if objArgTys1.Length = objArgTys2.Length then - List.map2 compareTypes objArgTys1 objArgTys2 - else - [] - else - []) @ - ((candidate.AllUnnamedCalledArgs, other.AllUnnamedCalledArgs) ||> List.map2 compareArg) - // "all args are at least as good, and one argument is actually better" - if cs |> List.forall (fun x -> x >= 0) && cs |> List.exists (fun x -> x > 0) then - 1 - // "all args are at least as bad, and one argument is actually worse" - elif cs |> List.forall (fun x -> x <= 0) && cs |> List.exists (fun x -> x < 0) then - -1 - // "argument lists are incomparable" - else - 0 - else - 0 - if c <> 0 then c else - - // prefer non-extension methods - let c = compare (not candidate.Method.IsExtensionMember) (not other.Method.IsExtensionMember) - if c <> 0 then c else - - // between extension methods, prefer most recently opened - let c = - if candidate.Method.IsExtensionMember && other.Method.IsExtensionMember then - compare candidate.Method.ExtensionMemberPriority other.Method.ExtensionMemberPriority - else - 0 - if c <> 0 then c else - - // Prefer non-generic methods - // Note: Relies on 'compare' respecting true > false - let c = compare candidate.CalledTyArgs.IsEmpty other.CalledTyArgs.IsEmpty - if c <> 0 then c else - - // F# 5.0 rule - prior to F# 5.0 named arguments (on the caller side) were not being taken - // into account when comparing overloads. So adding a name to an argument might mean - // overloads could no longer be distinguished. We thus look at *all* arguments (whether - // optional or not) as an additional comparison technique. - let c = - if g.langVersion.SupportsFeature(LanguageFeature.NullableOptionalInterop) then - let cs = - let args1 = candidate.AllCalledArgs |> List.concat - let args2 = other.AllCalledArgs |> List.concat - if args1.Length = args2.Length then - (args1, args2) ||> List.map2 compareArg - else - [] - // "all args are at least as good, and one argument is actually better" - if cs |> List.forall (fun x -> x >= 0) && cs |> List.exists (fun x -> x > 0) then - 1 - // "all args are at least as bad, and one argument is actually worse" - elif cs |> List.forall (fun x -> x <= 0) && cs |> List.exists (fun x -> x < 0) then - -1 - // "argument lists are incomparable" - else - 0 - else - 0 - if c <> 0 then c else - - // Properties are kept incl. almost-duplicates because of the partial-override possibility. - // E.g. base can have get,set and derived only get => we keep both props around until method resolution time. - // Now is the type to pick the better (more derived) one. - match candidate.AssociatedPropertyInfo,other.AssociatedPropertyInfo,candidate.Method.IsExtensionMember,other.Method.IsExtensionMember with - | Some p1, Some p2, false, false -> compareTypes p1.ApparentEnclosingType p2.ApparentEnclosingType - | _ -> 0 - - + let better (candidate: CalledMeth<_>, candidateWarnings: _ list, _, usesTDC1) (other: CalledMeth<_>, otherWarnings: _ list, _, usesTDC2) = + let struct (result, decidingRule) = findDecidingRule ctx (struct (candidate, usesTDC1, candidateWarnings.Length)) (struct (other, usesTDC2, otherWarnings.Length)) + if moreConcretEnabled then + match decidingRuleCache with + | ValueSome cache -> cache[struct(candidate :> obj, other :> obj)] <- decidingRule + | ValueNone -> () + result + let bestMethods = let indexedApplicableMeths = applicableMeths |> List.indexed indexedApplicableMeths |> List.choose (fun (i, candidate) -> @@ -3841,7 +3734,12 @@ and GetMostApplicableOverload csenv ndeep candidates applicableMeths calledMethG match bestMethods with | [(calledMeth, warns, t, _)] -> - Some calledMeth, OkResult (warns, ()), WithTrace t + let allWarns = + match decidingRuleCache with + | ValueNone -> warns + | ValueSome cache -> computeConcretenessWarnings cache applicableMeths calledMeth warns m + + Some calledMeth, OkResult(allWarns, ()), WithTrace t | bestMethods -> let methods = @@ -3865,7 +3763,15 @@ and GetMostApplicableOverload csenv ndeep candidates applicableMeths calledMethG let methods = List.concat methods - let err = FailOverloading csenv calledMethGroup reqdRetTyOpt isOpConversion callerArgs (PossibleCandidates(methodName, methods,cx)) m + let incomparableConcretenessInfo = + applicableMeths + |> List.tryPick (fun (meth1, _, _, _) -> + applicableMeths + |> List.tryPick (fun (meth2, _, _, _) -> + if System.Object.ReferenceEquals(meth1, meth2) then None + else explainIncomparableMethodConcreteness ctx meth1 meth2)) + + let err = FailOverloading csenv calledMethGroup reqdRetTyOpt isOpConversion callerArgs (PossibleCandidates(methodName, methods, cx, incomparableConcretenessInfo)) m None, ErrorD err, NoTrace let ResolveOverloadingForCall denv css m methodName callerArgs ad calledMethGroup permitOptArgs reqdRetTy = diff --git a/src/Compiler/Checking/ConstraintSolver.fsi b/src/Compiler/Checking/ConstraintSolver.fsi index 4c29d684c31..c544865159b 100644 --- a/src/Compiler/Checking/ConstraintSolver.fsi +++ b/src/Compiler/Checking/ConstraintSolver.fsi @@ -73,7 +73,8 @@ type OverloadResolutionFailure = | PossibleCandidates of methodName: string * candidates: OverloadInformation list * // methodNames may be different (with operators?), this is refactored from original logic to assemble overload failure message - cx: TraitConstraintInfo option + cx: TraitConstraintInfo option * + incomparableConcreteness: OverloadResolutionRules.IncomparableConcretenessInfo option /// Represents known information prior to checking an expression or pattern, e.g. it's expected type type OverallTy = diff --git a/src/Compiler/Checking/Expressions/CheckExpressions.fs b/src/Compiler/Checking/Expressions/CheckExpressions.fs index c0d29359266..a0fb735737f 100644 --- a/src/Compiler/Checking/Expressions/CheckExpressions.fs +++ b/src/Compiler/Checking/Expressions/CheckExpressions.fs @@ -1403,6 +1403,13 @@ let MakeAndPublishVal (cenv: cenv) env (altActualParent, inSig, declKind, valRec | ParentNone -> errorR(Error(FSComp.SR.tcCompiledNameAttributeMisused(), m)) | _ -> () + // OverloadResolutionPriority not allowed on override members + match memberInfoOpt with + | Some (PrelimMemberInfo(memberInfo, _, _)) when memberInfo.MemberFlags.IsOverrideOrExplicitImpl -> + if Option.isSome (TryFindFSharpAttributeOpt g g.attrib_OverloadResolutionPriorityAttribute attrs) then + errorR(Error(FSComp.SR.tcOverloadResolutionPriorityOnOverride(), m)) + | _ -> () + let compiledNameIsOnProp = match memberInfoOpt with | Some (PrelimMemberInfo(memberInfo, _, _)) -> diff --git a/src/Compiler/Checking/OverloadResolutionRules.fs b/src/Compiler/Checking/OverloadResolutionRules.fs new file mode 100644 index 00000000000..5c8ac2be5fb --- /dev/null +++ b/src/Compiler/Checking/OverloadResolutionRules.fs @@ -0,0 +1,660 @@ +// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information. + +/// DSL for overload resolution tiebreaker rules. +/// This module provides a structured representation of all rules used in method overload resolution. +module internal FSharp.Compiler.OverloadResolutionRules + +open FSharp.Compiler.Features +open FSharp.Compiler.Import +open FSharp.Compiler.Infos +open FSharp.Compiler.MethodCalls +open FSharp.Compiler.Syntax +open FSharp.Compiler.Text +open FSharp.Compiler.TcGlobals +open FSharp.Compiler.TypedTree +open FSharp.Compiler.TypedTreeOps +open FSharp.Compiler.TypeHierarchy +open FSharp.Compiler.TypeRelations + +type OverloadResolutionContext = + { + g: TcGlobals + amap: ImportMap + m: range + /// Nesting depth for subsumption checks + ndeep: int + /// Per-method cache for GetParamDatas results, avoiding redundant calls across pairwise comparisons + paramDataCache: System.Collections.Generic.Dictionary voption + /// Per-method cache for SRTP presence checks, avoiding redundant traversals across pairwise comparisons + srtpCache: System.Collections.Generic.Dictionary voption + } + +/// Identifies a tiebreaker rule in overload resolution. +/// Values are assigned to match the conceptual ordering in F# Language Spec §14.4. +/// Rules are evaluated in ascending order by their integer value. +[] +type TiebreakRuleId = + /// Prefer methods that don't use type-directed conversion + | NoTDC = 1 + /// Prefer methods that need less type-directed conversion + | LessTDC = 2 + /// Prefer methods that only have nullable type-directed conversions + | NullableTDC = 3 + /// Prefer methods that don't give 'this code is less generic' warnings + | NoWarnings = 4 + /// Prefer methods that don't use param array arg + | NoParamArray = 5 + /// Prefer methods with more precise param array arg type + | PreciseParamArray = 6 + /// Prefer methods that don't use out args + | NoOutArgs = 7 + /// Prefer methods that don't use optional args + | NoOptionalArgs = 8 + /// Compare regular unnamed args using subsumption ordering + | UnnamedArgs = 9 + /// Prefer non-extension methods over extension methods + | PreferNonExtension = 10 + /// Between extension methods, prefer most recently opened + | ExtensionPriority = 11 + /// Prefer non-generic methods over generic methods + | PreferNonGeneric = 12 + /// Prefer more concrete type instantiations over more generic ones + | MoreConcrete = 13 + /// F# 5.0 rule - compare all arguments including optional and named + | NullableOptionalInterop = 14 + /// For properties, prefer more derived type (partial override support) + | PropertyOverride = 15 + +/// Rules are ordered by their TiebreakRuleId (lower value = higher priority). +type TiebreakRule = + { + Id: TiebreakRuleId + /// Optional LanguageFeature required for this rule to be active. + /// If Some, the rule is skipped when the feature is not supported. + RequiredFeature: LanguageFeature option + /// Comparison function: returns >0 if candidate is better, <0 if other is better, 0 if equal + Compare: + OverloadResolutionContext + -> struct (CalledMeth * TypeDirectedConversionUsed * int) // candidate, TDC, warnCount + -> struct (CalledMeth * TypeDirectedConversionUsed * int) // other, TDC, warnCount + -> int + } + +// ------------------------------------------------------------------------- +// Type Concreteness Comparison +// ------------------------------------------------------------------------- + +/// Fold over two lists pairwise with a comparison function, accumulating dominance state. +/// Early-exits when incomparability is detected (both positive and negative seen). +/// Returns the accumulated state so it can be chained across multiple lists. +let private foldMap2 (f: 'a -> 'b -> int) initP initN (xs: 'a list) (ys: 'b list) = + let rec loop hasPositive hasNegative xs ys = + match xs, ys with + | [], _ + | _, [] -> struct (hasPositive, hasNegative) + | x :: xt, y :: yt -> + let c = f x y + let p = hasPositive || c > 0 + let n = hasNegative || c < 0 + + if p && n then + struct (true, true) // incomparable — early exit + else + loop p n xt yt + + loop initP initN xs ys + +/// Convert accumulated dominance state into a comparison result. +let private resolveAggregation (struct (hasPositive, hasNegative)) = + if not hasNegative && hasPositive then 1 + elif not hasPositive && hasNegative then -1 + else 0 + +/// Fold over two lists pairwise with a comparison function, aggregating using dominance. +let private aggregateMap2 f xs ys = + foldMap2 f false false xs ys |> resolveAggregation + +/// SRTP type parameters use a different constraint solving mechanism and shouldn't +/// be compared under the "more concrete" ordering. +let private isStaticallyResolvedTypeParam (tp: Typar) = + match tp.StaticReq with + | TyparStaticReq.HeadType -> true + | TyparStaticReq.None -> false + +let private containsSRTPTypeVar (g: TcGlobals) (ty: TType) : bool = + let rec loop (ty: TType) : bool = + let sty = stripTyEqns g ty + + match sty with + | TType_var(tp, _) -> isStaticallyResolvedTypeParam tp + | TType_app(_, args, _) -> args |> List.exists loop + | TType_tuple(_, elems) -> elems |> List.exists loop + | TType_fun(dom, rng, _) -> loop dom || loop rng + | TType_anon(_, tys) -> tys |> List.exists loop + | TType_forall(_, body) -> loop body + | TType_measure _ -> false + | TType_ucase _ -> false + + loop ty + +/// Returns 1 if ty1 is more concrete, -1 if ty2 is more concrete, 0 if incomparable. +let compareTypeConcreteness (g: TcGlobals) ty1 ty2 = + let rec loop ty1 ty2 = + let sty1 = stripTyEqns g ty1 + let sty2 = stripTyEqns g ty2 + + match sty1, sty2 with + // Neither F# nor C# allows constraint-only method overloads, so comparing + // constraint counts would be dead code. Both type vars are treated as equal. + | TType_var _, TType_var _ -> 0 + + | TType_var(tp, _), _ when isStaticallyResolvedTypeParam tp -> 0 + | _, TType_var(tp, _) when isStaticallyResolvedTypeParam tp -> 0 + | TType_var _, _ -> -1 + | _, TType_var _ -> 1 + + | TType_app(tcref1, args1, _), TType_app(tcref2, args2, _) -> + if not (tyconRefEq g tcref1 tcref2) then 0 + elif args1.Length <> args2.Length then 0 + else aggregateMap2 loop args1 args2 + + | TType_tuple(_, elems1), TType_tuple(_, elems2) -> + if elems1.Length <> elems2.Length then + 0 + else + aggregateMap2 loop elems1 elems2 + + | TType_fun(dom1, rng1, _), TType_fun(dom2, rng2, _) -> + let cDomain = loop dom1 dom2 + let cRange = loop rng1 rng2 + // Inline aggregation for 2 elements to avoid list allocation + let hasPositive = cDomain > 0 || cRange > 0 + let hasNegative = cDomain < 0 || cRange < 0 + + if not hasNegative && hasPositive then 1 + elif not hasPositive && hasNegative then -1 + else 0 + + | TType_anon(info1, tys1), TType_anon(info2, tys2) -> + if not (anonInfoEquiv info1 info2) then + 0 + else + aggregateMap2 loop tys1 tys2 + + | TType_measure _, TType_measure _ -> 0 + + | TType_forall(tps1, body1), TType_forall(tps2, body2) -> if tps1.Length <> tps2.Length then 0 else loop body1 body2 + + | _ -> 0 + + loop ty1 ty2 + +/// Represents why two methods are incomparable under concreteness ordering. +type IncomparableConcretenessInfo = + { + Method1Name: string + Method1BetterPositions: int list + Method2Name: string + Method2BetterPositions: int list + } + +/// Explain why two CalledMeth objects are incomparable under the concreteness ordering. +/// Returns Some info when the methods are incomparable due to mixed concreteness results. +let explainIncomparableMethodConcreteness<'T> + (ctx: OverloadResolutionContext) + (meth1: CalledMeth<'T>) + (meth2: CalledMeth<'T>) + : IncomparableConcretenessInfo option = + if meth1.CalledTyArgs.IsEmpty || meth2.CalledTyArgs.IsEmpty then + None + else + let formalParams1 = + meth1.Method.GetParamDatas(ctx.amap, ctx.m, meth1.Method.FormalMethodInst) + |> List.concat + + let formalParams2 = + meth2.Method.GetParamDatas(ctx.amap, ctx.m, meth2.Method.FormalMethodInst) + |> List.concat + + if formalParams1.Length <> formalParams2.Length then + None + else + let rec collectComparisons paramIdx (ty1: TType) (ty2: TType) : (int * int) list = + let sty1 = stripTyEqns ctx.g ty1 + let sty2 = stripTyEqns ctx.g ty2 + + match sty1, sty2 with + | TType_app(tcref1, args1, _), TType_app(tcref2, args2, _) when + tyconRefEq ctx.g tcref1 tcref2 && args1.Length = args2.Length + -> + args1 + |> List.mapi2 + (fun argIdx arg1 arg2 -> + let c = compareTypeConcreteness ctx.g arg1 arg2 + (argIdx + 1, c)) + args2 + | _ -> [ (paramIdx, compareTypeConcreteness ctx.g ty1 ty2) ] + + let allComparisons = + List.mapi2 + (fun i (ParamData(_, _, _, _, _, _, _, ty1)) (ParamData(_, _, _, _, _, _, _, ty2)) -> + collectComparisons (i + 1) ty1 ty2) + formalParams1 + formalParams2 + |> List.concat + + let meth1Better = + allComparisons |> List.choose (fun (pos, c) -> if c > 0 then Some pos else None) + + let meth2Better = + allComparisons |> List.choose (fun (pos, c) -> if c < 0 then Some pos else None) + + if not meth1Better.IsEmpty && not meth2Better.IsEmpty then + Some + { + Method1Name = meth1.Method.DisplayName + Method1BetterPositions = meth1Better + Method2Name = meth2.Method.DisplayName + Method2BetterPositions = meth2Better + } + else + None + +// ------------------------------------------------------------------------- +// Helper functions for comparisons +// ------------------------------------------------------------------------- + +/// Compare two things by the given predicate. +/// If the predicate returns true for x1 and false for x2, then x1 > x2 +/// If the predicate returns false for x1 and true for x2, then x1 < x2 +/// Otherwise x1 = x2 +let private compareCond (p: 'T -> 'T -> bool) x1 x2 = compare (p x1 x2) (p x2 x1) + +/// Compare types under the feasibly-subsumes ordering +let private compareTypes (ctx: OverloadResolutionContext) ty1 ty2 = + (ty1, ty2) + ||> compareCond (fun x1 x2 -> TypeFeasiblySubsumesType ctx.ndeep ctx.g ctx.amap ctx.m x2 CanCoerce x1) + +/// Compare arguments under the feasibly-subsumes ordering and the adhoc Func-is-better-than-other-delegates rule +let private compareArg (ctx: OverloadResolutionContext) (calledArg1: CalledArg) (calledArg2: CalledArg) = + let g = ctx.g + let c = compareTypes ctx calledArg1.CalledArgumentType calledArg2.CalledArgumentType + + if c <> 0 then + c + else + + let c = + (calledArg1.CalledArgumentType, calledArg2.CalledArgumentType) + ||> compareCond (fun ty1 ty2 -> + + // Func<_> is always considered better than any other delegate type + match tryTcrefOfAppTy g ty1 with + | ValueSome tcref1 when + tcref1.DisplayName = "Func" + && (match tcref1.PublicPath with + | Some p -> p.EnclosingPath = [| "System" |] + | _ -> false) + && isDelegateTy g ty1 + && isDelegateTy g ty2 + -> + true + + // T is always better than inref + | _ when isInByrefTy g ty2 && typeEquiv g ty1 (destByrefTy g ty2) -> true + + // T is always better than Nullable from F# 5.0 onwards + | _ when + g.langVersion.SupportsFeature(LanguageFeature.NullableOptionalInterop) + && isNullableTy g ty2 + && typeEquiv g ty1 (destNullableTy g ty2) + -> + true + + | _ -> false) + + if c <> 0 then c else 0 + +/// Compare argument lists using dominance: better in at least one, not worse in any +let private compareArgLists ctx (args1: CalledArg list) (args2: CalledArg list) = + if args1.Length = args2.Length then + aggregateMap2 (compareArg ctx) args1 args2 + else + 0 + +// ------------------------------------------------------------------------- +// Rule Definitions +// ------------------------------------------------------------------------- + +let private noTDCRule: TiebreakRule = + { + Id = TiebreakRuleId.NoTDC + RequiredFeature = None + Compare = + fun _ (struct (_, usesTDC1, _)) (struct (_, usesTDC2, _)) -> + compare + (match usesTDC1 with + | TypeDirectedConversionUsed.No -> 1 + | _ -> 0) + (match usesTDC2 with + | TypeDirectedConversionUsed.No -> 1 + | _ -> 0) + } + +let private lessTDCRule: TiebreakRule = + { + Id = TiebreakRuleId.LessTDC + RequiredFeature = None + Compare = + fun _ (struct (_, usesTDC1, _)) (struct (_, usesTDC2, _)) -> + compare + (match usesTDC1 with + | TypeDirectedConversionUsed.Yes(_, false, _) -> 1 + | _ -> 0) + (match usesTDC2 with + | TypeDirectedConversionUsed.Yes(_, false, _) -> 1 + | _ -> 0) + } + +let private nullableTDCRule: TiebreakRule = + { + Id = TiebreakRuleId.NullableTDC + RequiredFeature = None + Compare = + fun _ (struct (_, usesTDC1, _)) (struct (_, usesTDC2, _)) -> + compare + (match usesTDC1 with + | TypeDirectedConversionUsed.Yes(_, _, true) -> 1 + | _ -> 0) + (match usesTDC2 with + | TypeDirectedConversionUsed.Yes(_, _, true) -> 1 + | _ -> 0) + } + +let private noWarningsRule: TiebreakRule = + { + Id = TiebreakRuleId.NoWarnings + RequiredFeature = None + Compare = fun _ (struct (_, _, warnCount1)) (struct (_, _, warnCount2)) -> compare (warnCount1 = 0) (warnCount2 = 0) + } + +let private noParamArrayRule: TiebreakRule = + { + Id = TiebreakRuleId.NoParamArray + RequiredFeature = None + Compare = + fun _ (struct (candidate, _, _)) (struct (other, _, _)) -> + compare (not candidate.UsesParamArrayConversion) (not other.UsesParamArrayConversion) + } + +let private preciseParamArrayRule: TiebreakRule = + { + Id = TiebreakRuleId.PreciseParamArray + RequiredFeature = None + Compare = + fun ctx (struct (candidate, _, _)) (struct (other, _, _)) -> + if candidate.UsesParamArrayConversion && other.UsesParamArrayConversion then + compareTypes ctx (candidate.GetParamArrayElementType()) (other.GetParamArrayElementType()) + else + 0 + } + +let private noOutArgsRule: TiebreakRule = + { + Id = TiebreakRuleId.NoOutArgs + RequiredFeature = None + Compare = fun _ (struct (candidate, _, _)) (struct (other, _, _)) -> compare (not candidate.HasOutArgs) (not other.HasOutArgs) + } + +let private noOptionalArgsRule: TiebreakRule = + { + Id = TiebreakRuleId.NoOptionalArgs + RequiredFeature = None + Compare = + fun _ (struct (candidate, _, _)) (struct (other, _, _)) -> compare (not candidate.HasOptionalArgs) (not other.HasOptionalArgs) + } + +let private unnamedArgsRule: TiebreakRule = + { + Id = TiebreakRuleId.UnnamedArgs + RequiredFeature = None + Compare = + fun ctx (struct (candidate, _, _)) (struct (other, _, _)) -> + if candidate.TotalNumUnnamedCalledArgs = other.TotalNumUnnamedCalledArgs then + // Fold over obj-args first, then unnamed-args, with shared dominance state. + // This avoids intermediate list allocations from `@` concatenation while + // still detecting cross-group incomparability correctly. + let struct (p, n) = + if candidate.Method.IsExtensionMember && other.Method.IsExtensionMember then + let objArgTys1 = candidate.CalledObjArgTys(ctx.m) + let objArgTys2 = other.CalledObjArgTys(ctx.m) + + if objArgTys1.Length = objArgTys2.Length then + foldMap2 (compareTypes ctx) false false objArgTys1 objArgTys2 + else + struct (false, false) + else + struct (false, false) + + if p && n then + 0 + else + foldMap2 (compareArg ctx) p n candidate.AllUnnamedCalledArgs other.AllUnnamedCalledArgs + |> resolveAggregation + else + 0 + } + +let private preferNonExtensionRule: TiebreakRule = + { + Id = TiebreakRuleId.PreferNonExtension + RequiredFeature = None + Compare = + fun _ (struct (candidate, _, _)) (struct (other, _, _)) -> + compare (not candidate.Method.IsExtensionMember) (not other.Method.IsExtensionMember) + } + +let private extensionPriorityRule: TiebreakRule = + { + Id = TiebreakRuleId.ExtensionPriority + RequiredFeature = None + Compare = + fun _ (struct (candidate, _, _)) (struct (other, _, _)) -> + if candidate.Method.IsExtensionMember && other.Method.IsExtensionMember then + compare candidate.Method.ExtensionMemberPriority other.Method.ExtensionMemberPriority + else + 0 + } + +let private preferNonGenericRule: TiebreakRule = + { + Id = TiebreakRuleId.PreferNonGeneric + RequiredFeature = None + Compare = + fun _ (struct (candidate, _, _)) (struct (other, _, _)) -> compare candidate.CalledTyArgs.IsEmpty other.CalledTyArgs.IsEmpty + } + +let private getCachedParamData (ctx: OverloadResolutionContext) (meth: CalledMeth) = + let computeParamData () = + meth.Method.GetParamDatas(ctx.amap, ctx.m, meth.Method.FormalMethodInst) + |> List.concat + + match ctx.paramDataCache with + | ValueNone -> computeParamData () + | ValueSome cache -> + let key = meth :> obj + + match cache.TryGetValue(key) with + | true, v -> v + | _ -> + let v = computeParamData () + cache[key] <- v + v + +let private getCachedHasSRTP (ctx: OverloadResolutionContext) (meth: CalledMeth) = + let computeHasSRTP () = + let hasTyparSRTP = + meth.Method.FormalMethodTypars |> List.exists isStaticallyResolvedTypeParam + + let hasTyArgSRTP = + hasTyparSRTP || meth.CalledTyArgs |> List.exists (containsSRTPTypeVar ctx.g) + + hasTyArgSRTP + || (let paramData = getCachedParamData ctx meth in + + paramData + |> List.exists (fun (ParamData(_, _, _, _, _, _, _, ty)) -> containsSRTPTypeVar ctx.g ty)) + + match ctx.srtpCache with + | ValueNone -> computeHasSRTP () + | ValueSome cache -> + let key = meth :> obj + + match cache.TryGetValue(key) with + | true, v -> v + | _ -> + let result = computeHasSRTP () + cache[key] <- result + result + +let private moreConcreteRule: TiebreakRule = + { + Id = TiebreakRuleId.MoreConcrete + RequiredFeature = Some LanguageFeature.MoreConcreteTiebreaker + Compare = + fun ctx (struct (candidate, _, _)) (struct (other, _, _)) -> + if not candidate.CalledTyArgs.IsEmpty && not other.CalledTyArgs.IsEmpty then + if getCachedHasSRTP ctx candidate || getCachedHasSRTP ctx other then + 0 + else + let formalParams1 = getCachedParamData ctx candidate + let formalParams2 = getCachedParamData ctx other + + if formalParams1.Length = formalParams2.Length then + aggregateMap2 + (fun (ParamData(_, _, _, _, _, _, _, ty1)) (ParamData(_, _, _, _, _, _, _, ty2)) -> + compareTypeConcreteness ctx.g ty1 ty2) + formalParams1 + formalParams2 + else + 0 + else + 0 + } + +let private nullableOptionalInteropRule: TiebreakRule = + { + Id = TiebreakRuleId.NullableOptionalInterop + RequiredFeature = Some LanguageFeature.NullableOptionalInterop + Compare = + fun ctx (struct (candidate, _, _)) (struct (other, _, _)) -> + let args1 = candidate.AllCalledArgs |> List.concat + let args2 = other.AllCalledArgs |> List.concat + compareArgLists ctx args1 args2 + } + +let private propertyOverrideRule: TiebreakRule = + { + Id = TiebreakRuleId.PropertyOverride + RequiredFeature = None + Compare = + fun ctx (struct (candidate, _, _)) (struct (other, _, _)) -> + match + candidate.AssociatedPropertyInfo, + other.AssociatedPropertyInfo, + candidate.Method.IsExtensionMember, + other.Method.IsExtensionMember + with + | Some p1, Some p2, false, false -> compareTypes ctx p1.ApparentEnclosingType p2.ApparentEnclosingType + | _ -> 0 + } + +// ------------------------------------------------------------------------- +// Public API +// ------------------------------------------------------------------------- + +let private allTiebreakRules: TiebreakRule list = + [ + noTDCRule + lessTDCRule + nullableTDCRule + noWarningsRule + noParamArrayRule + preciseParamArrayRule + noOutArgsRule + noOptionalArgsRule + unnamedArgsRule + preferNonExtensionRule + extensionPriorityRule + preferNonGenericRule + moreConcreteRule + nullableOptionalInteropRule + propertyOverrideRule + ] + +let private isRuleEnabled (context: OverloadResolutionContext) (rule: TiebreakRule) = + match rule.RequiredFeature with + | None -> true + | Some feature -> context.g.langVersion.SupportsFeature(feature) + +/// Evaluate all tiebreaker rules and return both the result and the deciding rule. +/// Returns struct(result, ValueSome ruleId) if a rule decided, or struct(0, ValueNone) if all rules returned 0. +let findDecidingRule + (context: OverloadResolutionContext) + (candidate: struct (CalledMeth * TypeDirectedConversionUsed * int)) + (other: struct (CalledMeth * TypeDirectedConversionUsed * int)) + : struct (int * TiebreakRuleId voption) = + + let rec loop rules = + match rules with + | [] -> struct (0, ValueNone) + | rule :: rest -> + if isRuleEnabled context rule then + let c = rule.Compare context candidate other + if c <> 0 then struct (c, ValueSome rule.Id) else loop rest + else + loop rest + + loop allTiebreakRules + +// ------------------------------------------------------------------------- +// OverloadResolutionPriority Pre-Filter (RFC: .NET 9 attribute) +// ------------------------------------------------------------------------- + +/// Apply OverloadResolutionPriority pre-filter to a list of candidates. +/// Groups methods by declaring type and keeps only highest-priority within each group. +let filterByOverloadResolutionPriority<'T> (g: TcGlobals) (getMeth: 'T -> MethInfo) (candidates: 'T list) : 'T list = + match candidates with + | [] + | [ _ ] -> candidates + | _ when not (g.langVersion.SupportsFeature LanguageFeature.OverloadResolutionPriority) -> candidates + | twoOrMoreCandidates -> + // Fast path: check if any method has a non-zero priority before allocating the enriched list. + // In 99% of resolutions no method uses the attribute, so this avoids all allocation. + let hasAnyPriority = + twoOrMoreCandidates + |> List.exists (fun c -> (getMeth c).GetOverloadResolutionPriority() <> 0) + + if not hasAnyPriority then + candidates + else + let enriched = + twoOrMoreCandidates + |> List.map (fun c -> + let m = getMeth c + + let stamp = + match tryTcrefOfAppTy g m.ApparentEnclosingType with + | ValueSome tcref -> tcref.Stamp + | ValueNone -> 0L + + (c, stamp, m.GetOverloadResolutionPriority())) + + enriched + |> List.groupBy (fun (_, stamp, _) -> stamp) + |> List.collect (fun (_, group) -> + let _, _, maxPrio = group |> List.maxBy (fun (_, _, prio) -> prio) + + group + |> List.filter (fun (_, _, prio) -> prio = maxPrio) + |> List.map (fun (c, _, _) -> c)) diff --git a/src/Compiler/Checking/OverloadResolutionRules.fsi b/src/Compiler/Checking/OverloadResolutionRules.fsi new file mode 100644 index 00000000000..95911a76fdf --- /dev/null +++ b/src/Compiler/Checking/OverloadResolutionRules.fsi @@ -0,0 +1,77 @@ +// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information. + +/// DSL for overload resolution tiebreaker rules. +/// This module provides a structured representation of all rules used in method overload resolution. +module internal FSharp.Compiler.OverloadResolutionRules + +open FSharp.Compiler.Infos +open FSharp.Compiler.MethodCalls +open FSharp.Compiler.Text +open FSharp.Compiler.TcGlobals +open FSharp.Compiler.TypedTree +open FSharp.Compiler.Import + +type OverloadResolutionContext = + { + g: TcGlobals + amap: ImportMap + m: range + /// Nesting depth for subsumption checks + ndeep: int + /// Per-method cache for GetParamDatas results, avoiding redundant calls across pairwise comparisons + paramDataCache: System.Collections.Generic.Dictionary voption + /// Per-method cache for SRTP presence checks, avoiding redundant traversals across pairwise comparisons + srtpCache: System.Collections.Generic.Dictionary voption + } + +/// Represents why two methods are incomparable under concreteness ordering. +type IncomparableConcretenessInfo = + { Method1Name: string + Method1BetterPositions: int list + Method2Name: string + Method2BetterPositions: int list } + +/// Explain why two CalledMeth objects are incomparable under the concreteness ordering. +/// Returns Some info when the methods are incomparable due to mixed concreteness results. +val explainIncomparableMethodConcreteness: + ctx: OverloadResolutionContext -> + meth1: CalledMeth<'T> -> + meth2: CalledMeth<'T> -> + IncomparableConcretenessInfo option + +/// Identifies a tiebreaker rule in overload resolution. +/// Values are assigned to match the conceptual ordering in F# Language Spec §14.4. +/// Rules are evaluated in ascending order by their integer value. +[] +type TiebreakRuleId = + | NoTDC = 1 + | LessTDC = 2 + | NullableTDC = 3 + | NoWarnings = 4 + | NoParamArray = 5 + | PreciseParamArray = 6 + | NoOutArgs = 7 + | NoOptionalArgs = 8 + | UnnamedArgs = 9 + | PreferNonExtension = 10 + | ExtensionPriority = 11 + | PreferNonGeneric = 12 + | MoreConcrete = 13 + | NullableOptionalInterop = 14 + | PropertyOverride = 15 + +/// Evaluate all tiebreaker rules and return both the result and the deciding rule. +/// Returns struct(result, ValueSome ruleId) if a rule decided, or struct(0, ValueNone) if all rules returned 0. +val findDecidingRule: + context: OverloadResolutionContext -> + candidate: struct (CalledMeth * TypeDirectedConversionUsed * int) -> + other: struct (CalledMeth * TypeDirectedConversionUsed * int) -> + struct (int * TiebreakRuleId voption) + +// ------------------------------------------------------------------------- +// OverloadResolutionPriority Pre-Filter +// ------------------------------------------------------------------------- + +/// Apply OverloadResolutionPriority pre-filter to a list of candidates. +/// Groups methods by declaring type and keeps only highest-priority within each group. +val filterByOverloadResolutionPriority<'T> : g: TcGlobals -> getMeth: ('T -> MethInfo) -> candidates: 'T list -> 'T list diff --git a/src/Compiler/Checking/infos.fs b/src/Compiler/Checking/infos.fs index 746e23e57cb..a871b6b8910 100644 --- a/src/Compiler/Checking/infos.fs +++ b/src/Compiler/Checking/infos.fs @@ -1257,6 +1257,24 @@ type MethInfo = | MethInfoWithModifiedReturnType(mi,_) -> mi.GetCustomAttrs() | _ -> ILAttributes.Empty + /// Returns 0 if the attribute is not present or if targeting a runtime without the attribute. + member x.GetOverloadResolutionPriority() : int = + match x with + | ILMeth(g, ilMethInfo, _) -> + match TryDecodeILAttributeOpt g.attrib_OverloadResolutionPriorityAttribute ilMethInfo.RawMetadata.CustomAttrs with + | Some ([ ILAttribElem.Int32 priority ], _) -> priority + | _ -> 0 + | FSMeth(g, _, vref, _) -> + match TryFindFSharpInt32AttributeOpt g g.attrib_OverloadResolutionPriorityAttribute vref.Attribs with + | Some _ when vref.IsDefiniteFSharpOverrideMember -> 0 + | Some priority -> priority + | None -> 0 + | MethInfoWithModifiedReturnType(mi, _) -> mi.GetOverloadResolutionPriority() + | DefaultStructCtor _ -> 0 +#if !NO_TYPEPROVIDERS + | ProvidedMeth _ -> 0 +#endif + /// Get the parameter attributes of a method info, which get combined with the parameter names and types member x.GetParamAttribs(amap, m) = match x with diff --git a/src/Compiler/Checking/infos.fsi b/src/Compiler/Checking/infos.fsi index e091834e271..2463f959fff 100644 --- a/src/Compiler/Checking/infos.fsi +++ b/src/Compiler/Checking/infos.fsi @@ -523,6 +523,9 @@ type MethInfo = /// Get custom attributes for method (only applicable for IL methods) member GetCustomAttrs: unit -> ILAttributes + /// Returns 0 if the attribute is not present. + member GetOverloadResolutionPriority: unit -> int + /// Get the parameter attributes of a method info, which get combined with the parameter names and types member GetParamAttribs: amap: ImportMap * m: range -> ParamAttribs list list diff --git a/src/Compiler/Driver/CompilerDiagnostics.fs b/src/Compiler/Driver/CompilerDiagnostics.fs index 74195f5d53b..3eaccfabb5f 100644 --- a/src/Compiler/Driver/CompilerDiagnostics.fs +++ b/src/Compiler/Driver/CompilerDiagnostics.fs @@ -394,6 +394,8 @@ type PhasedDiagnostic with | 3395 -> false // tcImplicitConversionUsedForMethodArg - off by default | 3559 -> false // typrelNeverRefinedAwayFromTop - off by default | 3560 -> false // tcCopyAndUpdateRecordChangesAllFields - off by default + | 3575 -> false // tcMoreConcreteTiebreakerUsed - off by default + | 3576 -> false // tcGenericOverloadBypassed - off by default | 3579 -> false // alwaysUseTypedStringInterpolation - off by default | 3582 -> false // infoIfFunctionShadowsUnionCase - off by default | 3570 -> false // tcAmbiguousDiscardDotLambda - off by default @@ -973,11 +975,28 @@ type Exception with FSComp.SR.csNoOverloadsFound methodName + optionalParts + (FSComp.SR.csAvailableOverloads (formatOverloads overloads)) - | PossibleCandidates(methodName, [], _) -> FSComp.SR.csMethodIsOverloaded methodName - | PossibleCandidates(methodName, overloads, _) -> - FSComp.SR.csMethodIsOverloaded methodName - + optionalParts - + FSComp.SR.csCandidates (formatOverloads overloads) + | PossibleCandidates(methodName, [], _, _) -> FSComp.SR.csMethodIsOverloaded methodName + | PossibleCandidates(methodName, overloads, _, incomparableInfo) -> + let baseMessage = + FSComp.SR.csMethodIsOverloaded methodName + + optionalParts + + FSComp.SR.csCandidates (formatOverloads overloads) + + match incomparableInfo with + | Some info -> + let formatPositions positions = + match positions with + | [ p ] -> sprintf "position %d" p + | _ -> positions |> List.map string |> String.concat ", " |> sprintf "positions %s" + + let line1 = + sprintf " - %s is more concrete at %s" info.Method1Name (formatPositions info.Method1BetterPositions) + + let line2 = + sprintf " - %s is more concrete at %s" info.Method2Name (formatPositions info.Method2BetterPositions) + + baseMessage + nl + FSComp.SR.csIncomparableConcreteness (line1 + nl + line2) + | None -> baseMessage os.AppendString msg diff --git a/src/Compiler/FSComp.txt b/src/Compiler/FSComp.txt index b39bf210809..dccec0eea47 100644 --- a/src/Compiler/FSComp.txt +++ b/src/Compiler/FSComp.txt @@ -381,6 +381,7 @@ csNoOverloadsFoundTypeParametersPrefixPlural,"Known type parameters: %s" csNoOverloadsFoundReturnType,"Known return type: %s" csMethodIsOverloaded,"A unique overload for method '%s' could not be determined based on type information prior to this program point. A type annotation may be needed." csCandidates,"Candidates:\n%s" +csIncomparableConcreteness,"Neither candidate is strictly more concrete than the other:\n%s" csAvailableOverloads,"Available overloads:\n%s" csOverloadCandidateNamedArgumentTypeMismatch,"Argument '%s' doesn't match" csOverloadCandidateIndexedArgumentTypeMismatch,"Argument at index %d doesn't match" @@ -1749,6 +1750,8 @@ featureAccessorFunctionShorthand,"underscore dot shorthand for accessor only fun 3572,parsConstraintIntersectionSyntaxUsedWithNonFlexibleType,"Constraint intersection syntax may only be used with flexible types, e.g. '#IDisposable & #ISomeInterface'." 3573,tcStaticBindingInExtrinsicAugmentation,"Static bindings cannot be added to extrinsic augmentations. Consider using a 'static member' instead." 3574,pickleFsharpCoreBackwardsCompatible,"Newly added pickle state cannot be used in FSharp.Core, since it must be working in older compilers+tooling as well. The time window is at least 3 years after feature introduction. Violation: %s . Context: \n %s " +3575,tcMoreConcreteTiebreakerUsed,"Overload resolution selected '%s' based on type concreteness. The more concrete type '%s' was preferred over '%s'. This is an informational message and can be enabled with --warnon:3575." +3576,tcGenericOverloadBypassed,"A more generic overload was bypassed: '%s'. The selected overload '%s' was chosen because it has more concrete type parameters." 3577,tcOverrideUsesMultipleArgumentsInsteadOfTuple,"This override takes a tuple instead of multiple arguments. Try to add an additional layer of parentheses at the method definition (e.g. 'member _.Foo((x, y))'), or remove parentheses at the abstract method declaration (e.g. 'abstract member Foo: 'a * 'b -> 'c')." featureUnmanagedConstraintCsharpInterop,"Interop between C#'s and F#'s unmanaged generic constraint (emit additional modreq)" 3578,chkCopyUpdateSyntaxInAnonRecords,"This expression is an anonymous record, use {{|...|}} instead of {{...}}." @@ -1759,6 +1762,7 @@ featureUnmanagedConstraintCsharpInterop,"Interop between C#'s and F#'s unmanaged 3583,unnecessaryParentheses,"Parentheses can be removed." 3584,tcDotLambdaAtNotSupportedExpression,"Shorthand lambda syntax is only supported for atomic expressions, such as method, property, field or indexer on the implied '_' argument. For example: 'let f = _.Length'." 3585,tcStructUnionMultiCaseFieldsSameType,"If a multicase union type is a struct, then all fields with the same name must be of the same type. This rule applies also to the generated 'Item' name in case of unnamed fields." +3586,tcOverloadResolutionPriorityOnOverride,"The 'OverloadResolutionPriorityAttribute' cannot be applied to an override member. Apply it to the original declaration instead." featureReuseSameFieldsInStructUnions,"Share underlying fields in a [] discriminated union as long as they have same name and type" 3855,tcNoStaticMemberFoundForOverride,"No static abstract member was found that corresponds to this override" 3859,tcNoStaticPropertyFoundForOverride,"No static abstract property was found that corresponds to this override" @@ -1803,5 +1807,7 @@ featureAllowLetOrUseBangTypeAnnotationWithoutParens,"Allow let! and use! type an 3878,tcAttributeIsNotValidForUnionCaseWithFields,"This attribute is not valid for use on union cases with fields." 3879,xmlDocNotFirstOnLine,"XML documentation comments should be the first non-whitespace text on a line." featureReturnFromFinal,"Support for ReturnFromFinal/YieldFromFinal in computation expressions to enable tailcall optimization when available on the builder." +featureMoreConcreteTiebreaker,"Use 'most concrete' tiebreaker for overload resolution when methods differ only by type parameter concreteness." +featureOverloadResolutionPriority,"Support for OverloadResolutionPriorityAttribute to prioritize method overloads." 3880,optsLangVersionOutOfSupport,"Language version '%s' is out of support. The last .NET SDK supporting it is available at https://dotnet.microsoft.com/en-us/download/dotnet/%s" -3881,optsUnrecognizedLanguageFeature,"Unrecognized language feature name: '%s'. Use a valid feature name such as 'NameOf' or 'StringInterpolation'." \ No newline at end of file +3881,optsUnrecognizedLanguageFeature,"Unrecognized language feature name: '%s'. Use a valid feature name such as 'NameOf' or 'StringInterpolation'." diff --git a/src/Compiler/FSharp.Compiler.Service.fsproj b/src/Compiler/FSharp.Compiler.Service.fsproj index 6bd4b798cda..85cd0fa173e 100644 --- a/src/Compiler/FSharp.Compiler.Service.fsproj +++ b/src/Compiler/FSharp.Compiler.Service.fsproj @@ -368,6 +368,8 @@ + + diff --git a/src/Compiler/Facilities/LanguageFeatures.fs b/src/Compiler/Facilities/LanguageFeatures.fs index 9fb00722263..418f9887874 100644 --- a/src/Compiler/Facilities/LanguageFeatures.fs +++ b/src/Compiler/Facilities/LanguageFeatures.fs @@ -104,6 +104,8 @@ type LanguageFeature = | ErrorOnInvalidDeclsInTypeDefinitions | AllowTypedLetUseAndBang | ReturnFromFinal + | MoreConcreteTiebreaker + | OverloadResolutionPriority /// LanguageVersion management type LanguageVersion(versionText, ?disabledFeaturesArray: LanguageFeature array) = @@ -242,6 +244,8 @@ type LanguageVersion(versionText, ?disabledFeaturesArray: LanguageFeature array) LanguageFeature.AllowAccessModifiersToAutoPropertiesGettersAndSetters, languageVersion100 LanguageFeature.ReturnFromFinal, languageVersion100 LanguageFeature.ErrorOnInvalidDeclsInTypeDefinitions, languageVersion100 + LanguageFeature.MoreConcreteTiebreaker, previewVersion + LanguageFeature.OverloadResolutionPriority, previewVersion // F# 11.0 // Put stabilized features here for F# 11.0 previews via .NET SDK preview channels @@ -440,6 +444,8 @@ type LanguageVersion(versionText, ?disabledFeaturesArray: LanguageFeature array) | LanguageFeature.ErrorOnInvalidDeclsInTypeDefinitions -> FSComp.SR.featureErrorOnInvalidDeclsInTypeDefinitions () | LanguageFeature.AllowTypedLetUseAndBang -> FSComp.SR.featureAllowLetOrUseBangTypeAnnotationWithoutParens () | LanguageFeature.ReturnFromFinal -> FSComp.SR.featureReturnFromFinal () + | LanguageFeature.MoreConcreteTiebreaker -> FSComp.SR.featureMoreConcreteTiebreaker () + | LanguageFeature.OverloadResolutionPriority -> FSComp.SR.featureOverloadResolutionPriority () /// Get a version string associated with the given feature. static member GetFeatureVersionString feature = diff --git a/src/Compiler/Facilities/LanguageFeatures.fsi b/src/Compiler/Facilities/LanguageFeatures.fsi index 09cb4273571..4c23e1db966 100644 --- a/src/Compiler/Facilities/LanguageFeatures.fsi +++ b/src/Compiler/Facilities/LanguageFeatures.fsi @@ -95,6 +95,8 @@ type LanguageFeature = | ErrorOnInvalidDeclsInTypeDefinitions | AllowTypedLetUseAndBang | ReturnFromFinal + | MoreConcreteTiebreaker + | OverloadResolutionPriority /// LanguageVersion management type LanguageVersion = diff --git a/src/Compiler/TypedTree/TcGlobals.fs b/src/Compiler/TypedTree/TcGlobals.fs index 3e0bdfd0905..5e6410948a2 100644 --- a/src/Compiler/TypedTree/TcGlobals.fs +++ b/src/Compiler/TypedTree/TcGlobals.fs @@ -1507,6 +1507,7 @@ type TcGlobals( member val attrib_CallerMemberNameAttribute = findSysAttrib "System.Runtime.CompilerServices.CallerMemberNameAttribute" member val attrib_SkipLocalsInitAttribute = findSysAttrib "System.Runtime.CompilerServices.SkipLocalsInitAttribute" member val attrib_DecimalConstantAttribute = findSysAttrib "System.Runtime.CompilerServices.DecimalConstantAttribute" + member val attrib_OverloadResolutionPriorityAttribute = tryFindSysAttrib "System.Runtime.CompilerServices.OverloadResolutionPriorityAttribute" member val attribs_Unsupported = v_attribs_Unsupported member val attrib_ProjectionParameterAttribute = mk_MFCore_attrib "ProjectionParameterAttribute" diff --git a/src/Compiler/TypedTree/TcGlobals.fsi b/src/Compiler/TypedTree/TcGlobals.fsi index e69bc7b5e80..165284ae46e 100644 --- a/src/Compiler/TypedTree/TcGlobals.fsi +++ b/src/Compiler/TypedTree/TcGlobals.fsi @@ -474,6 +474,8 @@ type internal TcGlobals = member attrib_DecimalConstantAttribute: BuiltinAttribInfo + member attrib_OverloadResolutionPriorityAttribute: BuiltinAttribInfo option + member attrib_StructAttribute: BuiltinAttribInfo member attrib_StructLayoutAttribute: BuiltinAttribInfo diff --git a/src/Compiler/TypedTree/TypedTreeOps.fs b/src/Compiler/TypedTree/TypedTreeOps.fs index 89985c45ce5..fd7e56e1321 100644 --- a/src/Compiler/TypedTree/TypedTreeOps.fs +++ b/src/Compiler/TypedTree/TypedTreeOps.fs @@ -3566,6 +3566,11 @@ let TryFindFSharpInt32Attribute g nm attrs = match TryFindFSharpAttribute g nm attrs with | Some(Attrib(_, _, [ AttribInt32Arg b ], _, _, _, _)) -> Some b | _ -> None + +let TryFindFSharpInt32AttributeOpt g nmOpt attrs = + match nmOpt with + | Some nm -> TryFindFSharpInt32Attribute g nm attrs + | None -> None let TryFindFSharpStringAttribute g nm attrs = match TryFindFSharpAttribute g nm attrs with @@ -3588,6 +3593,11 @@ let TryFindILAttributeOpt attr attrs = | Some (AttribInfo (atref, _)) -> HasILAttribute atref attrs | _ -> false +let TryDecodeILAttributeOpt attr attrs = + match attr with + | Some (AttribInfo (atref, _)) -> TryDecodeILAttribute atref attrs + | _ -> None + let IsILAttrib (AttribInfo (builtInAttrRef, _)) attr = isILAttrib builtInAttrRef attr diff --git a/src/Compiler/TypedTree/TypedTreeOps.fsi b/src/Compiler/TypedTree/TypedTreeOps.fsi index e5bff312024..96f84ce9c0c 100755 --- a/src/Compiler/TypedTree/TypedTreeOps.fsi +++ b/src/Compiler/TypedTree/TypedTreeOps.fsi @@ -2364,6 +2364,9 @@ val TryFindILAttribute: BuiltinAttribInfo -> ILAttributes -> bool val TryFindILAttributeOpt: BuiltinAttribInfo option -> ILAttributes -> bool +val TryDecodeILAttributeOpt: + BuiltinAttribInfo option -> ILAttributes -> (ILAttribElem list * ILAttributeNamedArg list) option + val IsMatchingFSharpAttribute: TcGlobals -> BuiltinAttribInfo -> Attrib -> bool val IsMatchingFSharpAttributeOpt: TcGlobals -> BuiltinAttribInfo option -> Attrib -> bool @@ -2386,6 +2389,8 @@ val TryFindLocalizedFSharpStringAttribute: TcGlobals -> BuiltinAttribInfo -> Att val TryFindFSharpInt32Attribute: TcGlobals -> BuiltinAttribInfo -> Attribs -> int32 option +val TryFindFSharpInt32AttributeOpt: TcGlobals -> BuiltinAttribInfo option -> Attribs -> int32 option + /// Try to find a specific attribute on a type definition, where the attribute accepts a string argument. /// /// This is used to detect the 'DefaultMemberAttribute' and 'ConditionalAttribute' attributes (on type definitions) diff --git a/src/Compiler/xlf/FSComp.txt.cs.xlf b/src/Compiler/xlf/FSComp.txt.cs.xlf index 85dbf4dd95c..c674dc081fb 100644 --- a/src/Compiler/xlf/FSComp.txt.cs.xlf +++ b/src/Compiler/xlf/FSComp.txt.cs.xlf @@ -187,6 +187,11 @@ Obecná konstrukce vyžaduje, aby byl parametr obecného typu známý jako typ struct nebo reference. Zvažte možnost přidat anotaci typu. + + Neither candidate is strictly more concrete than the other:\n{0} + Neither candidate is strictly more concrete than the other:\n{0} + + Known types of arguments: {0} Známé typy argumentů: {0} @@ -497,6 +502,11 @@ Zahození shody vzoru není povolené pro případ sjednocení, který nepřijímá žádná data. + + Use 'most concrete' tiebreaker for overload resolution when methods differ only by type parameter concreteness. + Use 'most concrete' tiebreaker for overload resolution when methods differ only by type parameter concreteness. + + nameof nameof @@ -532,6 +542,11 @@ Otevřít deklaraci typu + + Support for OverloadResolutionPriorityAttribute to prioritize method overloads. + Support for OverloadResolutionPriorityAttribute to prioritize method overloads. + + overloads for custom operations přetížení pro vlastní operace @@ -1457,6 +1472,11 @@ Expected unit-of-measure type parameter must be marked with the [<Measure>] attribute. + + A more generic overload was bypassed: '{0}'. The selected overload '{1}' was chosen because it has more concrete type parameters. + A more generic overload was bypassed: '{0}'. The selected overload '{1}' was chosen because it has more concrete type parameters. + + The syntax 'expr1[expr2]' is used for indexing. Consider adding a type annotation to enable indexing, or if calling a function add a space, e.g. 'expr1 [expr2]'. Syntaxe expr1[expr2] se používá pro indexování. Pokud chcete povolit indexování, zvažte možnost přidat anotaci typu, nebo pokud voláte funkci, přidejte mezeru, třeba expr1 [expr2]. @@ -1577,6 +1597,11 @@ The following required properties have to be initialized:{0} + + Overload resolution selected '{0}' based on type concreteness. The more concrete type '{1}' was preferred over '{2}'. This is an informational message and can be enabled with --warnon:3575. + Overload resolution selected '{0}' based on type concreteness. The more concrete type '{1}' was preferred over '{2}'. This is an informational message and can be enabled with --warnon:3575. + + The field '{0}' appears multiple times in this record expression or pattern Pole „{0}“ se v tomto výrazu nebo vzoru záznamu zobrazuje vícekrát @@ -1641,6 +1666,7 @@ The syntax 'expr1[expr2]' is now reserved for indexing and is ambiguous when used as an argument. See https://aka.ms/fsharp-index-notation. If calling a function with multiple curried arguments, add a space between them, e.g. 'someFunction expr1 [expr2]'. Syntaxe expr1[expr2] je teď vyhrazena pro indexování a je při použití jako argument nejednoznačná. Více informací: https://aka.ms/fsharp-index-notation. Pokud voláte funkci s vícenásobnými curryfikovanými argumenty, přidejte mezi ně mezeru, třeba someFunction expr1 [expr2]. + This override takes a tuple instead of multiple arguments. Try to add an additional layer of parentheses at the method definition (e.g. 'member _.Foo((x, y))'), or remove parentheses at the abstract method declaration (e.g. 'abstract member Foo: 'a * 'b -> 'c'). @@ -1742,6 +1768,11 @@ Pokud je typ sjednocení s více písmeny strukturou, musí být všechna pole se stejným názvem stejného typu. Toto pravidlo platí také pro vygenerovaný název Item v případě nepojmenovaných polí. + + The 'OverloadResolutionPriorityAttribute' cannot be applied to an override member. Apply it to the original declaration instead. + The 'OverloadResolutionPriorityAttribute' cannot be applied to an override member. Apply it to the original declaration instead. + + This expression implicitly converts type '{0}' to type '{1}'. See https://aka.ms/fsharp-implicit-convs. Tento výraz implicitně převede typ {0} na typ {1}. Přečtěte si téma https://aka.ms/fsharp-implicit-convs. diff --git a/src/Compiler/xlf/FSComp.txt.de.xlf b/src/Compiler/xlf/FSComp.txt.de.xlf index 370e248e1d6..873c2f893f4 100644 --- a/src/Compiler/xlf/FSComp.txt.de.xlf +++ b/src/Compiler/xlf/FSComp.txt.de.xlf @@ -187,6 +187,11 @@ Für ein generisches Konstrukt muss ein generischer Typparameter als Struktur- oder Verweistyp bekannt sein. Erwägen Sie das Hinzufügen einer Typanmerkung. + + Neither candidate is strictly more concrete than the other:\n{0} + Neither candidate is strictly more concrete than the other:\n{0} + + Known types of arguments: {0} Bekannte Argumenttypen: {0} @@ -497,6 +502,11 @@ Das Verwerfen von Musterübereinstimmungen ist für einen Union-Fall, der keine Daten akzeptiert, nicht zulässig. + + Use 'most concrete' tiebreaker for overload resolution when methods differ only by type parameter concreteness. + Use 'most concrete' tiebreaker for overload resolution when methods differ only by type parameter concreteness. + + nameof nameof @@ -532,6 +542,11 @@ Deklaration für offene Typen + + Support for OverloadResolutionPriorityAttribute to prioritize method overloads. + Support for OverloadResolutionPriorityAttribute to prioritize method overloads. + + overloads for custom operations Überladungen für benutzerdefinierte Vorgänge @@ -1457,6 +1472,11 @@ Expected unit-of-measure type parameter must be marked with the [<Measure>] attribute. + + A more generic overload was bypassed: '{0}'. The selected overload '{1}' was chosen because it has more concrete type parameters. + A more generic overload was bypassed: '{0}'. The selected overload '{1}' was chosen because it has more concrete type parameters. + + The syntax 'expr1[expr2]' is used for indexing. Consider adding a type annotation to enable indexing, or if calling a function add a space, e.g. 'expr1 [expr2]'. Die Syntax "expr1[expr2]" wird für die Indizierung verwendet. Fügen Sie ggf. eine Typanmerkung hinzu, um die Indizierung zu aktivieren, oder fügen Sie beim Aufrufen einer Funktion ein Leerzeichen hinzu, z. B. "expr1 [expr2]". @@ -1577,6 +1597,11 @@ The following required properties have to be initialized:{0} + + Overload resolution selected '{0}' based on type concreteness. The more concrete type '{1}' was preferred over '{2}'. This is an informational message and can be enabled with --warnon:3575. + Overload resolution selected '{0}' based on type concreteness. The more concrete type '{1}' was preferred over '{2}'. This is an informational message and can be enabled with --warnon:3575. + + The field '{0}' appears multiple times in this record expression or pattern Das Feld "{0}" ist in diesem Datensatzausdruck oder Muster mehrmals vorhanden. @@ -1641,6 +1666,7 @@ The syntax 'expr1[expr2]' is now reserved for indexing and is ambiguous when used as an argument. See https://aka.ms/fsharp-index-notation. If calling a function with multiple curried arguments, add a space between them, e.g. 'someFunction expr1 [expr2]'. Die Syntax "expr1[expr2]" ist jetzt für die Indizierung reserviert und mehrdeutig, wenn sie als Argument verwendet wird. Siehe https://aka.ms/fsharp-index-notation. Wenn Sie eine Funktion mit mehreren geschweiften Argumenten aufrufen, fügen Sie ein Leerzeichen dazwischen hinzu, z. B. "someFunction expr1 [expr2]". + This override takes a tuple instead of multiple arguments. Try to add an additional layer of parentheses at the method definition (e.g. 'member _.Foo((x, y))'), or remove parentheses at the abstract method declaration (e.g. 'abstract member Foo: 'a * 'b -> 'c'). @@ -1742,6 +1768,11 @@ Wenn ein Union-Typ mit mehreren Großbuchstaben eine Struktur ist, müssen alle Felder mit demselben Namen denselben Typ aufweisen. Diese Regel gilt auch für den generierten Elementnamen bei unbenannten Feldern. + + The 'OverloadResolutionPriorityAttribute' cannot be applied to an override member. Apply it to the original declaration instead. + The 'OverloadResolutionPriorityAttribute' cannot be applied to an override member. Apply it to the original declaration instead. + + This expression implicitly converts type '{0}' to type '{1}'. See https://aka.ms/fsharp-implicit-convs. Dieser Ausdruck konvertiert den Typ "{0}" implizit in den Typ "{1}". Siehe https://aka.ms/fsharp-implicit-convs. diff --git a/src/Compiler/xlf/FSComp.txt.es.xlf b/src/Compiler/xlf/FSComp.txt.es.xlf index c760506b835..3e58a8be605 100644 --- a/src/Compiler/xlf/FSComp.txt.es.xlf +++ b/src/Compiler/xlf/FSComp.txt.es.xlf @@ -187,6 +187,11 @@ Una construcción genérica requiere que un parámetro de tipo genérico se conozca como tipo de referencia o estructura. Puede agregar una anotación de tipo. + + Neither candidate is strictly more concrete than the other:\n{0} + Neither candidate is strictly more concrete than the other:\n{0} + + Known types of arguments: {0} Tipos de argumentos conocidos: {0} @@ -497,6 +502,11 @@ No se permite el descarte de coincidencia de patrón para un caso de unión que no tome datos. + + Use 'most concrete' tiebreaker for overload resolution when methods differ only by type parameter concreteness. + Use 'most concrete' tiebreaker for overload resolution when methods differ only by type parameter concreteness. + + nameof nameof @@ -532,6 +542,11 @@ declaración de tipo abierto + + Support for OverloadResolutionPriorityAttribute to prioritize method overloads. + Support for OverloadResolutionPriorityAttribute to prioritize method overloads. + + overloads for custom operations sobrecargas para operaciones personalizadas @@ -1457,6 +1472,11 @@ Expected unit-of-measure type parameter must be marked with the [<Measure>] attribute. + + A more generic overload was bypassed: '{0}'. The selected overload '{1}' was chosen because it has more concrete type parameters. + A more generic overload was bypassed: '{0}'. The selected overload '{1}' was chosen because it has more concrete type parameters. + + The syntax 'expr1[expr2]' is used for indexing. Consider adding a type annotation to enable indexing, or if calling a function add a space, e.g. 'expr1 [expr2]'. La sintaxis "expr1[expr2]" se usa para la indexación. Considere la posibilidad de agregar una anotación de tipo para habilitar la indexación, si se llama a una función, agregue un espacio, por ejemplo, "expr1 [expr2]". @@ -1577,6 +1597,11 @@ The following required properties have to be initialized:{0} + + Overload resolution selected '{0}' based on type concreteness. The more concrete type '{1}' was preferred over '{2}'. This is an informational message and can be enabled with --warnon:3575. + Overload resolution selected '{0}' based on type concreteness. The more concrete type '{1}' was preferred over '{2}'. This is an informational message and can be enabled with --warnon:3575. + + The field '{0}' appears multiple times in this record expression or pattern El campo “{0}” aparece varias veces en esta expresión o patrón de registro. @@ -1641,6 +1666,7 @@ The syntax 'expr1[expr2]' is now reserved for indexing and is ambiguous when used as an argument. See https://aka.ms/fsharp-index-notation. If calling a function with multiple curried arguments, add a space between them, e.g. 'someFunction expr1 [expr2]'. La sintaxis "expr1[expr2]" está reservada ahora para la indexación y es ambigua cuando se usa como argumento. Vea https://aka.ms/fsharp-index-notation. Si se llama a una función con varios argumentos currificados, agregue un espacio entre ellos, por ejemplo, "unaFunción expr1 [expr2]". + This override takes a tuple instead of multiple arguments. Try to add an additional layer of parentheses at the method definition (e.g. 'member _.Foo((x, y))'), or remove parentheses at the abstract method declaration (e.g. 'abstract member Foo: 'a * 'b -> 'c'). @@ -1742,6 +1768,11 @@ Si un tipo de unión multicase es un struct, todos los campos con el mismo nombre deben ser del mismo tipo. Esta regla se aplica también al nombre "Item" generado en el caso de campos sin nombre. + + The 'OverloadResolutionPriorityAttribute' cannot be applied to an override member. Apply it to the original declaration instead. + The 'OverloadResolutionPriorityAttribute' cannot be applied to an override member. Apply it to the original declaration instead. + + This expression implicitly converts type '{0}' to type '{1}'. See https://aka.ms/fsharp-implicit-convs. Esta expresión convierte implícitamente el tipo '{0}' al tipo '{1}'. Consulte https://aka.ms/fsharp-implicit-convs. diff --git a/src/Compiler/xlf/FSComp.txt.fr.xlf b/src/Compiler/xlf/FSComp.txt.fr.xlf index 94935d998ff..dc04583fe4f 100644 --- a/src/Compiler/xlf/FSComp.txt.fr.xlf +++ b/src/Compiler/xlf/FSComp.txt.fr.xlf @@ -187,6 +187,11 @@ L'utilisation d'une construction générique est possible uniquement si un paramètre de type générique est connu en tant que type struct ou type référence. Ajoutez une annotation de type. + + Neither candidate is strictly more concrete than the other:\n{0} + Neither candidate is strictly more concrete than the other:\n{0} + + Known types of arguments: {0} Types d'argument connus : {0} @@ -497,6 +502,11 @@ L’abandon des correspondances de modèle n’est pas autorisé pour un cas d’union qui n’accepte aucune donnée. + + Use 'most concrete' tiebreaker for overload resolution when methods differ only by type parameter concreteness. + Use 'most concrete' tiebreaker for overload resolution when methods differ only by type parameter concreteness. + + nameof nameof @@ -532,6 +542,11 @@ déclaration de type ouverte + + Support for OverloadResolutionPriorityAttribute to prioritize method overloads. + Support for OverloadResolutionPriorityAttribute to prioritize method overloads. + + overloads for custom operations surcharges pour les opérations personnalisées @@ -1457,6 +1472,11 @@ Expected unit-of-measure type parameter must be marked with the [<Measure>] attribute. + + A more generic overload was bypassed: '{0}'. The selected overload '{1}' was chosen because it has more concrete type parameters. + A more generic overload was bypassed: '{0}'. The selected overload '{1}' was chosen because it has more concrete type parameters. + + The syntax 'expr1[expr2]' is used for indexing. Consider adding a type annotation to enable indexing, or if calling a function add a space, e.g. 'expr1 [expr2]'. La syntaxe « expr1[expr2] » est utilisée pour l’indexation. Envisagez d’ajouter une annotation de type pour activer l’indexation, ou si vous appelez une fonction, ajoutez un espace, par exemple « expr1 [expr2] ». @@ -1577,6 +1597,11 @@ The following required properties have to be initialized:{0} + + Overload resolution selected '{0}' based on type concreteness. The more concrete type '{1}' was preferred over '{2}'. This is an informational message and can be enabled with --warnon:3575. + Overload resolution selected '{0}' based on type concreteness. The more concrete type '{1}' was preferred over '{2}'. This is an informational message and can be enabled with --warnon:3575. + + The field '{0}' appears multiple times in this record expression or pattern Le champ « {0} » apparaît plusieurs fois dans cette expression ou modèle d'enregistrement. @@ -1641,6 +1666,7 @@ The syntax 'expr1[expr2]' is now reserved for indexing and is ambiguous when used as an argument. See https://aka.ms/fsharp-index-notation. If calling a function with multiple curried arguments, add a space between them, e.g. 'someFunction expr1 [expr2]'. La syntaxe « expr1[expr2] » est désormais réservée à l’indexation et est ambiguë lorsqu’elle est utilisée comme argument. Voir https://aka.ms/fsharp-index-notation. Si vous appelez une fonction avec plusieurs arguments codés, ajoutez un espace entre eux, par exemple « someFunction expr1 [expr2] ». + This override takes a tuple instead of multiple arguments. Try to add an additional layer of parentheses at the method definition (e.g. 'member _.Foo((x, y))'), or remove parentheses at the abstract method declaration (e.g. 'abstract member Foo: 'a * 'b -> 'c'). @@ -1742,6 +1768,11 @@ Si un type union multicase est un struct, tous les champs portant le même nom doivent être du même type. Cette règle s’applique également au nom « Item » généré en cas de champs sans nom. + + The 'OverloadResolutionPriorityAttribute' cannot be applied to an override member. Apply it to the original declaration instead. + The 'OverloadResolutionPriorityAttribute' cannot be applied to an override member. Apply it to the original declaration instead. + + This expression implicitly converts type '{0}' to type '{1}'. See https://aka.ms/fsharp-implicit-convs. Cette expression convertit implicitement le type « {0} » en type « {1} ». Voir https://aka.ms/fsharp-implicit-convs. diff --git a/src/Compiler/xlf/FSComp.txt.it.xlf b/src/Compiler/xlf/FSComp.txt.it.xlf index e83cb026ed3..c40d1d2aa4c 100644 --- a/src/Compiler/xlf/FSComp.txt.it.xlf +++ b/src/Compiler/xlf/FSComp.txt.it.xlf @@ -187,6 +187,11 @@ Un costrutto generico richiede che un parametro di tipo generico sia noto come tipo riferimento o struct. Provare ad aggiungere un'annotazione di tipo. + + Neither candidate is strictly more concrete than the other:\n{0} + Neither candidate is strictly more concrete than the other:\n{0} + + Known types of arguments: {0} Tipi di argomenti noti: {0} @@ -497,6 +502,11 @@ L'eliminazione della corrispondenza dei criteri non è consentita per case di unione che non accetta dati. + + Use 'most concrete' tiebreaker for overload resolution when methods differ only by type parameter concreteness. + Use 'most concrete' tiebreaker for overload resolution when methods differ only by type parameter concreteness. + + nameof nameof @@ -532,6 +542,11 @@ dichiarazione di tipo aperto + + Support for OverloadResolutionPriorityAttribute to prioritize method overloads. + Support for OverloadResolutionPriorityAttribute to prioritize method overloads. + + overloads for custom operations overload per le operazioni personalizzate @@ -1457,6 +1472,11 @@ Expected unit-of-measure type parameter must be marked with the [<Measure>] attribute. + + A more generic overload was bypassed: '{0}'. The selected overload '{1}' was chosen because it has more concrete type parameters. + A more generic overload was bypassed: '{0}'. The selected overload '{1}' was chosen because it has more concrete type parameters. + + The syntax 'expr1[expr2]' is used for indexing. Consider adding a type annotation to enable indexing, or if calling a function add a space, e.g. 'expr1 [expr2]'. La sintassi 'expr1[expr2]' viene usata per l'indicizzazione. Provare ad aggiungere un'annotazione di tipo per abilitare l'indicizzazione oppure se la chiamata a una funzione aggiunge uno spazio, ad esempio 'expr1 [expr2]'. @@ -1577,6 +1597,11 @@ The following required properties have to be initialized:{0} + + Overload resolution selected '{0}' based on type concreteness. The more concrete type '{1}' was preferred over '{2}'. This is an informational message and can be enabled with --warnon:3575. + Overload resolution selected '{0}' based on type concreteness. The more concrete type '{1}' was preferred over '{2}'. This is an informational message and can be enabled with --warnon:3575. + + The field '{0}' appears multiple times in this record expression or pattern Il campo "{0}" viene visualizzato più volte in questa espressione di record o criterio. @@ -1641,6 +1666,7 @@ The syntax 'expr1[expr2]' is now reserved for indexing and is ambiguous when used as an argument. See https://aka.ms/fsharp-index-notation. If calling a function with multiple curried arguments, add a space between them, e.g. 'someFunction expr1 [expr2]'. La sintassi 'expr1[expr2]' è ora riservata per l'indicizzazione ed è ambigua quando usata come argomento. Vedere https://aka.ms/fsharp-index-notation. Se si chiama una funzione con più argomenti sottoposti a corsi, aggiungere uno spazio tra di essi, ad esempio 'someFunction expr1 [expr2]'. + This override takes a tuple instead of multiple arguments. Try to add an additional layer of parentheses at the method definition (e.g. 'member _.Foo((x, y))'), or remove parentheses at the abstract method declaration (e.g. 'abstract member Foo: 'a * 'b -> 'c'). @@ -1742,6 +1768,11 @@ Se un tipo di unione multicase è uno struct, tutti i campi con lo stesso nome devono essere dello stesso tipo. Questa regola si applica anche al nome 'Elemento' generato in caso di campi senza nome. + + The 'OverloadResolutionPriorityAttribute' cannot be applied to an override member. Apply it to the original declaration instead. + The 'OverloadResolutionPriorityAttribute' cannot be applied to an override member. Apply it to the original declaration instead. + + This expression implicitly converts type '{0}' to type '{1}'. See https://aka.ms/fsharp-implicit-convs. Questa espressione converte in modo implicito il tipo '{0}' nel tipo '{1}'. Vedere https://aka.ms/fsharp-implicit-convs. diff --git a/src/Compiler/xlf/FSComp.txt.ja.xlf b/src/Compiler/xlf/FSComp.txt.ja.xlf index 9888850c875..900501ea278 100644 --- a/src/Compiler/xlf/FSComp.txt.ja.xlf +++ b/src/Compiler/xlf/FSComp.txt.ja.xlf @@ -187,6 +187,11 @@ ジェネリック コンストラクトでは、ジェネリック型パラメーターが構造体または参照型として認識されている必要があります。型の注釈の追加を検討してください。 + + Neither candidate is strictly more concrete than the other:\n{0} + Neither candidate is strictly more concrete than the other:\n{0} + + Known types of arguments: {0} 既知の型の引数: {0} @@ -497,6 +502,11 @@ データを受け取らない共用体ケースでは、パターン一致の破棄は許可されません。 + + Use 'most concrete' tiebreaker for overload resolution when methods differ only by type parameter concreteness. + Use 'most concrete' tiebreaker for overload resolution when methods differ only by type parameter concreteness. + + nameof nameof @@ -532,6 +542,11 @@ オープン型宣言 + + Support for OverloadResolutionPriorityAttribute to prioritize method overloads. + Support for OverloadResolutionPriorityAttribute to prioritize method overloads. + + overloads for custom operations カスタム操作のオーバーロード @@ -1457,6 +1472,11 @@ Expected unit-of-measure type parameter must be marked with the [<Measure>] attribute. + + A more generic overload was bypassed: '{0}'. The selected overload '{1}' was chosen because it has more concrete type parameters. + A more generic overload was bypassed: '{0}'. The selected overload '{1}' was chosen because it has more concrete type parameters. + + The syntax 'expr1[expr2]' is used for indexing. Consider adding a type annotation to enable indexing, or if calling a function add a space, e.g. 'expr1 [expr2]'. 構文 'expr1[expr2]' はインデックス作成に使用されます。インデックスを有効にするために型の注釈を追加するか、関数を呼び出す場合には、'expr1 [expr2]' のようにスペースを入れます。 @@ -1577,6 +1597,11 @@ The following required properties have to be initialized:{0} + + Overload resolution selected '{0}' based on type concreteness. The more concrete type '{1}' was preferred over '{2}'. This is an informational message and can be enabled with --warnon:3575. + Overload resolution selected '{0}' based on type concreteness. The more concrete type '{1}' was preferred over '{2}'. This is an informational message and can be enabled with --warnon:3575. + + The field '{0}' appears multiple times in this record expression or pattern フィールド '{0}' は、このレコード式またはパターンに複数回出現します @@ -1641,6 +1666,7 @@ The syntax 'expr1[expr2]' is now reserved for indexing and is ambiguous when used as an argument. See https://aka.ms/fsharp-index-notation. If calling a function with multiple curried arguments, add a space between them, e.g. 'someFunction expr1 [expr2]'. 構文 'expr1[expr2]' は引数として使用されている場合、あいまいです。https://aka.ms/fsharp-index-notation を参照してください。複数のカリー化された引数を持つ関数を呼び出す場合には、'someFunction expr1 [expr2]' のように間にスペースを追加します。 + This override takes a tuple instead of multiple arguments. Try to add an additional layer of parentheses at the method definition (e.g. 'member _.Foo((x, y))'), or remove parentheses at the abstract method declaration (e.g. 'abstract member Foo: 'a * 'b -> 'c'). @@ -1742,6 +1768,11 @@ マルチケース共用体型が構造体の場合、同じ名前を持つすべてのフィールドが同じ型である必要があります。このルールは、名前のないフィールドの場合に生成された 'Item' 名にも適用されます。 + + The 'OverloadResolutionPriorityAttribute' cannot be applied to an override member. Apply it to the original declaration instead. + The 'OverloadResolutionPriorityAttribute' cannot be applied to an override member. Apply it to the original declaration instead. + + This expression implicitly converts type '{0}' to type '{1}'. See https://aka.ms/fsharp-implicit-convs. この式は、型 '{0}' を型 '{1}' に暗黙的に変換します。https://aka.ms/fsharp-implicit-convs を参照してください。 diff --git a/src/Compiler/xlf/FSComp.txt.ko.xlf b/src/Compiler/xlf/FSComp.txt.ko.xlf index 433af22b44b..7b5ba80212b 100644 --- a/src/Compiler/xlf/FSComp.txt.ko.xlf +++ b/src/Compiler/xlf/FSComp.txt.ko.xlf @@ -187,6 +187,11 @@ 제네릭 구문을 사용하려면 구조체 또는 참조 형식의 제네릭 형식 매개 변수가 필요합니다. 형식 주석을 추가하세요. + + Neither candidate is strictly more concrete than the other:\n{0} + Neither candidate is strictly more concrete than the other:\n{0} + + Known types of arguments: {0} 알려진 인수 형식: {0} @@ -497,6 +502,11 @@ 데이터를 사용하지 않는 공용 구조체 사례에는 패턴 일치 삭제가 허용되지 않습니다. + + Use 'most concrete' tiebreaker for overload resolution when methods differ only by type parameter concreteness. + Use 'most concrete' tiebreaker for overload resolution when methods differ only by type parameter concreteness. + + nameof nameof @@ -532,6 +542,11 @@ 개방형 형식 선언 + + Support for OverloadResolutionPriorityAttribute to prioritize method overloads. + Support for OverloadResolutionPriorityAttribute to prioritize method overloads. + + overloads for custom operations 사용자 지정 작업의 오버로드 @@ -1457,6 +1472,11 @@ Expected unit-of-measure type parameter must be marked with the [<Measure>] attribute. + + A more generic overload was bypassed: '{0}'. The selected overload '{1}' was chosen because it has more concrete type parameters. + A more generic overload was bypassed: '{0}'. The selected overload '{1}' was chosen because it has more concrete type parameters. + + The syntax 'expr1[expr2]' is used for indexing. Consider adding a type annotation to enable indexing, or if calling a function add a space, e.g. 'expr1 [expr2]'. 인덱싱에는 'expr1[expr2]' 구문이 사용됩니다. 인덱싱을 사용하도록 설정하기 위해 형식 주석을 추가하는 것을 고려하거나 함수를 호출하는 경우 공백을 추가하세요(예: 'expr1 [expr2]'). @@ -1577,6 +1597,11 @@ The following required properties have to be initialized:{0} + + Overload resolution selected '{0}' based on type concreteness. The more concrete type '{1}' was preferred over '{2}'. This is an informational message and can be enabled with --warnon:3575. + Overload resolution selected '{0}' based on type concreteness. The more concrete type '{1}' was preferred over '{2}'. This is an informational message and can be enabled with --warnon:3575. + + The field '{0}' appears multiple times in this record expression or pattern '{0}' 필드가 이 레코드 식 또는 패턴에 여러 번 나타납니다. @@ -1641,6 +1666,7 @@ The syntax 'expr1[expr2]' is now reserved for indexing and is ambiguous when used as an argument. See https://aka.ms/fsharp-index-notation. If calling a function with multiple curried arguments, add a space between them, e.g. 'someFunction expr1 [expr2]'. 구문 'expr1[expr2]'은 이제 인덱싱용으로 예약되어 있으며 인수로 사용될 때 모호합니다. https://aka.ms/fsharp-index-notation을 참조하세요. 여러 개의 커리된 인수로 함수를 호출하는 경우 그 사이에 공백을 추가하세요(예: 'someFunction expr1 [expr2]'). + This override takes a tuple instead of multiple arguments. Try to add an additional layer of parentheses at the method definition (e.g. 'member _.Foo((x, y))'), or remove parentheses at the abstract method declaration (e.g. 'abstract member Foo: 'a * 'b -> 'c'). @@ -1742,6 +1768,11 @@ 멀티캐시 공용 구조체 형식이 구조체이면 이름이 같은 모든 필드의 형식이 같아야 합니다. 이 규칙은 명명되지 않은 필드의 경우 생성된 '항목' 이름에도 적용됩니다. + + The 'OverloadResolutionPriorityAttribute' cannot be applied to an override member. Apply it to the original declaration instead. + The 'OverloadResolutionPriorityAttribute' cannot be applied to an override member. Apply it to the original declaration instead. + + This expression implicitly converts type '{0}' to type '{1}'. See https://aka.ms/fsharp-implicit-convs. 이 식은 암시적으로 '{0}' 형식을 '{1}' 형식으로 변환 합니다. https://aka.ms/fsharp-implicit-convs 참조 diff --git a/src/Compiler/xlf/FSComp.txt.pl.xlf b/src/Compiler/xlf/FSComp.txt.pl.xlf index 8d167f8c5c4..99cdbe82ce0 100644 --- a/src/Compiler/xlf/FSComp.txt.pl.xlf +++ b/src/Compiler/xlf/FSComp.txt.pl.xlf @@ -187,6 +187,11 @@ Konstrukcja ogólna wymaga, aby parametr typu ogólnego był znany jako struktura lub typ referencyjny. Rozważ dodanie adnotacji typu. + + Neither candidate is strictly more concrete than the other:\n{0} + Neither candidate is strictly more concrete than the other:\n{0} + + Known types of arguments: {0} Znane typy argumentów: {0} @@ -497,6 +502,11 @@ Odrzucenie dopasowania wzorca jest niedozwolone w przypadku unii, która nie pobiera żadnych danych. + + Use 'most concrete' tiebreaker for overload resolution when methods differ only by type parameter concreteness. + Use 'most concrete' tiebreaker for overload resolution when methods differ only by type parameter concreteness. + + nameof nameof @@ -532,6 +542,11 @@ deklaracja typu otwartego + + Support for OverloadResolutionPriorityAttribute to prioritize method overloads. + Support for OverloadResolutionPriorityAttribute to prioritize method overloads. + + overloads for custom operations przeciążenia dla operacji niestandardowych @@ -1457,6 +1472,11 @@ Expected unit-of-measure type parameter must be marked with the [<Measure>] attribute. + + A more generic overload was bypassed: '{0}'. The selected overload '{1}' was chosen because it has more concrete type parameters. + A more generic overload was bypassed: '{0}'. The selected overload '{1}' was chosen because it has more concrete type parameters. + + The syntax 'expr1[expr2]' is used for indexing. Consider adding a type annotation to enable indexing, or if calling a function add a space, e.g. 'expr1 [expr2]'. Do indeksowania używana jest składnia „expr1[expr2]”. Rozważ dodanie adnotacji typu, aby umożliwić indeksowanie, lub jeśli wywołujesz funkcję dodaj spację, np. „expr1 [expr2]”. @@ -1577,6 +1597,11 @@ The following required properties have to be initialized:{0} + + Overload resolution selected '{0}' based on type concreteness. The more concrete type '{1}' was preferred over '{2}'. This is an informational message and can be enabled with --warnon:3575. + Overload resolution selected '{0}' based on type concreteness. The more concrete type '{1}' was preferred over '{2}'. This is an informational message and can be enabled with --warnon:3575. + + The field '{0}' appears multiple times in this record expression or pattern Pole „{0}” pojawia się wiele razy w tym wyrażeniu rekordu lub wzorcu @@ -1641,6 +1666,7 @@ The syntax 'expr1[expr2]' is now reserved for indexing and is ambiguous when used as an argument. See https://aka.ms/fsharp-index-notation. If calling a function with multiple curried arguments, add a space between them, e.g. 'someFunction expr1 [expr2]'. Składnia wyrażenia „expr1[expr2]” jest teraz zarezerwowana do indeksowania i jest niejednoznaczna, gdy jest używana jako argument. Zobacz: https://aka.ms/fsharp-index-notation. Jeśli wywołujesz funkcję z wieloma argumentami typu curried, dodaj spację między nimi, np. „someFunction expr1 [expr2]”. + This override takes a tuple instead of multiple arguments. Try to add an additional layer of parentheses at the method definition (e.g. 'member _.Foo((x, y))'), or remove parentheses at the abstract method declaration (e.g. 'abstract member Foo: 'a * 'b -> 'c'). @@ -1742,6 +1768,11 @@ Jeśli typ unii wieloskładnikowej jest strukturą, wszystkie pola o tej samej nazwie muszą być tego samego typu. Ta reguła ma zastosowanie również do wygenerowanej nazwy „item” w przypadku pól bez nazwy. + + The 'OverloadResolutionPriorityAttribute' cannot be applied to an override member. Apply it to the original declaration instead. + The 'OverloadResolutionPriorityAttribute' cannot be applied to an override member. Apply it to the original declaration instead. + + This expression implicitly converts type '{0}' to type '{1}'. See https://aka.ms/fsharp-implicit-convs. To wyrażenie bezwzględnie konwertuje typ "{0}" na typ "{1}". Zobacz https://aka.ms/fsharp-implicit-convs. diff --git a/src/Compiler/xlf/FSComp.txt.pt-BR.xlf b/src/Compiler/xlf/FSComp.txt.pt-BR.xlf index 872af8ca069..a7ca5c33e3d 100644 --- a/src/Compiler/xlf/FSComp.txt.pt-BR.xlf +++ b/src/Compiler/xlf/FSComp.txt.pt-BR.xlf @@ -187,6 +187,11 @@ Um constructo genérico exige que um parâmetro de tipo genérico seja conhecido como um tipo de referência ou struct. Considere adicionar uma anotação de tipo. + + Neither candidate is strictly more concrete than the other:\n{0} + Neither candidate is strictly more concrete than the other:\n{0} + + Known types of arguments: {0} Tipos de argumentos conhecidos: {0} @@ -497,6 +502,11 @@ O descarte de correspondência de padrão não é permitido para casos união que não aceitam dados. + + Use 'most concrete' tiebreaker for overload resolution when methods differ only by type parameter concreteness. + Use 'most concrete' tiebreaker for overload resolution when methods differ only by type parameter concreteness. + + nameof nameof @@ -532,6 +542,11 @@ declaração de tipo aberto + + Support for OverloadResolutionPriorityAttribute to prioritize method overloads. + Support for OverloadResolutionPriorityAttribute to prioritize method overloads. + + overloads for custom operations sobrecargas para operações personalizadas @@ -1457,6 +1472,11 @@ Expected unit-of-measure type parameter must be marked with the [<Measure>] attribute. + + A more generic overload was bypassed: '{0}'. The selected overload '{1}' was chosen because it has more concrete type parameters. + A more generic overload was bypassed: '{0}'. The selected overload '{1}' was chosen because it has more concrete type parameters. + + The syntax 'expr1[expr2]' is used for indexing. Consider adding a type annotation to enable indexing, or if calling a function add a space, e.g. 'expr1 [expr2]'. A sintaxe 'expr1[expr2]' é usada para indexação. Considere adicionar uma anotação de tipo para habilitar a indexação ou, se chamar uma função, adicione um espaço, por exemplo, 'expr1 [expr2]'. @@ -1577,6 +1597,11 @@ The following required properties have to be initialized:{0} + + Overload resolution selected '{0}' based on type concreteness. The more concrete type '{1}' was preferred over '{2}'. This is an informational message and can be enabled with --warnon:3575. + Overload resolution selected '{0}' based on type concreteness. The more concrete type '{1}' was preferred over '{2}'. This is an informational message and can be enabled with --warnon:3575. + + The field '{0}' appears multiple times in this record expression or pattern O campo "{0}" aparece várias vezes nesta expressão de registro ou padrão @@ -1641,6 +1666,7 @@ The syntax 'expr1[expr2]' is now reserved for indexing and is ambiguous when used as an argument. See https://aka.ms/fsharp-index-notation. If calling a function with multiple curried arguments, add a space between them, e.g. 'someFunction expr1 [expr2]'. A sintaxe 'expr1[expr2]' agora está reservada para indexação e é ambígua quando usada como um argumento. Consulte https://aka.ms/fsharp-index-notation. Se chamar uma função com vários argumentos na forma curried, adicione um espaço entre eles, por exemplo, 'someFunction expr1 [expr2]'. + This override takes a tuple instead of multiple arguments. Try to add an additional layer of parentheses at the method definition (e.g. 'member _.Foo((x, y))'), or remove parentheses at the abstract method declaration (e.g. 'abstract member Foo: 'a * 'b -> 'c'). @@ -1742,6 +1768,11 @@ Se um tipo de união multicase for um struct, todos os campos com o mesmo nome deverão ser do mesmo tipo. Essa regra também se aplica ao nome 'Item' gerado no caso de campos sem nome. + + The 'OverloadResolutionPriorityAttribute' cannot be applied to an override member. Apply it to the original declaration instead. + The 'OverloadResolutionPriorityAttribute' cannot be applied to an override member. Apply it to the original declaration instead. + + This expression implicitly converts type '{0}' to type '{1}'. See https://aka.ms/fsharp-implicit-convs. Essa expressão converte implicitamente o tipo '{0}' ao tipo '{1}'. Consulte https://aka.ms/fsharp-implicit-convs. diff --git a/src/Compiler/xlf/FSComp.txt.ru.xlf b/src/Compiler/xlf/FSComp.txt.ru.xlf index 940bcf5df31..8f1aebb73ae 100644 --- a/src/Compiler/xlf/FSComp.txt.ru.xlf +++ b/src/Compiler/xlf/FSComp.txt.ru.xlf @@ -187,6 +187,11 @@ В универсальной конструкции требуется использовать параметр универсального типа, известный как структура или ссылочный тип. Рекомендуется добавить заметку с типом. + + Neither candidate is strictly more concrete than the other:\n{0} + Neither candidate is strictly more concrete than the other:\n{0} + + Known types of arguments: {0} Известные типы аргументов: {0} @@ -497,6 +502,11 @@ Отмена сопоставления с шаблоном не разрешена для случая объединения, не принимающего данные. + + Use 'most concrete' tiebreaker for overload resolution when methods differ only by type parameter concreteness. + Use 'most concrete' tiebreaker for overload resolution when methods differ only by type parameter concreteness. + + nameof nameof @@ -532,6 +542,11 @@ объявление открытого типа + + Support for OverloadResolutionPriorityAttribute to prioritize method overloads. + Support for OverloadResolutionPriorityAttribute to prioritize method overloads. + + overloads for custom operations перегрузки для настраиваемых операций @@ -1457,6 +1472,11 @@ Expected unit-of-measure type parameter must be marked with the [<Measure>] attribute. + + A more generic overload was bypassed: '{0}'. The selected overload '{1}' was chosen because it has more concrete type parameters. + A more generic overload was bypassed: '{0}'. The selected overload '{1}' was chosen because it has more concrete type parameters. + + The syntax 'expr1[expr2]' is used for indexing. Consider adding a type annotation to enable indexing, or if calling a function add a space, e.g. 'expr1 [expr2]'. Для индексирования используется синтаксис "expr1[expr2]". Рассмотрите возможность добавления аннотации типа для включения индексации или при вызове функции добавьте пробел, например "expr1 [expr2]". @@ -1577,6 +1597,11 @@ The following required properties have to be initialized:{0} + + Overload resolution selected '{0}' based on type concreteness. The more concrete type '{1}' was preferred over '{2}'. This is an informational message and can be enabled with --warnon:3575. + Overload resolution selected '{0}' based on type concreteness. The more concrete type '{1}' was preferred over '{2}'. This is an informational message and can be enabled with --warnon:3575. + + The field '{0}' appears multiple times in this record expression or pattern Поле "{0}" появляется несколько раз в данном выражении записи или шаблона @@ -1641,6 +1666,7 @@ The syntax 'expr1[expr2]' is now reserved for indexing and is ambiguous when used as an argument. See https://aka.ms/fsharp-index-notation. If calling a function with multiple curried arguments, add a space between them, e.g. 'someFunction expr1 [expr2]'. Синтаксис "expr1[expr2]" теперь зарезервирован для индексирования и неоднозначен при использовании в качестве аргумента. См. https://aka.ms/fsharp-index-notation. При вызове функции с несколькими каррированными аргументами добавьте между ними пробел, например "someFunction expr1 [expr2]". + This override takes a tuple instead of multiple arguments. Try to add an additional layer of parentheses at the method definition (e.g. 'member _.Foo((x, y))'), or remove parentheses at the abstract method declaration (e.g. 'abstract member Foo: 'a * 'b -> 'c'). @@ -1742,6 +1768,11 @@ Если тип объединения нескольких регистров является структурой, то все поля с одинаковым именем должны быть одного типа. Это правило также применяется к сгенерированному имени «Элемент» в случае безымянных полей. + + The 'OverloadResolutionPriorityAttribute' cannot be applied to an override member. Apply it to the original declaration instead. + The 'OverloadResolutionPriorityAttribute' cannot be applied to an override member. Apply it to the original declaration instead. + + This expression implicitly converts type '{0}' to type '{1}'. See https://aka.ms/fsharp-implicit-convs. Это выражение неявно преобразует тип "{0}" в тип "{1}". См. сведения на странице https://aka.ms/fsharp-implicit-convs. diff --git a/src/Compiler/xlf/FSComp.txt.tr.xlf b/src/Compiler/xlf/FSComp.txt.tr.xlf index 28576ca243d..c202f7001fa 100644 --- a/src/Compiler/xlf/FSComp.txt.tr.xlf +++ b/src/Compiler/xlf/FSComp.txt.tr.xlf @@ -187,6 +187,11 @@ Genel yapı, genel bir tür parametresinin yapı veya başvuru türü olarak bilinmesini gerektirir. Tür ek açıklaması eklemeyi düşünün. + + Neither candidate is strictly more concrete than the other:\n{0} + Neither candidate is strictly more concrete than the other:\n{0} + + Known types of arguments: {0} Bilinen bağımsız değişken türleri: {0} @@ -497,6 +502,11 @@ Veri almayan birleşim durumu için desen eşleştirme atma kullanılamaz. + + Use 'most concrete' tiebreaker for overload resolution when methods differ only by type parameter concreteness. + Use 'most concrete' tiebreaker for overload resolution when methods differ only by type parameter concreteness. + + nameof nameof @@ -532,6 +542,11 @@ açık tür bildirimi + + Support for OverloadResolutionPriorityAttribute to prioritize method overloads. + Support for OverloadResolutionPriorityAttribute to prioritize method overloads. + + overloads for custom operations özel işlemler için aşırı yüklemeler @@ -1457,6 +1472,11 @@ Expected unit-of-measure type parameter must be marked with the [<Measure>] attribute. + + A more generic overload was bypassed: '{0}'. The selected overload '{1}' was chosen because it has more concrete type parameters. + A more generic overload was bypassed: '{0}'. The selected overload '{1}' was chosen because it has more concrete type parameters. + + The syntax 'expr1[expr2]' is used for indexing. Consider adding a type annotation to enable indexing, or if calling a function add a space, e.g. 'expr1 [expr2]'. Söz dizimi “expr1[expr2]” dizin oluşturma için kullanılıyor. Dizin oluşturmayı etkinleştirmek için bir tür ek açıklama eklemeyi düşünün veya bir işlev çağırıyorsanız bir boşluk ekleyin, örn. “expr1 [expr2]”. @@ -1577,6 +1597,11 @@ The following required properties have to be initialized:{0} + + Overload resolution selected '{0}' based on type concreteness. The more concrete type '{1}' was preferred over '{2}'. This is an informational message and can be enabled with --warnon:3575. + Overload resolution selected '{0}' based on type concreteness. The more concrete type '{1}' was preferred over '{2}'. This is an informational message and can be enabled with --warnon:3575. + + The field '{0}' appears multiple times in this record expression or pattern '{0}' alanı bu kayıt ifadesinde veya deseninde birden fazla görünüyor. @@ -1641,6 +1666,7 @@ The syntax 'expr1[expr2]' is now reserved for indexing and is ambiguous when used as an argument. See https://aka.ms/fsharp-index-notation. If calling a function with multiple curried arguments, add a space between them, e.g. 'someFunction expr1 [expr2]'. Söz dizimi “expr1[expr2]” artık dizin oluşturma için ayrılmıştır ve bağımsız değişken olarak kullanıldığında belirsizdir. https://aka.ms/fsharp-index-notation'a bakın. Birden çok curry bağımsız değişkenli bir işlev çağırıyorsanız, aralarına bir boşluk ekleyin, örn. “someFunction expr1 [expr2]”. + This override takes a tuple instead of multiple arguments. Try to add an additional layer of parentheses at the method definition (e.g. 'member _.Foo((x, y))'), or remove parentheses at the abstract method declaration (e.g. 'abstract member Foo: 'a * 'b -> 'c'). @@ -1742,6 +1768,11 @@ Çok durumlu bir birleşim türü bir yapıysa, aynı ada sahip tüm alanların aynı türde olması gerekir. Bu kural adlandırılmamış alanlar olması durumunda oluşturulan ‘Öğe’ adı için de geçerlidir. + + The 'OverloadResolutionPriorityAttribute' cannot be applied to an override member. Apply it to the original declaration instead. + The 'OverloadResolutionPriorityAttribute' cannot be applied to an override member. Apply it to the original declaration instead. + + This expression implicitly converts type '{0}' to type '{1}'. See https://aka.ms/fsharp-implicit-convs. Bu ifade '{0}' türünü örtülü olarak '{1}' türüne dönüştürür. https://aka.ms/fsharp-implicit-convs adresine bakın. diff --git a/src/Compiler/xlf/FSComp.txt.zh-Hans.xlf b/src/Compiler/xlf/FSComp.txt.zh-Hans.xlf index 6c459b3f33c..c88e8ab572e 100644 --- a/src/Compiler/xlf/FSComp.txt.zh-Hans.xlf +++ b/src/Compiler/xlf/FSComp.txt.zh-Hans.xlf @@ -187,6 +187,11 @@ 泛型构造要求泛型类型参数被视为结构或引用类型。请考虑添加类型注释。 + + Neither candidate is strictly more concrete than the other:\n{0} + Neither candidate is strictly more concrete than the other:\n{0} + + Known types of arguments: {0} 已知参数类型: {0} @@ -497,6 +502,11 @@ 不允许将模式匹配丢弃用于不采用数据的联合事例。 + + Use 'most concrete' tiebreaker for overload resolution when methods differ only by type parameter concreteness. + Use 'most concrete' tiebreaker for overload resolution when methods differ only by type parameter concreteness. + + nameof nameof @@ -532,6 +542,11 @@ 开放类型声明 + + Support for OverloadResolutionPriorityAttribute to prioritize method overloads. + Support for OverloadResolutionPriorityAttribute to prioritize method overloads. + + overloads for custom operations 自定义操作的重载 @@ -1457,6 +1472,11 @@ Expected unit-of-measure type parameter must be marked with the [<Measure>] attribute. + + A more generic overload was bypassed: '{0}'. The selected overload '{1}' was chosen because it has more concrete type parameters. + A more generic overload was bypassed: '{0}'. The selected overload '{1}' was chosen because it has more concrete type parameters. + + The syntax 'expr1[expr2]' is used for indexing. Consider adding a type annotation to enable indexing, or if calling a function add a space, e.g. 'expr1 [expr2]'. 语法“expr1[expr2]”用于索引。考虑添加类型批注来启用索引,或者在调用函数添加空格,例如“expr1 [expr2]”。 @@ -1577,6 +1597,11 @@ The following required properties have to be initialized:{0} + + Overload resolution selected '{0}' based on type concreteness. The more concrete type '{1}' was preferred over '{2}'. This is an informational message and can be enabled with --warnon:3575. + Overload resolution selected '{0}' based on type concreteness. The more concrete type '{1}' was preferred over '{2}'. This is an informational message and can be enabled with --warnon:3575. + + The field '{0}' appears multiple times in this record expression or pattern 字段“{0}”在此记录表达式或模式中多次出现 @@ -1641,6 +1666,7 @@ The syntax 'expr1[expr2]' is now reserved for indexing and is ambiguous when used as an argument. See https://aka.ms/fsharp-index-notation. If calling a function with multiple curried arguments, add a space between them, e.g. 'someFunction expr1 [expr2]'. 语法“expr1[expr2]”现在保留用于索引,用作参数时不明确。请参见 https://aka.ms/fsharp-index-notation。如果使用多个扩充参数调用函数, 请在它们之间添加空格,例如“someFunction expr1 [expr2]”。 + This override takes a tuple instead of multiple arguments. Try to add an additional layer of parentheses at the method definition (e.g. 'member _.Foo((x, y))'), or remove parentheses at the abstract method declaration (e.g. 'abstract member Foo: 'a * 'b -> 'c'). @@ -1742,6 +1768,11 @@ 如果多重联合类型是结构,则具有相同名称的所有字段必须具有相同的类型。对于未命名字段,此规则也适用于生成的“Item”名称。 + + The 'OverloadResolutionPriorityAttribute' cannot be applied to an override member. Apply it to the original declaration instead. + The 'OverloadResolutionPriorityAttribute' cannot be applied to an override member. Apply it to the original declaration instead. + + This expression implicitly converts type '{0}' to type '{1}'. See https://aka.ms/fsharp-implicit-convs. 此表达式将类型“{0}”隐式转换为类型“{1}”。请参阅 https://aka.ms/fsharp-implicit-convs。 diff --git a/src/Compiler/xlf/FSComp.txt.zh-Hant.xlf b/src/Compiler/xlf/FSComp.txt.zh-Hant.xlf index 296ef8fa7de..c6153fc8b83 100644 --- a/src/Compiler/xlf/FSComp.txt.zh-Hant.xlf +++ b/src/Compiler/xlf/FSComp.txt.zh-Hant.xlf @@ -187,6 +187,11 @@ 泛型建構要求泛型型別參數必須指定為結構或參考型別。請考慮新增型別註解。 + + Neither candidate is strictly more concrete than the other:\n{0} + Neither candidate is strictly more concrete than the other:\n{0} + + Known types of arguments: {0} 已知的引數類型: {0} @@ -497,6 +502,11 @@ 不接受資料的聯集案例不允許模式比對捨棄。 + + Use 'most concrete' tiebreaker for overload resolution when methods differ only by type parameter concreteness. + Use 'most concrete' tiebreaker for overload resolution when methods differ only by type parameter concreteness. + + nameof nameof @@ -532,6 +542,11 @@ 開放式類型宣告 + + Support for OverloadResolutionPriorityAttribute to prioritize method overloads. + Support for OverloadResolutionPriorityAttribute to prioritize method overloads. + + overloads for custom operations 為自訂作業多載 @@ -1457,6 +1472,11 @@ Expected unit-of-measure type parameter must be marked with the [<Measure>] attribute. + + A more generic overload was bypassed: '{0}'. The selected overload '{1}' was chosen because it has more concrete type parameters. + A more generic overload was bypassed: '{0}'. The selected overload '{1}' was chosen because it has more concrete type parameters. + + The syntax 'expr1[expr2]' is used for indexing. Consider adding a type annotation to enable indexing, or if calling a function add a space, e.g. 'expr1 [expr2]'. 語法 'expr1[expr2]' 已用於編製索引。請考慮新增類型註釋來啟用編製索引,或是呼叫函式並新增空格,例如 'expr1 [expr2]'。 @@ -1577,6 +1597,11 @@ The following required properties have to be initialized:{0} + + Overload resolution selected '{0}' based on type concreteness. The more concrete type '{1}' was preferred over '{2}'. This is an informational message and can be enabled with --warnon:3575. + Overload resolution selected '{0}' based on type concreteness. The more concrete type '{1}' was preferred over '{2}'. This is an informational message and can be enabled with --warnon:3575. + + The field '{0}' appears multiple times in this record expression or pattern 欄位 '{0}' 在這個記錄運算式或模式中出現多次 @@ -1641,6 +1666,7 @@ The syntax 'expr1[expr2]' is now reserved for indexing and is ambiguous when used as an argument. See https://aka.ms/fsharp-index-notation. If calling a function with multiple curried arguments, add a space between them, e.g. 'someFunction expr1 [expr2]'. 語法 'expr1[expr2]' 現已為編製索引保留,但用作引數時不明確。請參閱 https://aka.ms/fsharp-index-notation。如果要呼叫具有多個調用引數的函式,請在它們之間新增空格,例如 'someFunction expr1 [expr2]'。 + This override takes a tuple instead of multiple arguments. Try to add an additional layer of parentheses at the method definition (e.g. 'member _.Foo((x, y))'), or remove parentheses at the abstract method declaration (e.g. 'abstract member Foo: 'a * 'b -> 'c'). @@ -1742,6 +1768,11 @@ 如果多寫聯集類型是結構,則所有具有相同名稱的欄位都必須是相同的類型。此規則也適用於未命名欄位時產生的 'Item' 名稱。 + + The 'OverloadResolutionPriorityAttribute' cannot be applied to an override member. Apply it to the original declaration instead. + The 'OverloadResolutionPriorityAttribute' cannot be applied to an override member. Apply it to the original declaration instead. + + This expression implicitly converts type '{0}' to type '{1}'. See https://aka.ms/fsharp-implicit-convs. 此運算式將類型 '{0}' 隱含轉換為類型 '{1}'。請參閱 https://aka.ms/fsharp-implicit-convs。 diff --git a/tests/FSharp.Compiler.ComponentTests/Conformance/OverloadResolutionPriority/CSharpPriorityLib.cs b/tests/FSharp.Compiler.ComponentTests/Conformance/OverloadResolutionPriority/CSharpPriorityLib.cs new file mode 100644 index 00000000000..b24eabc5090 --- /dev/null +++ b/tests/FSharp.Compiler.ComponentTests/Conformance/OverloadResolutionPriority/CSharpPriorityLib.cs @@ -0,0 +1,244 @@ +using System; +using System.Runtime.CompilerServices; + +namespace PriorityTests +{ + public static class BasicPriority + { + [OverloadResolutionPriority(1)] + public static string HighPriority(object o) => "high"; + + [OverloadResolutionPriority(0)] + public static string LowPriority(object o) => "low"; + + [OverloadResolutionPriority(2)] + public static string Invoke(object o) => "priority-2"; + + [OverloadResolutionPriority(1)] + public static string Invoke(string s) => "priority-1-string"; + + [OverloadResolutionPriority(0)] + public static string Invoke(int i) => "priority-0-int"; + } + + public static class NegativePriority + { + [OverloadResolutionPriority(-1)] + public static string Legacy(object o) => "legacy"; + + public static string Legacy(string s) => "current"; // default priority 0 + + [OverloadResolutionPriority(-2)] + public static string Obsolete(object o) => "very-old"; + + [OverloadResolutionPriority(-1)] + public static string Obsolete(string s) => "old"; + + public static string Obsolete(int i) => "new"; // default priority 0 + } + + public static class PriorityVsConcreteness + { + [OverloadResolutionPriority(1)] + public static string Process(T value) => "generic-high-priority"; + + [OverloadResolutionPriority(0)] + public static string Process(int value) => "int-low-priority"; + + [OverloadResolutionPriority(1)] + public static string Handle(T[] arr) => "array-generic-high"; + + public static string Handle(int[] arr) => "array-int-default"; + } + + public static class ExtensionTypeA + { + [OverloadResolutionPriority(1)] + public static string ExtMethod(this string s, int x) => "TypeA-priority1"; + + public static string ExtMethod(this string s, object o) => "TypeA-priority0"; + } + + public static class ExtensionTypeB + { + [OverloadResolutionPriority(2)] + public static string ExtMethod(this string s, int x) => "TypeB-priority2"; + + public static string ExtMethod(this string s, object o) => "TypeB-priority0"; + } + + public static class DefaultPriority + { + public static string NoAttr(object o) => "no-attr"; + + [OverloadResolutionPriority(0)] + public static string ExplicitZero(object o) => "explicit-zero"; + + [OverloadResolutionPriority(1)] + public static string PositiveOne(object o) => "positive-one"; + + public static string Mixed(string s) => "mixed-default"; + + [OverloadResolutionPriority(1)] + public static string Mixed(object o) => "mixed-priority"; + } +} + +namespace ExtensionPriorityTests +{ + // ===== Per-declaring-type scoped priority for extensions ===== + + public static class ExtensionModuleA + { + [OverloadResolutionPriority(1)] + public static string Transform(this T value) => "ModuleA-generic-priority1"; + + [OverloadResolutionPriority(0)] + public static string Transform(this int value) => "ModuleA-int-priority0"; + } + + public static class ExtensionModuleB + { + [OverloadResolutionPriority(0)] + public static string Transform(this T value) => "ModuleB-generic-priority0"; + + [OverloadResolutionPriority(2)] + public static string Transform(this int value) => "ModuleB-int-priority2"; + } + + // ===== Same priority, normal tiebreakers apply ===== + + public static class SamePriorityTiebreaker + { + [OverloadResolutionPriority(1)] + public static string Process(T value) => "generic"; + + [OverloadResolutionPriority(1)] + public static string Process(int value) => "int"; + + [OverloadResolutionPriority(1)] + public static string Process(string value) => "string"; + } + + public static class SamePriorityArrayTypes + { + [OverloadResolutionPriority(1)] + public static string Handle(T[] arr) => "generic-array"; + + [OverloadResolutionPriority(1)] + public static string Handle(int[] arr) => "int-array"; + } + + // ===== Inheritance hierarchy with mixed priorities ===== + + public class BaseClass + { + [OverloadResolutionPriority(0)] + public virtual string Method(object o) => "Base-object-priority0"; + + [OverloadResolutionPriority(1)] + public virtual string Method(string s) => "Base-string-priority1"; + } + + public class DerivedClass : BaseClass + { + public override string Method(object o) => "Derived-object"; + public override string Method(string s) => "Derived-string"; + } + + public class DerivedClassWithNewMethods : BaseClass + { + [OverloadResolutionPriority(2)] + public string Method(int i) => "DerivedNew-int-priority2"; + } + + // ===== Extension methods vs instance methods priority ===== + + public class TargetClass + { + [OverloadResolutionPriority(0)] + public string DoWork(object o) => "Instance-object-priority0"; + + [OverloadResolutionPriority(1)] + public string DoWork(string s) => "Instance-string-priority1"; + } + + public static class TargetClassExtensions + { + [OverloadResolutionPriority(2)] + public static string DoWork(this TargetClass tc, int i) => "Extension-int-priority2"; + } + + // ===== Instance-only class for priority testing ===== + + public class InstanceOnlyClass + { + [OverloadResolutionPriority(2)] + public string Call(object o) => "object-priority2"; + + [OverloadResolutionPriority(0)] + public string Call(string s) => "string-priority0"; + } + + // ===== Priority with zero vs absent attribute ===== + + public static class ExplicitVsImplicitZero + { + [OverloadResolutionPriority(0)] + public static string WithExplicitZero(object o) => "explicit-zero"; + + public static string WithoutAttr(string s) => "no-attr"; + } + + // ===== Complex generic scenarios ===== + + public static class ComplexGenerics + { + [OverloadResolutionPriority(2)] + public static string Process(T t, U u) => "fully-generic-priority2"; + + [OverloadResolutionPriority(1)] + public static string Process(T t, int u) => "partial-concrete-priority1"; + + [OverloadResolutionPriority(0)] + public static string Process(int t, int u) => "fully-concrete-priority0"; + } + + // ===== Property / Indexer with ORP ===== + + public class IndexerWithPriority + { + [OverloadResolutionPriority(1)] + public string this[object key] => "object-indexer-priority1"; + + [OverloadResolutionPriority(0)] + public string this[string key] => "string-indexer-priority0"; + + [OverloadResolutionPriority(2)] + public string this[int index1, int index2] => "two-int-indexer-priority2"; + + [OverloadResolutionPriority(0)] + public string this[object index1, object index2] => "two-object-indexer-priority0"; + } + + // ===== Virtual Base with ORP ===== + + public class VirtualBaseWithPriority + { + [OverloadResolutionPriority(1)] + public virtual string Compute(object o) => "base-object-priority1"; + + [OverloadResolutionPriority(0)] + public virtual string Compute(string s) => "base-string-priority0"; + + [OverloadResolutionPriority(-1)] + public virtual string Compute(int i) => "base-int-priority-neg1"; + } + + public class DerivedOverridesVirtual : VirtualBaseWithPriority + { + public override string Compute(object o) => "derived-object"; + public override string Compute(string s) => "derived-string"; + public override string Compute(int i) => "derived-int"; + } +} diff --git a/tests/FSharp.Compiler.ComponentTests/Conformance/OverloadResolutionPriority/ORPTestRunner.fs b/tests/FSharp.Compiler.ComponentTests/Conformance/OverloadResolutionPriority/ORPTestRunner.fs new file mode 100644 index 00000000000..e036d733c8c --- /dev/null +++ b/tests/FSharp.Compiler.ComponentTests/Conformance/OverloadResolutionPriority/ORPTestRunner.fs @@ -0,0 +1,195 @@ +module ORPTestRunner + +open PriorityTests +open ExtensionPriorityTests + +let mutable failures = 0 + +let test (name: string) (expected: string) (actual: string) = + if actual <> expected then + printfn "FAIL: %s - Expected '%s' but got '%s'" name expected actual + failures <- failures + 1 + else + printfn "PASS: %s" name + +// ============================================================================ +// Basic Priority Tests - consuming C# ORP from F# +// ============================================================================ + +let testBasicPriority () = + test "Higher priority wins over lower" "priority-2" (BasicPriority.Invoke("test")) + + test "Negative priority deprioritizes" "current" (NegativePriority.Legacy("test")) + + test "Multiple negative priority levels" "new" (NegativePriority.Obsolete(42)) + + test "Priority overrides concreteness" "generic-high-priority" (PriorityVsConcreteness.Process(42)) + + test "Default priority is 0" "mixed-priority" (DefaultPriority.Mixed("test")) + +// ============================================================================ +// Per-declaring-type Extension Tests +// ============================================================================ + +let testExtensions () = + test "Extension type B priority" "TypeB-priority2" (ExtensionTypeB.ExtMethod("hello", 42)) + + let x = 42 + test "Per-type extension priority" "ModuleB-int-priority2" (x.Transform()) + +// ============================================================================ +// Same Priority - Normal Tiebreakers Apply +// ============================================================================ + +let testSamePriorityTiebreakers () = + test "Same priority - int wins by concreteness" "int" (SamePriorityTiebreaker.Process(42)) + + test "Same priority - string wins by concreteness" "string" (SamePriorityTiebreaker.Process("hello")) + + test "Same priority - int[] wins by concreteness" "int-array" (SamePriorityArrayTypes.Handle([|1; 2; 3|])) + +// ============================================================================ +// Inheritance Tests +// ============================================================================ + +let testInheritance () = + let derived = DerivedClassWithNewMethods() + test "Derived new method highest priority" "DerivedNew-int-priority2" (derived.Method(42)) + + let derivedBase = DerivedClass() + test "Base priority respected in derived" "Derived-string" (derivedBase.Method("test")) + +// ============================================================================ +// Instance Method Priority +// ============================================================================ + +let testInstanceMethods () = + let obj = InstanceOnlyClass() + test "Instance method priority" "object-priority2" (obj.Call("hello")) + + let target = TargetClass() + test "Extension adds new overload" "Extension-int-priority2" (target.DoWork(42)) + +// ============================================================================ +// Explicit vs Implicit Zero Priority +// ============================================================================ + +let testExplicitVsImplicit () = + test "No attr direct call" "no-attr" (ExplicitVsImplicitZero.WithoutAttr("test")) + test "Explicit zero direct call" "explicit-zero" (ExplicitVsImplicitZero.WithExplicitZero(box "test")) + +// ============================================================================ +// Complex Generics +// ============================================================================ + +let testComplexGenerics () = + test "Complex generics - fully generic wins" "fully-generic-priority2" (ComplexGenerics.Process(1, 2)) + + test "Complex generics - partial match" "fully-generic-priority2" (ComplexGenerics.Process("hello", 42)) + +// ============================================================================ +// F# Code USING the ORP attribute (defining overloads with ORP) +// ============================================================================ + +type FSharpWithORP = + [] + static member Greet(o: obj) = "fsharp-obj-priority2" + + [] + static member Greet(s: string) = "fsharp-string-priority0" + + static member Greet(i: int) = "fsharp-int-default" + +type FSharpGenericPriority = + [] + static member Process<'T>(x: 'T) = "fsharp-generic-priority1" + + [] + static member Process(x: int) = "fsharp-int-priority0" + +[] +module FSharpExtensions = + type System.String with + [] + member this.FsExtend(x: obj) = "fsharp-ext-obj-priority1" + + [] + member this.FsExtend(x: int) = "fsharp-ext-int-priority0" + +let testFSharpUsingORP () = + test "F# ORP - obj wins by priority" "fsharp-obj-priority2" (FSharpWithORP.Greet("hello")) + + test "F# ORP - generic wins by priority" "fsharp-generic-priority1" (FSharpGenericPriority.Process(42)) + + test "F# extension ORP - obj wins by priority" "fsharp-ext-obj-priority1" ("test".FsExtend(42)) + +// ============================================================================ +// Virtual Base ORPA Inheritance Tests +// ============================================================================ + +let testVirtualBaseOrpa () = + // When called via Base type, priority1 object overload should win over priority0 string + let baseObj = VirtualBaseWithPriority() + test "Virtual base - object wins by priority" "base-object-priority1" (baseObj.Compute("hello")) + + // When called via Derived instance, base priority should still apply + // Derived overrides don't change priority - it's read from base declaration + let derived = DerivedOverridesVirtual() + test "Derived virtual - base priority respected, object wins" "derived-object" (derived.Compute("hello")) + + // Int has priority -1, should lose to both object(1) and string(0) + let intResult = derived.Compute(42) + test "Derived virtual - int with neg priority" "derived-int" intResult + +// ============================================================================ +// Main entry point +// ============================================================================ + +[] +let main _ = + printfn "Running OverloadResolutionPriority tests..." + printfn "" + + printfn "=== Basic Priority Tests ===" + testBasicPriority () + printfn "" + + printfn "=== Extension Tests ===" + testExtensions () + printfn "" + + printfn "=== Same Priority Tiebreaker Tests ===" + testSamePriorityTiebreakers () + printfn "" + + printfn "=== Inheritance Tests ===" + testInheritance () + printfn "" + + printfn "=== Instance Method Tests ===" + testInstanceMethods () + printfn "" + + printfn "=== Explicit vs Implicit Zero Tests ===" + testExplicitVsImplicit () + printfn "" + + printfn "=== Complex Generics Tests ===" + testComplexGenerics () + printfn "" + + printfn "=== F# Using ORP Attribute Tests ===" + testFSharpUsingORP () + printfn "" + + printfn "=== Virtual Base ORPA Tests ===" + testVirtualBaseOrpa () + printfn "" + + printfn "========================================" + if failures = 0 then + printfn "All tests passed!" + 0 + else + printfn "FAILED: %d test(s) failed" failures + 1 diff --git a/tests/FSharp.Compiler.ComponentTests/Conformance/OverloadResolutionPriority/OverloadResolutionPriorityTests.fs b/tests/FSharp.Compiler.ComponentTests/Conformance/OverloadResolutionPriority/OverloadResolutionPriorityTests.fs new file mode 100644 index 00000000000..a651b26011d --- /dev/null +++ b/tests/FSharp.Compiler.ComponentTests/Conformance/OverloadResolutionPriority/OverloadResolutionPriorityTests.fs @@ -0,0 +1,112 @@ +// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information. + +namespace Conformance.OverloadResolutionPriority + +open FSharp.Test +open FSharp.Test.Compiler +open Xunit +open Conformance.SharedTestHelpers + +module OverloadResolutionPriorityTests = + + [] + let ``OverloadResolutionPriority - comprehensive test`` () = + FsFromPath (__SOURCE_DIRECTORY__ ++ "ORPTestRunner.fs") + |> withReferences [csharpPriorityLib] + |> withLangVersionPreview + |> asExe + |> compileAndRun + |> shouldSucceed + |> ignore + + [] + let ``OverloadResolutionPriority - Debug.Assert selects two-arg overload`` () = + Fs """ +module TestDebugAssert + +open System.Diagnostics + +let run () = + Debug.Assert(true) + Debug.Assert(false, "explicit message") +""" + |> withLangVersionPreview + |> compile + |> shouldSucceed + |> ignore + + [] + let ``OverloadResolutionPriority - indexer with priority`` () = + // Known limitation: ORPA on C# indexers does not override F# overload resolution. + // F# selects the more specific type (string over object) regardless of priority. + Fs """ +module TestIndexerPriority + +open ExtensionPriorityTests + +let run () = + let obj = IndexerWithPriority() + // Single-arg indexer: F# picks string-priority0 (more specific) despite object having priority1 + let r1 = obj.["hello"] + if r1 <> "string-indexer-priority0" then + failwithf "Expected 'string-indexer-priority0' but got '%s'" r1 + + // Two-arg indexer: F# picks two-int-priority2 (both more specific and higher priority) + let r2 = obj.[1, 2] + if r2 <> "two-int-indexer-priority2" then + failwithf "Expected 'two-int-indexer-priority2' but got '%s'" r2 + +run () +""" + |> withReferences [csharpPriorityLib] + |> withLangVersionPreview + |> asExe + |> compileAndRun + |> shouldSucceed + |> ignore + + [] + let ``OverloadResolutionPriority - error on F# override`` () = + Fs """ +module TestORPOnOverride + +open System.Runtime.CompilerServices + +type Base() = + abstract member DoWork: int -> string + default _.DoWork(x: int) = "base" + + abstract member DoWork: string -> string + default _.DoWork(s: string) = "base-string" + +type Derived() = + inherit Base() + + [] + override _.DoWork(x: int) = "derived" +""" + |> withLangVersionPreview + |> compile + |> shouldFail + |> withErrorCode 3586 + |> ignore + + [] + let ``OverloadResolutionPriority - allowed on non-override F# member`` () = + Fs """ +module TestORPOnNonOverride + +open System.Runtime.CompilerServices + +type MyClass() = + [] + member _.Work(x: obj) = "obj" + + member _.Work(x: string) = "string" + +let result = MyClass().Work("hello") +""" + |> withLangVersionPreview + |> compile + |> shouldSucceed + |> ignore diff --git a/tests/FSharp.Compiler.ComponentTests/Conformance/SharedTestHelpers.fs b/tests/FSharp.Compiler.ComponentTests/Conformance/SharedTestHelpers.fs new file mode 100644 index 00000000000..04532c45518 --- /dev/null +++ b/tests/FSharp.Compiler.ComponentTests/Conformance/SharedTestHelpers.fs @@ -0,0 +1,12 @@ +// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information. + +namespace Conformance + +open FSharp.Test.Compiler + +module SharedTestHelpers = + + let csharpPriorityLib = + CSharpFromPath (System.IO.Path.Combine(__SOURCE_DIRECTORY__, "OverloadResolutionPriority", "CSharpPriorityLib.cs")) + |> withCSharpLanguageVersionPreview + |> withName "CSharpPriorityLib" diff --git a/tests/FSharp.Compiler.ComponentTests/Conformance/Tiebreakers/TiebreakerTests.fs b/tests/FSharp.Compiler.ComponentTests/Conformance/Tiebreakers/TiebreakerTests.fs new file mode 100644 index 00000000000..59ab5fe7f29 --- /dev/null +++ b/tests/FSharp.Compiler.ComponentTests/Conformance/Tiebreakers/TiebreakerTests.fs @@ -0,0 +1,1343 @@ +// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information. + +namespace Conformance.Tiebreakers + +open FSharp.Test +open FSharp.Test.Compiler +open Xunit +open Conformance.SharedTestHelpers + +module TiebreakerTests = + + let private concretenessWarningSource = + """ +module Test + +type Example = + static member Invoke<'t>(value: Option<'t>) = "generic" + static member Invoke<'t>(value: Option<'t list>) = "more concrete" + +let result = Example.Invoke(Some([1])) + """ + + let genericVsConcreteNestingCases: obj[] seq = + let case desc source = + [| desc :> obj; source :> obj |] + + [ + case + "Basic - Option<'t> vs Option" + "module Test\ntype Example =\n static member Invoke(value: Option<'t>) = \"generic\"\n static member Invoke(value: Option) = \"int\"\nlet result = Example.Invoke(Some 42)\nif result <> \"int\" then failwithf \"Expected 'int' but got '%s' - wrong overload selected\" result" + + case + "Nested - Option> vs Option>" + "module Test\ntype Example =\n static member Handle(value: Option>) = \"nested generic\"\n static member Handle(value: Option>) = \"nested int\"\nlet result = Example.Handle(Some(Some 42))\nif result <> \"nested int\" then failwithf \"Expected 'nested int' but got '%s' - wrong overload selected\" result" + + case + "Triple nesting - list>> vs list>>" + "module Test\ntype Example =\n static member Deep(value: list>>) = \"generic\"\n static member Deep(value: list>>) = \"int\"\nlet result = Example.Deep([Some(Ok 42)])\nif result <> \"int\" then failwithf \"Expected 'int' but got '%s' - wrong overload selected\" result" + ] + + [] + [] + let ``Generic vs concrete at varying nesting depths`` (_description: string) (source: string) = + FSharp source + |> asExe + |> compileAndRun + |> shouldSucceed + |> ignore + + [] + let ``Example 5 - Multiple Type Parameters - Result fully concrete wins`` () = + FSharp """ +module Test + +type Example = + static member Transform(value: Result<'ok, 'error>) = "fully generic" + static member Transform(value: Result) = "int ok" + static member Transform(value: Result<'ok, string>) = "string error" + static member Transform(value: Result) = "both concrete" + +let result = Example.Transform(Ok 42 : Result) +if result <> "both concrete" then + failwithf "Expected 'both concrete' but got '%s' - wrong overload selected" result + """ + |> asExe + |> compileAndRun + |> shouldSucceed + |> ignore + + [] + [", "int ok", "Ok 42 : Result")>] + [", "string error", "Ok \"test\" : Result")>] + let ``Example 5 - Multiple Type Parameters - Partial concreteness resolves`` (methodName: string, concreteParam: string, concreteDesc: string, callExpr: string) = + FSharp $""" +module Test + +type Example = + static member {methodName}(value: Result<'ok, 'error>) = "fully generic" + static member {methodName}(value: {concreteParam}) = "{concreteDesc}" + +let result = Example.{methodName}({callExpr}) +if result <> "{concreteDesc}" then + failwithf "Expected '{concreteDesc}' but got '%%s' - wrong overload selected" result + """ + |> withLangVersionPreview + |> asExe + |> compileAndRun + |> shouldSucceed + |> ignore + + [] + let ``Example 6 - Incomparable Concreteness - Result int e vs Result t string - ambiguous with helpful message`` () = + FSharp """ +module Test + +type Example = + static member Compare(value: Result) = "int ok" + static member Compare(value: Result<'ok, string>) = "string error" + +let result = Example.Compare(Ok 42 : Result) + """ + |> typecheck + |> shouldFail + |> withErrorCode 41 // FS0041: A unique overload could not be determined + |> withDiagnosticMessageMatches "Neither candidate is strictly more concrete" + |> withDiagnosticMessageMatches "Compare is more concrete at position 1" + |> ignore + + [] + let ``Multiple Type Parameters - Three way comparison with clear winner`` () = + FSharp """ +module Test + +type Example = + static member Check(a: 't, b: 'u) = "both generic" + static member Check(a: int, b: 'u) = "first concrete" + static member Check(a: int, b: string) = "both concrete" + +let result = Example.Check(42, "hello") +if result <> "both concrete" then + failwithf "Expected 'both concrete' but got '%s' - wrong overload selected" result + """ + |> asExe + |> compileAndRun + |> shouldSucceed + |> ignore + + [] + let ``Multiple Type Parameters - Tuple-like scenario`` () = + FSharp """ +module Test + +type Example = + static member Pair(fst: 't, snd: 'u) = "both generic" + static member Pair(fst: int, snd: int) = "both int" + +let result = Example.Pair(1, 2) + """ + |> typecheck + |> shouldSucceed + |> ignore + + [] + let ``Example 7 - ValueTask constructor scenario - Task of T vs T - resolves to Task`` () = + FSharp """ +module Test + +open System.Threading.Tasks + +[] +type ValueTaskSimulator<'T> = + | FromResult of 'T + | FromTask of Task<'T> + +type ValueTaskFactory = + static member Create(result: 'T) = ValueTaskSimulator<'T>.FromResult result + static member Create(task: Task<'T>) = ValueTaskSimulator<'T>.FromTask task + +let createFromTask () = + let task = Task.FromResult(42) + let result = ValueTaskFactory.Create(task) + result + """ + |> withLangVersionPreview + |> typecheck + |> shouldSucceed + |> ignore + + [] + let ``Example 7 - ValueTask constructor - bare int resolves to result overload`` () = + FSharp """ +module Test + +open System.Threading.Tasks + +type ValueTaskFactory = + static member Create(result: 'T) = "result" + static member Create(task: Task<'T>) = "task" + +let createFromInt () = + let result = ValueTaskFactory.Create(42) + result + """ + |> typecheck + |> shouldSucceed + |> ignore + + [] + let ``Example 8 - CE Source overloads - FsToolkit AsyncResult pattern - resolves`` () = + FSharp """ +module Test + +open System + +type AsyncResultBuilder() = + member _.Return(x) = async { return Ok x } + member _.ReturnFrom(x) = x + + member _.Source(result: Async>) : Async> = result + member _.Source(result: Result<'ok, 'error>) : Async> = async { return result } + member _.Source(asyncValue: Async<'t>) : Async> = + async { + let! v = asyncValue + return Ok v + } + + member _.Bind(computation: Async>, f: 'ok -> Async>) = + async { + let! result = computation + match result with + | Ok value -> return! f value + | Error e -> return Error e + } + +let asyncResult = AsyncResultBuilder() + +let example () = + let source : Async> = async { return Ok 42 } + asyncResult.Source(source) + """ + |> withLangVersionPreview + |> typecheck + |> shouldSucceed + |> ignore + + [] + let ``Example 8 - CE Source overloads - Async of plain value uses generic`` () = + FSharp """ +module Test + +type SimpleBuilder() = + member _.Source(asyncResult: Async>) = "async result" + member _.Source(asyncValue: Async<'t>) = "async generic" + +let builder = SimpleBuilder() + +let result = builder.Source(async { return 42 }) + """ + |> typecheck + |> shouldSucceed + |> ignore + + [] + let ``Example 9 - CE Bind with Task types - TaskBuilder pattern`` () = + FSharp """ +module Test + +open System.Threading.Tasks + +type TaskBuilder() = + member _.Return(x: 'a) : Task<'a> = Task.FromResult(x) + + member _.Bind(taskLike: 't, continuation: 't -> Task<'b>) : Task<'b> = + continuation taskLike + + member _.Bind(task: Task<'a>, continuation: 'a -> Task<'b>) : Task<'b> = + task.ContinueWith(fun (t: Task<'a>) -> continuation(t.Result)).Unwrap() + +let taskBuilder = TaskBuilder() + +let example () = + let task = Task.FromResult(42) + taskBuilder.Bind(task, fun x -> Task.FromResult(x + 1)) + """ + |> typecheck + |> shouldSucceed + |> ignore + + [] + let ``Example 9 - CE Bind with Task - non-task value uses generic overload`` () = + FSharp """ +module Test + +open System.Threading.Tasks + +type SimpleTaskBuilder() = + member _.Bind(taskLike: 't, continuation: 't -> Task<'b>) = continuation taskLike + member _.Bind(task: Task<'a>, continuation: 'a -> Task<'b>) = + task.ContinueWith(fun (t: Task<'a>) -> continuation(t.Result)).Unwrap() + +let builder = SimpleTaskBuilder() + +let result = builder.Bind(42, fun x -> Task.FromResult(x + 1)) + """ + |> typecheck + |> shouldSucceed + |> ignore + + [] + let ``Real-world pattern - Source with Result types vs generic - resolves`` () = + FSharp """ +module Test + +type Builder() = + member _.Source(x: Result<'a, 'e>) = "result" + member _.Source(x: 't) = "generic" + +let b = Builder() + +let result = b.Source(Ok 42 : Result) + """ + |> withLangVersionPreview + |> typecheck + |> shouldSucceed + |> ignore + + [] + let ``Real-world pattern - Nested task result types`` () = + FSharp """ +module Test + +open System.Threading.Tasks + +type AsyncBuilder() = + member _.Bind(x: Task>, f: 'a -> Task>) = + x.ContinueWith(fun (t: Task>) -> + match t.Result with + | Ok v -> f(v) + | Error e -> Task.FromResult(Error e) + ).Unwrap() + + member _.Bind(x: Task<'t>, f: 't -> Task>) = + x.ContinueWith(fun (t: Task<'t>) -> f(t.Result)).Unwrap() + +let ab = AsyncBuilder() + +let example () = + let taskResult : Task> = Task.FromResult(Ok 42) + ab.Bind(taskResult, fun x -> Task.FromResult(Ok (x + 1))) + """ + |> typecheck + |> shouldSucceed + |> ignore + + [] + let ``Example 10 - Mixed Optional and Generic - existing optional rule has priority`` () = + FSharp """ +module Test + +type Example = + static member Configure(value: Option<'t>) = "generic, required" + static member Configure(value: Option, ?timeout: int) = "int, optional timeout" + +let result = Example.Configure(Some 42) + """ + |> typecheck + |> shouldSucceed + |> ignore + + [] + let ``Example 10 - Mixed Optional - verify priority order does not change`` () = + FSharp """ +module Test + +type Example = + static member Process(value: Option>) = "nested generic, no optional" + static member Process(value: Option>, ?retries: int) = "nested int, with optional" + +let result = Example.Process(Some(Some 42)) + """ + |> typecheck + |> shouldSucceed + |> ignore + + let bothHaveOptionalTestCases: obj[] seq = + let case desc source = [| desc :> obj; source :> obj |] + + [ + case "Same optional types" + "module Test\ntype Example =\n static member Format(value: Option<'t>, ?prefix: string) = \"generic\"\n static member Format(value: Option, ?prefix: string) = \"int\"\nlet result = Example.Format(Some 42)" + + case "Different optional types" + "module Test\ntype Example =\n static member Transform(value: Option<'t>, ?prefix: string) = \"generic\"\n static member Transform(value: Option, ?timeout: int) = \"int\"\nlet result = Example.Transform(Some 42)" + + case "Multiple optional params" + "module Test\ntype Example =\n static member Config(value: Option<'t>, ?prefix: string, ?suffix: string) = \"generic\"\n static member Config(value: Option, ?min: int, ?max: int) = \"int\"\nlet result = Example.Config(Some 42)" + + case "Nested generics" + "module Test\ntype Example =\n static member Handle(value: Option>, ?tag: string) = \"nested generic\"\n static member Handle(value: Option>, ?tag: string) = \"nested int\"\nlet result = Example.Handle(Some(Some 42))" + ] + + [] + [] + let ``Both have optional params - concreteness breaks tie`` (_description: string) (source: string) = + FSharp source + |> typecheck + |> shouldSucceed + |> ignore + + let paramArrayTestCases: obj[] seq = + let case desc source = [| desc :> obj; source :> obj |] + + [ + case "Option elements" + "module Test\ntype Example =\n static member Log([] items: Option<'t>[]) = \"generic options\"\n static member Log([] items: Option[]) = \"int options\"\nlet result = Example.Log(Some 1, Some 2, Some 3)" + + case "Nested Option elements" + "module Test\ntype Example =\n static member Combine([] values: Option>[]) = \"nested generic\"\n static member Combine([] values: Option>[]) = \"nested int\"\nlet result = Example.Combine(Some(Some 1), Some(Some 2))" + + case "Result elements" + "module Test\ntype Example =\n static member Process([] results: Result[]) = \"generic error\"\n static member Process([] results: Result[]) = \"string error\"\nlet r1 : Result = Ok 1\nlet r2 : Result = Ok 2\nlet result = Example.Process(r1, r2)" + ] + + [] + [] + let ``ParamArray with generic elements - concreteness breaks tie`` (_description: string) (source: string) = + FSharp source + |> typecheck + |> shouldSucceed + |> ignore + + [] + let ``ParamArray vs explicit array - identical types remain ambiguous`` () = + FSharp """ +module Test + +type Example = + static member Write(messages: string[]) = "explicit array" + static member Write([] messages: string[]) = "param array" + +let messages = [| "a"; "b"; "c" |] +let result = Example.Write(messages) + """ + |> typecheck + |> shouldFail + |> withErrorCode 41 // FS0041 - ambiguous when both have same types + |> ignore + + [] + let ``Combined Optional and ParamArray - complex scenario`` () = + FSharp """ +module Test + +type Example = + static member Send(target: string, [] data: Option<'t>[]) = "generic" + static member Send(target: string, [] data: Option[]) = "int" + +let result = Example.Send("dest", Some 1, Some 2, Some 3) + """ + |> typecheck + |> shouldSucceed + |> ignore + + [] + let ``Example 13 - Intrinsic method always preferred over extension`` () = + FSharp """ +module Test + +type Container<'t>() = + member this.Transform() = "intrinsic generic" + +[] +module ContainerExtensions = + type Container<'t> with + member this.TransformExt() = "extension - same signature" + +let c = Container() +let result = c.Transform() + """ + |> typecheck + |> shouldSucceed + |> ignore + + [] + let ``Example 13 - Less concrete intrinsic still wins over more concrete extension`` () = + FSharp """ +module Test + +type Wrapper<'t>() = + member this.Process(value: 't) = "intrinsic generic" + +[] +module WrapperExtensions = + type Wrapper<'t> with + member this.ProcessExt(value: int) = "extension concrete" + +let w = Wrapper() +let result = w.Process(42) + """ + |> typecheck + |> shouldSucceed + |> ignore + + [] + let ``Example 13 - Extension with different return type - intrinsic preferred`` () = + FSharp """ +module Test + +type Handler<'t>() = + member this.Execute(input: 't) = sprintf "intrinsic: %A" input + +[] +module HandlerExtensions = + type Handler<'t> with + member this.ExecuteExt(input: int) = sprintf "extension int: %d" input + +let h = Handler() +let result = h.Execute(42) + """ + |> typecheck + |> shouldSucceed + |> ignore + + [] + let ``Extension methods in same module - concreteness breaks tie`` () = + FSharp """ +module Test + +type Data = { Value: int } + +module DataExtensions = + type Data with + member this.Map(f: 'a -> 'b) = "generic map" + member this.Map(f: int -> int) = "int map" + +open DataExtensions + +let d = { Value = 1 } +let result = d.Map(fun x -> x + 1) + """ + |> typecheck + |> shouldSucceed + |> ignore + + let sameModuleExtensionTestCases: obj[] seq = + let case desc source = [| desc :> obj; source :> obj |] + + [ + case "Result types" + "module Test\ntype Wrapper = class end\nmodule WrapperExtensions =\n type Wrapper with\n static member Process(value: Result<'ok, 'err>) = \"generic result\"\n static member Process(value: Result) = \"concrete result\"\nopen WrapperExtensions\nlet result = Wrapper.Process(Ok 42 : Result)" + + case "Option type" + "module Test\ntype Processor = class end\nmodule ProcessorExtensions =\n type Processor with\n static member Handle(value: Option<'t>) = \"generic option\"\n static member Handle(value: Option) = \"int option\"\nopen ProcessorExtensions\nlet result = Processor.Handle(Some 42)" + + case "Nested generic" + "module Test\ntype Builder = class end\nmodule BuilderExtensions =\n type Builder with\n static member Create(value: Option>) = \"nested generic\"\n static member Create(value: Option>) = \"nested int\"\nopen BuilderExtensions\nlet result = Builder.Create(Some(Some 42))" + ] + + [] + [] + let ``Extension methods in same module - concreteness resolves`` (_description: string) (source: string) = + FSharp source + |> typecheck + |> shouldSucceed + |> ignore + + [] + let ``SRTP resolution - intrinsic method preferred over extension`` () = + FSharp """ +module Test + +type Processor() = + member this.Handle(x: obj) = "intrinsic obj" + +module ProcessorExtensions = + type Processor with + member this.HandleExt(x: int) = "extension int" + +open ProcessorExtensions + +let inline handle (p: ^T when ^T : (member Handle : 'a -> string)) (arg: 'a) = + (^T : (member Handle : 'a -> string) (p, arg)) + +let p = Processor() + +let directResult = p.Handle(42) + +let srtpResult = handle p 42 + """ + |> typecheck + |> shouldSucceed + |> ignore + + [] + let ``SRTP resolution - extension-only overloads resolved by concreteness`` () = + FSharp """ +module Test + +type Data = { Value: int } + +module DataExtensions = + type Data with + member this.Format(x: 't) = sprintf "generic: %A" x + member this.Format(x: string) = sprintf "string: %s" x + +open DataExtensions + +let d = { Value = 1 } + +let directResult = d.Format("hello") + """ + |> typecheck + |> shouldSucceed + |> ignore + + [] + let ``SRTP resolution - generic SRTP constraint with concrete extension`` () = + FSharp """ +module Test + +type Container<'t> = { Item: 't } + +module ContainerExtensions = + type Container<'t> with + member this.Extract() = this.Item + member this.Extract() = 0 // Specialized for int return - but this creates ambiguity + """ + |> typecheck + |> shouldSucceed + |> ignore + + [] + let ``C# style extension methods consumed in F# - concreteness applies`` () = + FSharp """ +module Test + +type System.String with + member this.Transform(arg: 't) = sprintf "generic %A" arg + member this.Transform(arg: int) = sprintf "int %d" arg + +let result = "hello".Transform(42) + """ + |> typecheck + |> shouldSucceed + |> ignore + + [] + let ``Extension priority - later opened module takes precedence over concreteness`` () = + FSharp """ +module Test + +module GenericExtensions = + type System.Int32 with + member this.Describe() = "generic extension" + +module ConcreteExtensions = + type System.Int32 with + member this.Describe() = "concrete extension" + +open ConcreteExtensions +open GenericExtensions + +let result = (42).Describe() + """ + |> typecheck + |> shouldSucceed + |> ignore + + [] + let ``Extension methods - incomparable concreteness remains ambiguous`` () = + FSharp """ +module Test + +type Pair = class end + +module PairExtensions = + type Pair with + static member Compare(a: Result) = "int ok" + static member Compare(a: Result<'t, string>) = "string error" + +open PairExtensions + +let result = Pair.Compare(Ok 42 : Result) + """ + |> typecheck + |> shouldFail + |> withErrorCode 41 // FS0041: incomparable concreteness + |> ignore + + [] + let ``FsToolkit pattern - same module extensions resolved by concreteness`` () = + FSharp """ +module Test + +open System + +type AsyncResultBuilder() = + member _.Return(x) = async { return Ok x } + +[] +module AsyncResultCEExtensions = + type AsyncResultBuilder with + member inline _.Source(result: Async<'t>) : Async> = + async { + let! v = result + return Ok v + } + + member inline _.Source(result: Async>) : Async> = + result + +let asyncResult = AsyncResultBuilder() + +let example () = + let source : Async> = async { return Ok 42 } + asyncResult.Source(source) + """ + |> withLangVersionPreview + |> typecheck + |> shouldSucceed + |> ignore + + [] + let ``Adhoc rule - T is always better than inref of T`` () = + FSharp """ +module Test + +type Example = + static member Process(x: int) = "by value" + static member Process(x: inref) = "by ref" + +let value = 42 +let result = Example.Process(value) + """ + |> typecheck + |> shouldSucceed + |> ignore + + [] + let ``Adhoc rule priority - T over inref T takes precedence over concreteness`` () = + FSharp """ +module Test + +type Example = + static member Process<'a>(x: 'a) = "generic by value" + static member Process(x: inref) = "concrete by ref" + +let value = 42 +let result = Example.Process(value) + """ + |> typecheck + |> shouldSucceed + |> ignore + + [] + let ``Constrained type variable - different wrapper types with constraints allowed`` () = + FSharp """ +module Test + +open System + +type Example = + static member Compare(value: 't) = "generic" + static member Compare(value: IComparable) = "interface" + +let result = Example.Compare(42) + """ + |> typecheck + |> shouldSucceed + |> ignore + + [] + let ``TDC priority - No TDC preferred over TDC even when TDC target is more concrete`` () = + FSharp """ +module Test + +type Example = + static member Process(x: int) = "int" // No TDC needed + static member Process(x: int64) = "int64" // Would need TDC: int→int64 + +let result = Example.Process(42) + """ + |> typecheck + |> shouldSucceed + |> ignore + + [] + let ``TDC priority - Concreteness applies only when TDC is equal`` () = + FSharp """ +module Test + +type Example = + static member Invoke(value: Option<'t>) = "generic" + static member Invoke(value: Option) = "concrete" + +let result = Example.Invoke(Some([1])) + """ + |> typecheck + |> shouldSucceed + |> ignore + + [] + let ``TDC priority - Combined TDC and generic resolution`` () = + FSharp """ +module Test + +type Example = + static member Handle(x: int64, y: Option<'t>) = "generic" + static member Handle(x: int64, y: Option) = "concrete" + +let result = Example.Handle(42L, Some("hello")) + """ + |> typecheck + |> shouldSucceed + |> ignore + + [] + let ``TDC priority - Nullable TDC preferred over op_Implicit TDC`` () = + FSharp """ +module Test + +type Example = + static member Method(x: System.Nullable) = "nullable" // TDC: int → Nullable + static member Method(x: int) = "direct" // No TDC + +let result = Example.Method(42) + """ + |> typecheck + |> shouldSucceed + |> ignore + + [] + let ``Adhoc rule - Func is preferred over other delegate types`` () = + FSharp """ +module Test + +open System + +type CustomDelegate = delegate of int -> string + +type Example = + static member Process(f: Func) = "func" + static member Process(f: CustomDelegate) = "custom" + +let result = Example.Process(fun x -> string x) + """ + |> typecheck + |> shouldSucceed + |> ignore + + [] + let ``Adhoc rule - Func concreteness applies when both are Func`` () = + FSharp """ +module Test + +open System + +type Example = + static member Invoke(f: Func) = "concrete func" + static member Invoke(f: Func<'a, 'b>) = "generic func" + +let result = Example.Invoke(fun x -> string x) + """ + |> typecheck + |> shouldSucceed + |> ignore + + [] + let ``Adhoc rule - Nullable concreteness applies when both are Nullable`` () = + FSharp """ +module Test + +type Example = + static member Convert(value: System.Nullable) = "nullable int" + static member Convert(value: System.Nullable<'t>) = "nullable generic" + +let result = Example.Convert(System.Nullable(42)) + """ + |> typecheck + |> shouldSucceed + |> ignore + + [] + let ``Adhoc rule - Nullable and concreteness combined`` () = + FSharp """ +module Test + +type Example = + static member Convert(value: int) = "int" + static member Convert(value: System.Nullable) = "nullable int" + static member Convert(value: System.Nullable<'t>) = "nullable generic" + +let result1 = Example.Convert(42) + +let result2 = Example.Convert(System.Nullable(42)) + """ + |> typecheck + |> shouldSucceed + |> ignore + + [] + let ``SRTP - Generic SRTP vs concrete type instantiation`` () = + FSharp """ +module Test + +type Handler = + static member inline Process< ^T when ^T : (static member Parse : string -> ^T)>(s: string) : Option< ^T> = + Some (( ^T) : (static member Parse : string -> ^T) s) + static member inline Process(s: string) : Option = + Some(System.Int32.Parse s) + +let result : Option = Handler.Process("42") + """ + |> typecheck + |> shouldSucceed + |> ignore + + [] + let ``SRTP - Inline function with concrete specialization`` () = + FSharp """ +module Test + +type Converter = + static member inline Convert< ^T when ^T : (member Value : int)>(x: ^T) = (^T : (member Value : int) x) + static member Convert(x: System.Nullable) = x.GetValueOrDefault() + +let result = Converter.Convert(System.Nullable(42)) + """ + |> typecheck + |> shouldSucceed + |> ignore + + [] + let ``SRTP - Member constraint with nested type arguments`` () = + FSharp """ +module Test + +type Builder = + static member inline Build< ^T when ^T : (static member Create : unit -> Option< ^T>)>() : Option< ^T> = + (^T : (static member Create : unit -> Option< ^T>) ()) + static member Build() : Option = Some 0 + +let result : Option = Builder.Build() + """ + |> typecheck + |> shouldSucceed + |> ignore + + [] + let ``SRTP skip - Both generic with SRTP produces ambiguity`` () = + FSharp """ +module Test + +type Resolver = + static member inline Resolve< ^T>(input: Option< ^T>) = "srtp option" + static member inline Resolve< ^T>(input: Option< ^T list>) = "srtp option list" + +let result : string = Resolver.Resolve(Some([1])) + """ + |> withLangVersionPreview + |> typecheck + |> shouldFail + |> withErrorCode 41 + |> ignore + + let concreteWrapperTestCases: obj[] seq = + let case desc source = [| desc :> obj; source :> obj |] + + [ + case "Async vs Async<'T>" + "module Test\ntype AsyncRunner =\n static member Run(comp: Async) = \"int async\"\n static member Run(comp: Async<'T>) = \"generic async\"\nlet computation = async { return 42 }\nlet result = AsyncRunner.Run(computation)" + + case "Async> vs Async>" + "module Test\ntype AsyncHandler =\n static member Handle(comp: Async>) = \"int result async\"\n static member Handle(comp: Async>) = \"generic result async\"\nlet computation : Async> = async { return Ok 42 }\nlet result = AsyncHandler.Handle(computation)" + + case "MailboxProcessor vs MailboxProcessor<'T>" + "module Test\ntype Message = Start | Stop\ntype Dispatcher =\n static member Dispatch(mb: MailboxProcessor) = \"int mailbox\"\n static member Dispatch(mb: MailboxProcessor<'T>) = \"generic mailbox\"\nlet mb = MailboxProcessor.Start(fun inbox -> async { return () })\nlet result = Dispatcher.Dispatch(mb)" + + case "Lazy vs Lazy<'T>" + "module Test\ntype LazyLoader =\n static member Load(value: Lazy) = \"int list lazy\"\n static member Load(value: Lazy<'T>) = \"generic lazy\"\nlet lazyValue = lazy [1; 2; 3]\nlet result = LazyLoader.Load(lazyValue)" + + case "Choice vs Choice<'T1, 'T2>" + "module Test\ntype Router =\n static member Route(choice: Choice) = \"int or string\"\n static member Route(choice: Choice<'T1, 'T2>) = \"generic choice\"\nlet c = Choice1Of2 42\nlet result = Router.Route(c)" + + case "ValueOption vs ValueOption<'T>" + "module Test\ntype ValueProcessor =\n static member Process(v: ValueOption) = \"voption int\"\n static member Process(v: ValueOption<'T>) = \"voption generic\"\nlet vopt = ValueSome 42\nlet result = ValueProcessor.Process(vopt)" + + case "seq vs seq<'T>" + "module Test\ntype SeqHandler =\n static member Handle(s: seq) = \"int seq\"\n static member Handle(s: seq<'T>) = \"generic seq\"\nlet numbers = seq { 1; 2; 3 }\nlet result = SeqHandler.Handle(numbers)" + + case "Option list vs Option<'T> list" + "module Test\ntype ListHandler =\n static member Handle(lst: Option list) = \"option int list\"\n static member Handle(lst: Option<'T> list) = \"option generic list\"\nlet items = [ Some 1; Some 2; None ]\nlet result = ListHandler.Handle(items)" + + case "Async vs Async<'T>" + "module Test\ntype AsyncBuilder =\n static member Wrap(comp: Async) = \"tuple async\"\n static member Wrap(comp: Async<'T>) = \"generic async\"\nlet work = async { return (42, \"hello\") }\nlet result = AsyncBuilder.Wrap(work)" + + case "Result vs Result" + "module Test\ntype ErrorHandler =\n static member Handle(r: Result) = \"int result string error\"\n static member Handle(r: Result) = \"int result generic error\"\nlet ok : Result = Ok 42\nlet result = ErrorHandler.Handle(ok)" + + case "Tree vs Tree<'T>" + "module Test\ntype Tree<'T> =\n | Leaf of 'T\n | Node of Tree<'T> * Tree<'T>\ntype TreeProcessor =\n static member Process(t: Tree) = \"int tree\"\n static member Process(t: Tree<'T>) = \"generic tree\"\nlet tree = Node(Leaf 1, Leaf 2)\nlet result = TreeProcessor.Process(tree)" + + case "inref> vs inref>" + "module Test\ntype RefProcessor =\n static member Transform(ref: inref>) = \"generic result\"\n static member Transform(ref: inref>) = \"int result\"\nlet runTest () =\n let mutable result: Result = Ok 42\n RefProcessor.Transform(&result)" + + case "outref vs outref<'T>" + "module Test\ntype Writer =\n static member Write(dest: outref, value: int) = dest <- value\n static member Write(dest: outref<'T>, value: 'T) = dest <- value\nlet mutable x = 0\nWriter.Write(&x, 42)" + + case "inref/outref vs inref<'T>/outref<'T>" + "module Test\ntype Transformer =\n static member Transform(src: inref, dest: outref) = dest <- src\n static member Transform(src: inref<'T>, dest: outref<'T>) = dest <- src\nlet mutable value = 42\nlet mutable result = 0\nTransformer.Transform(&value, &result)" + + case "byref> vs byref>" + "module Test\ntype RefProcessor =\n static member Process(r: byref>) = r <- Some 42\n static member Process(r: byref>) = r <- None\nlet mutable opt : Option = None\nRefProcessor.Process(&opt)" + + case "nativeptr vs nativeptr<'T>" + "module Test\nopen Microsoft.FSharp.NativeInterop\ntype PtrHandler =\n static member Handle(p: nativeptr) = 1\n static member Handle(p: nativeptr<'T>) = 2\nlet inline handlePtr (p: nativeptr) = PtrHandler.Handle(p)" + + case "{| Value: int |} vs {| Value: 'T |}" + "module Test\ntype Processor =\n static member Process(r: {| Value: int |}) = \"int\"\n static member Process(r: {| Value: 'T |}) = \"generic\"\nlet result = Processor.Process({| Value = 42 |})" + + case "nested {| Inner: {| X: int |} |} vs {| Inner: {| X: 'T |} |}" + "module Test\ntype Handler =\n static member Handle(r: {| Inner: {| X: int |} |}) = \"concrete\"\n static member Handle(r: {| Inner: {| X: 'T |} |}) = \"generic\"\nlet result = Handler.Handle({| Inner = {| X = 42 |} |})" + + case "Option<{| Id: int; Name: string |}> vs Option<{| Id: 'T; Name: string |}>" + "module Test\ntype Builder =\n static member Build(x: Option<{| Id: int; Name: string |}>) = \"concrete\"\n static member Build(x: Option<{| Id: 'T; Name: string |}>) = \"generic id\"\nlet result = Builder.Build(Some {| Id = 1; Name = \"test\" |})" + + case "float vs float<'u>" + "module Test\n[] type m\n[] type s\ntype Calculator =\n static member Calculate(x: float) = \"meters\"\n static member Calculate(x: float<'u>) = \"generic unit\"\nlet distance : float = 5.0\nlet result = Calculator.Calculate(distance)" + + case "float vs float<'u>" + "module Test\n[] type m\n[] type s\ntype Physics =\n static member Velocity(x: float) = \"velocity\"\n static member Velocity(x: float<'u>) = \"generic\"\nlet speed : float = 10.0\nlet result = Physics.Velocity(speed)" + + case "Option> vs Option>" + "module Test\n[] type kg\ntype Scale =\n static member Weigh(x: Option>) = \"kg\"\n static member Weigh(x: Option>) = \"generic\"\nlet result = Scale.Weigh(Some 75.0)" + + case "float[] vs float<'u>[]" + "module Test\n[] type Hz\ntype SignalProcessor =\n static member Process(samples: float[]) = \"Hz array\"\n static member Process(samples: float<'u>[]) = \"generic array\"\nlet frequencies : float[] = [| 440.0; 880.0 |]\nlet result = SignalProcessor.Process(frequencies)" + ] + + let concreteWrapperNetCoreTestCases: obj[] seq = + let case desc source = [| desc :> obj; source :> obj |] + + [ + case "Span vs Span<'T>" + "module Test\nopen System\ntype Parser =\n static member Parse(data: Span<'T>) = \"generic\"\n static member Parse(data: Span) = \"bytes\"\nlet runTest () =\n let buffer: byte[] = [| 1uy; 2uy; 3uy |]\n let span = Span(buffer)\n Parser.Parse(span)" + + case "ReadOnlySpan vs ReadOnlySpan<'T>" + "module Test\nopen System\ntype Parser =\n static member Parse(data: ReadOnlySpan<'T>) = \"generic\"\n static member Parse(data: ReadOnlySpan) = \"bytes\"\nlet runTest () =\n let bytes: byte[] = [| 1uy; 2uy; 3uy |]\n let roSpan = ReadOnlySpan(bytes)\n Parser.Parse(roSpan)" + + case "Span> vs Span>" + "module Test\nopen System\ntype DataHandler =\n static member Handle(data: Span>) = \"generic option\"\n static member Handle(data: Span>) = \"int option\"\nlet runTest () =\n let options: Option[] = [| Some 1; Some 2 |]\n let span = Span(options)\n DataHandler.Handle(span)" + + case "ValueTask vs ValueTask<'T>" + "module Test\nopen System.Threading.Tasks\ntype TaskRunner =\n static member Run(t: ValueTask) = \"int valuetask\"\n static member Run(t: ValueTask<'T>) = \"generic valuetask\"\nlet vt = ValueTask(42)\nlet result = TaskRunner.Run(vt)" + ] + + [] + [] + let ``Concrete wrapper type resolves over generic`` (_description: string) (source: string) = + FSharp source + |> typecheck + |> shouldSucceed + |> ignore + + [] + [] + let ``Concrete wrapper type resolves over generic (NETCOREAPP)`` (_description: string) (source: string) = + FSharp source + |> typecheck + |> shouldSucceed + |> ignore + + [] + let ``Warning 3575 - Not emitted by default when concreteness tiebreaker used`` () = + FSharp concretenessWarningSource + |> withLangVersionPreview + |> typecheck + |> shouldSucceed + |> ignore + + [] + let ``Warning 3575 - Emitted when enabled and concreteness tiebreaker is used`` () = + FSharp concretenessWarningSource + |> withLangVersionPreview + |> withOptions ["--warnon:3575"] + |> typecheck + |> shouldFail + |> withWarningCode 3575 + |> withDiagnosticMessageMatches "concreteness" + |> ignore + + [] + let ``Warning 3576 - Emitted when enabled and generic overload is bypassed`` () = + FSharp concretenessWarningSource + |> withLangVersionPreview + |> withOptions ["--warnon:3576"] + |> typecheck + |> shouldFail + |> withWarningCode 3576 + |> withDiagnosticMessageMatches "bypassed" + |> withDiagnosticMessageMatches "Invoke" + |> ignore + + [] + let ``Warning 3576 - Multiple bypassed overloads`` () = + FSharp """ +module Test + +type Example = + static member Process<'t>(value: 't) = "fully generic" + static member Process<'t>(value: Option<'t>) = "option generic" + static member Process<'t>(value: Option<'t list>) = "most concrete" + +let result = Example.Process(Some([1])) + """ + |> withLangVersionPreview + |> withOptions ["--warnon:3576"] + |> typecheck + |> shouldFail + |> withWarningCode 3576 + |> ignore + + [] + let ``SRTP - member constraint with overloaded static member`` () = + FSharp """ +module Test + +type Converter = + static member Convert<'t>(x: 't) = box x + static member Convert(x: int) = box (x * 2) + +let result = Converter.Convert 21 + """ + |> typecheck + |> shouldSucceed + |> ignore + + [] + let ``SRTP - inline function calling overloaded method`` () = + FSharp """ +module Test + +type Handler = + static member Handle<'t>(x: 't) = x + static member Handle(x: int) = x * 2 + +let inline handle x = Handler.Handle x + +let result : int = handle 21 + """ + |> typecheck + |> shouldSucceed + |> ignore + + [] + let ``SRTP - layered inline with deferred overload resolution`` () = + FSharp """ +module Test + +type Processor = + static member Process<'t>(x: Option<'t>) = x + static member Process(x: Option) = x |> Option.map ((*) 2) + +let inline layer3 x = Processor.Process(Some x) +let inline layer2 x = layer3 x +let inline layer1 x = layer2 x + +let result = layer1 42 + """ + |> typecheck + |> shouldSucceed + |> ignore + + [] + let ``SRTP - explicit member constraint with Parse`` () = + FSharp """ +module Test + +type MyParser = + static member Parse(s: string) = 42 + static member Parse<'t>(s: string) = Unchecked.defaultof<'t> + +let inline parse< ^T when ^T : (static member Parse : string -> ^T)> (s: string) : ^T = + (^T : (static member Parse : string -> ^T) s) + +let result : int = parse "42" + """ + |> typecheck + |> shouldSucceed + |> ignore + + [] + let ``SRTP - witness passing with explicit type`` () = + FSharp """ +module Test + +type IMonoid<'T> = + abstract Zero : 'T + abstract Plus : 'T -> 'T -> 'T + +type IntMonoid() = + interface IMonoid with + member _.Zero = 0 + member _.Plus a b = a + b + +type Folder = + static member Fold<'t>(xs: 't list, m: IMonoid<'t>) = + List.fold (fun acc x -> m.Plus acc x) m.Zero xs + static member Fold(xs: int list, m: IMonoid) = + List.fold (fun acc x -> m.Plus acc x) m.Zero xs + +let sum = Folder.Fold([1;2;3], IntMonoid() :> IMonoid) + """ + |> typecheck + |> shouldSucceed + |> ignore + + [] + let ``SRTP - nested generic in inline with concrete specialization`` () = + FSharp """ +module Test + +type Wrapper = + static member Wrap<'t>(x: Option<'t>) = Some x + static member Wrap(x: Option) = Some (x |> Option.map ((*) 2)) + +let inline wrap x = Wrapper.Wrap(Some x) +let inline wrapTwice x = wrap x |> Option.bind id + +let result = wrapTwice 21 + """ + |> typecheck + |> shouldSucceed + |> ignore + + [] + let ``LangVersion Latest - Non-generic overload preferred over generic - existing behavior`` () = + FSharp """ +module Test + +type Example = + static member Process(value: 't) = "generic" + static member Process(value: int) = "int" + +let result = Example.Process(42) + """ + |> withLangVersion "latest" + |> typecheck + |> shouldSucceed + |> ignore + + [] + let ``LangVersion Latest - Non-extension method preferred over extension - existing behavior`` () = + FSharp """ +module Test + +type MyType() = + member this.Invoke(x: int) = "instance" + +module Extensions = + type MyType with + member this.Invoke(x: obj) = "extension" + +open Extensions + +let t = MyType() +let result = t.Invoke(42) + """ + |> withLangVersion "latest" + |> typecheck + |> shouldSucceed + |> ignore + + let moreConcretDisabledAmbiguousCases: obj[] seq = + let case desc source = + [| desc :> obj; source :> obj |] + + [ + case + "fully generic vs wrapped generic" + "module Test\ntype Example =\n static member Process(value: 't) = \"fully generic\"\n static member Process(value: Option<'t>) = \"wrapped\"\nlet result = Example.Process(Some 42)" + + case + "array generic vs bare generic" + "module Test\ntype Example =\n static member Handle(value: 't) = \"bare\"\n static member Handle(value: 't array) = \"array\"\nlet result = Example.Handle([|1; 2; 3|])" + ] + + [] + [] + let ``LangVersion Latest - MoreConcrete disabled - overloads remain ambiguous`` (_description: string) (source: string) = + FSharp source + |> withLangVersion "latest" + |> typecheck + |> shouldFail + |> withErrorCode 41 + |> ignore + + let moreConcreteTestCases: obj[] seq = + let case desc source = [| desc :> obj; source :> obj |] + + [ + case "Option<'T> vs Option<'T list> - nested list more concrete" + "module Test\ntype Resolver =\n static member Resolve<'t>(x: Option<'t>) = \"generic\"\n static member Resolve<'t>(x: Option<'t list>) = \"list\"\nlet result = Resolver.Resolve(Some [1;2;3])\nif result <> \"list\" then failwithf \"Expected 'list' but got '%s'\" result" + + case "Result<'T,'E> vs Result<'T, string> - partial concreteness" + "module Test\ntype Handler =\n static member Handle<'t,'e>(x: Result<'t,'e>) = \"generic\"\n static member Handle<'t>(x: Result<'t, string>) = \"string err\"\nlet result = Handler.Handle(Ok 42 : Result)\nif result <> \"string err\" then failwithf \"Expected 'string err' but got '%s'\" result" + + case "'T vs Option<'T> - wrapped more concrete than bare" + "module Test\ntype Picker =\n static member Pick<'t>(x: 't) = \"bare\"\n static member Pick<'t>(x: Option<'t>) = \"option\"\nlet result = Picker.Pick(Some 1)\nif result <> \"option\" then failwithf \"Expected 'option' but got '%s'\" result" + + case "Option<'T> vs Option> - double wrap more concrete" + "module Test\ntype Deep =\n static member Go<'t>(x: Option<'t>) = \"single\"\n static member Go<'t>(x: Option>) = \"double\"\nlet result = Deep.Go(Some(Some 1))\nif result <> \"double\" then failwithf \"Expected 'double' but got '%s'\" result" + + case "list<'T> vs list - tuple element more concrete" + "module Test\ntype Proc =\n static member Run<'t>(x: list<'t>) = \"generic\"\n static member Run<'t>(x: list) = \"paired\"\nlet result = Proc.Run([(1, \"a\")])\nif result <> \"paired\" then failwithf \"Expected 'paired' but got '%s'\" result" + + case "'a -> 'b vs 'a -> string - concrete range in function type" + "module Test\ntype Dispatcher =\n static member Dispatch<'a, 'b>(handler: 'a -> 'b) = \"fully generic\"\n static member Dispatch<'a>(handler: 'a -> string) = \"concrete range\"\nlet result = Dispatcher.Dispatch(fun (x: int) -> \"hello\")\nif result <> \"concrete range\" then failwithf \"Expected 'concrete range' but got '%s'\" result" + + case "'a * 'b vs 'a * int - concrete element in tuple type" + "module Test\ntype Handler =\n static member Handle<'a, 'b>(pair: 'a * 'b) = \"fully generic tuple\"\n static member Handle<'a>(pair: 'a * int) = \"concrete second\"\nlet result = Handler.Handle((\"hello\", 42))\nif result <> \"concrete second\" then failwithf \"Expected 'concrete second' but got '%s'\" result" + + case "'a -> 'b vs int -> 'b - concrete domain in function type" + "module Test\ntype Mapper =\n static member Map<'a, 'b>(f: 'a -> 'b, items: 'a list) = \"generic\"\n static member Map<'b>(f: int -> 'b, items: int list) = \"int domain\"\nlet result = Mapper.Map((fun x -> string x), [1; 2; 3])\nif result <> \"int domain\" then failwithf \"Expected 'int domain' but got '%s'\" result" + + case "'a * 'b vs int * 'b - concrete first element in tuple" + "module Test\ntype Tupler =\n static member Pack<'a, 'b>(x: 'a * 'b) = \"generic\"\n static member Pack<'b>(x: int * 'b) = \"int first\"\nlet result = Tupler.Pack((42, \"hello\"))\nif result <> \"int first\" then failwithf \"Expected 'int first' but got '%s'\" result" + ] + + [] + [] + let ``MoreConcrete tiebreaker resolves both-generic overloads`` (_description: string) (source: string) = + FSharp source + |> withLangVersionPreview + |> asExe + |> compileAndRun + |> shouldSucceed + |> ignore + + let orpIgnoredTestCases: obj[] seq = + [ + [| "higher priority does not win"; "BasicPriority.Invoke(\"test\")"; "priority-1-string" |] + [| "negative priority has no effect"; "NegativePriority.Legacy(\"test\")"; "current" |] + [| "priority does not override concreteness"; "PriorityVsConcreteness.Process(42)"; "int-low-priority" |] + ] + + [] + [] + let ``LangVersion Latest - ORP attribute ignored`` (_description: string) (callExpr: string) (expected: string) = + FSharp $""" +module Test +open PriorityTests + +let result = {callExpr} +if result <> "{expected}" then + failwithf "Expected '{expected}' but got '%%s' - ORP should be ignored" result + """ + |> withReferences [csharpPriorityLib] + |> withLangVersion "latest" + |> asExe + |> compileAndRun + |> shouldSucceed + |> ignore diff --git a/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj b/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj index 63150ad3a7d..c3620e0228d 100644 --- a/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj +++ b/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj @@ -141,6 +141,9 @@ + + +