Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
90 commits
Select commit Hold shift + click to select a range
14cdb2b
Add DSL for overload resolution tiebreaker rules
T-Gro Jan 18, 2026
eed6225
Implement compareTypeConcreteness function for RFC tiebreakers
T-Gro Jan 18, 2026
bff8e96
Integrate 'more concrete' tiebreaker into better() function
T-Gro Jan 18, 2026
3b3ffef
Add test infrastructure for Tiebreakers conformance tests
T-Gro Jan 18, 2026
e1e2385
Add RFC example tests 1-4 for Most Concrete tiebreaker
T-Gro Jan 18, 2026
73e1ff1
Add multiple type parameter tests (Examples 5-6)
T-Gro Jan 18, 2026
7e4a950
Add real-world scenario tests for Examples 7-9
T-Gro Jan 18, 2026
627020d
Add tests for Examples 10-12: Optional and ParamArray interactions
T-Gro Jan 18, 2026
1b17909
Add extension method tests for RFC FS-XXXX tiebreaker
T-Gro Jan 18, 2026
706b211
Add byref and Span tests for RFC tiebreaker
T-Gro Jan 18, 2026
0f3bf2a
Add constraint and TDC interaction tests
T-Gro Jan 18, 2026
75e3e2b
Add orthogonal test scenarios for tiebreaker feature
T-Gro Jan 18, 2026
1a9c786
Fix overload resolution concreteness tiebreaker to compare formal par…
T-Gro Jan 19, 2026
3924c11
Sprint 2: Verify test expectations - all RFC examples correctly confi…
T-Gro Jan 19, 2026
65ca1e6
Add optional diagnostics FS3575/FS3576 for concreteness tiebreaker
T-Gro Jan 19, 2026
b56c847
Add LanguageFeature.MoreConcreteTiebreaker language feature flag
T-Gro Jan 19, 2026
21ed52d
Update CONTEXT.md and VISION.md for Sprint 4 completion
T-Gro Jan 19, 2026
bc1bde6
Add release notes and format OverloadResolutionRules for Most Concret…
T-Gro Jan 19, 2026
7ac7cc3
docs: Add RFC example coverage mapping to VISION.md
T-Gro Jan 20, 2026
00914c2
Document deferred items for RFC MoreConcrete tiebreaker
T-Gro Jan 20, 2026
4c9ecab
Cleanup PR: Remove workflow artifacts, add design documentation
T-Gro Jan 20, 2026
e65d861
Add compareTypeConcreteness and aggregateComparisons to OverloadResol…
T-Gro Jan 20, 2026
df5c009
Refactor better() and wasConcretenessTiebreaker() to use rule engine
T-Gro Jan 20, 2026
bb24c00
Add explainIncomparableConcreteness API for enhanced FS0041 diagnostics
T-Gro Jan 21, 2026
6ad81e7
Integrate enhanced FS0041 error message for incomparable concreteness
T-Gro Jan 21, 2026
b629c8c
Sprint 2: Verify error info extension complete (already done in Sprin…
T-Gro Jan 21, 2026
bd52440
Sprint 3: Verify enhanced FS0041 message formatting
T-Gro Jan 21, 2026
53ed498
Sprint 4: Verify enhanced FS0041 diagnostic test
T-Gro Jan 21, 2026
37db094
Better explanations
T-Gro Jan 21, 2026
6954e89
Implement constraint count comparison for type variables (RFC Example…
T-Gro Jan 21, 2026
ed35225
Update documentation: Example 15 now implemented
T-Gro Jan 21, 2026
31c511c
Implement FS3576 diagnostic for bypassed generic overloads
T-Gro Jan 21, 2026
96bfbb4
Fix constraint-based overload tests to expect FS0438
T-Gro Jan 21, 2026
42b17ad
Update release note placeholders with clear TBD markers
T-Gro Jan 21, 2026
3e95280
Fix SRTP regression in MoreConcrete tiebreaker rule
T-Gro Jan 21, 2026
a164742
Add RFC quality assessment for FS-XXXX Most Concrete tiebreaker
T-Gro Jan 21, 2026
0fd35ee
Update RFC diagnostic messages to match FSComp.txt exactly
T-Gro Jan 21, 2026
bccd0d9
Verify RFC claims vs implementation
T-Gro Jan 21, 2026
a9c69a0
change rules from strings to ordered and strongly typed enums
T-Gro Jan 22, 2026
ff753d1
doc changes, cleanups
T-Gro Jan 22, 2026
1ec2bab
Add LanguageFeature.OverloadResolutionPriority for F# 10.0
T-Gro Jan 22, 2026
f6d90ee
Add GetOverloadResolutionPriority() method to MethInfo
T-Gro Jan 22, 2026
891e216
Add OverloadResolutionPriorityAttribute tests for RFC FS-XXXX
T-Gro Jan 22, 2026
23139d4
Fix ORP tests to run as baseline tests (not skipped)
T-Gro Jan 22, 2026
9974608
Sprint 1: Add ORP tests with inline C# library
T-Gro Jan 22, 2026
9cb2a22
Sprint 1: Fix ORP tests - remove Skip, document current behavior
T-Gro Jan 22, 2026
07a094c
Sprint 1: Add ORP tests with inline C# library
T-Gro Jan 22, 2026
f02f714
Sprint 2: Implement ORP pre-filter in ConstraintSolver.fs
T-Gro Jan 22, 2026
73da94c
Sprint 2: Add release notes for OverloadResolutionPriority support
T-Gro Jan 22, 2026
497dbb9
Sprint 3: Add 12 ORP edge case tests
T-Gro Jan 22, 2026
652d1e8
Add FS3590 diagnostic for OverloadResolutionPriority
T-Gro Jan 22, 2026
9163e4f
Move MoreConcreteTiebreaker and OverloadResolutionPriority to preview
T-Gro Jan 23, 2026
ccec98e
Add RequiredFeature to TiebreakRule type
T-Gro Jan 23, 2026
9946c65
Add LangVersion Latest Tests for feature gating verification
T-Gro Jan 23, 2026
996b998
Release notes: Document langversion=preview requirement for MoreConcr…
T-Gro Jan 23, 2026
ad34329
cleanup
T-Gro Jan 23, 2026
5777753
code cleanups
T-Gro Jan 23, 2026
f3118b6
Add RFC documents for tiebreaker and OverloadResolutionPriority features
T-Gro Feb 11, 2026
3c724a3
Remove ORP RFC (already exists in fslang-design)
T-Gro Feb 11, 2026
f50b118
Merge branch 'main' into feature/tiebreakers
T-Gro Feb 12, 2026
fa0a8af
Merge branch 'feature/tiebreakers' of https://github.com/dotnet/fshar…
T-Gro Feb 12, 2026
68920e0
fix CI failures
T-Gro Feb 12, 2026
dd7bf92
filterByOverloadResolutionPriority refactor
T-Gro Feb 12, 2026
ed6f351
Fix Desktop test failures: guard net472-incompatible tiebreaker tests…
T-Gro Feb 12, 2026
4c1662d
Fixup #1: Address CODE-QUALITY, PERF, and TEST-CODE-QUALITY verifier …
T-Gro Feb 12, 2026
d8172a9
Fixup #1 (iteration 2): Address remaining CODE-QUALITY, PERF, TEST-CO…
T-Gro Feb 12, 2026
8165d0c
Remove dead code and reduce API surface in OverloadResolutionRules
T-Gro Feb 12, 2026
6bafd8b
Fixup #2: Remove dead FS3590 diagnostic, hide TiebreakRule from API s…
T-Gro Feb 12, 2026
f7e5d15
Fixup #2 (iteration 2): Remove leftover docs, deduplicate tests, para…
T-Gro Feb 12, 2026
41f8807
Fixup #2 (iteration 3): Remove redundant comments, merge duplicate Wa…
T-Gro Feb 12, 2026
25c370b
Fixup #3: Address PERF, TEST-CODE-QUALITY, and NO-LEFTOVERS verifier …
T-Gro Feb 12, 2026
5346f77
Fixup #3 (iteration 2): Cache GetParamDatas and SRTP checks in moreCo…
T-Gro Feb 12, 2026
14b9c3f
Fix release notes: add PR #19277 links and FSharp.Compiler.Service en…
T-Gro Feb 12, 2026
2d4e2f4
Fixup #1: Address PERF, TEST-COVERAGE, HONEST-ASSESSMENT, NO-LEFTOVER…
T-Gro Feb 12, 2026
c3b0e1d
Fixup #2: Commit 4 test upgrades from typecheck to compileAndRun with…
T-Gro Feb 13, 2026
10addb1
Fixup #3: Fix unnamedArgsRule to use flat comparison list for cross-g…
T-Gro Feb 13, 2026
9277273
Fixup #1: Code quality and perf improvements for overload resolution
T-Gro Feb 13, 2026
bac3b04
Fixup #2: Add MoreConcrete, function-type, and tuple-type test coverage
T-Gro Feb 13, 2026
c3967f0
Fixup #2: Deduplicate tests, absorb standalone Fact tests into moreCo…
T-Gro Feb 13, 2026
0a5cc6d
Fixup #1: Perf optimizations and test coverage improvements
T-Gro Feb 13, 2026
fcc6460
Fix PERF and TEST-CODE-QUALITY verifier issues
T-Gro Feb 13, 2026
42754d9
Fix fantomas formatting in OverloadResolutionRules.fs
T-Gro Feb 13, 2026
ae73afc
draft rfc
T-Gro Feb 13, 2026
f97b018
Remove Description field from TiebreakRule record
T-Gro Feb 13, 2026
00e14df
Wrap paramDataCache, srtpCache, and decidingRuleCache with voption
T-Gro Feb 13, 2026
807acf1
Extract computeConcretenessWarnings helper from GetMostApplicableOver…
T-Gro Feb 13, 2026
dd19c6b
Add Debug.Assert ORPA test verifying two-arg overload selection
T-Gro Feb 13, 2026
49822e1
Add test pinning ORPA behavior on C# indexed properties
T-Gro Feb 13, 2026
c6fd2dc
Report error 3586 when OverloadResolutionPriority is applied to overr…
T-Gro Feb 13, 2026
26db26d
Add Virtual Base ORPA inheritance test
T-Gro Feb 13, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions docs/release-notes/.FSharp.Compiler.Service/10.0.300.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
2 changes: 2 additions & 0 deletions docs/release-notes/.Language/preview.md
Original file line number Diff line number Diff line change
@@ -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
167 changes: 167 additions & 0 deletions docs/rfcs/FS-XXXX-most-concrete-tiebreaker.md
Original file line number Diff line number Diff line change
@@ -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<int>` 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<int>(task)
// Current: FS0041 or requires named parameter: ValueTask<int>(task = task)
// Proposed: Resolves automatically — Task<int> is more concrete than 'T
```

This pattern affects real code: users must write `ValueTask<int>(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<int list>) = "concrete"

// Current: Error FS0041 — Proposed: Resolves to Option<int list> 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<int>`) 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<int,'e>` 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<int>`) | 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<int>` and `List<int>` 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<int, 'e>) 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<int>)
```

## 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<int,'e>` 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<T>` overloads.
Loading
Loading