Skip to content

Support OverloadResolutionPriority and a most-concrete tiebreaker - #19277

Open
T-Gro wants to merge 139 commits into
mainfrom
feature/tiebreakers
Open

Support OverloadResolutionPriority and a most-concrete tiebreaker#19277
T-Gro wants to merge 139 commits into
mainfrom
feature/tiebreakers

Conversation

@T-Gro

@T-Gro T-Gro commented Feb 12, 2026

Copy link
Copy Markdown
Member

Two overload-resolution features, both gated behind --langversion:preview.

  • Most-concrete tiebreaker — when candidates are otherwise equally ranked, the overload with more concrete parameter types is chosen, so Api.Call(Some 42) resolves to Call(x: 'T option) instead of failing with FS0041 (ambiguous). Implements fslang-suggestions #905; design in RFC FS-1340.

  • OverloadResolutionPriorityAttribute (.NET 9) — candidates marked with a higher priority are preferred over lower-priority ones, matching C#'s behavior for steering callers toward newer library overloads. Implements fslang-suggestions #821; design in RFC FS-1338.

The tiebreaker is transparent: opt into --warnon:3575 to see which concrete overload was selected and --warnon:3576 for each generic overload it bypassed.

T-Gro added 30 commits January 18, 2026 21:49
- Create OverloadResolutionRules.fs with TiebreakRule record type
- Extract all 12 existing rules from better() function into DSL format
- Add placeholder for new 'MoreConcrete' rule at priority 13
- Include evaluateTiebreakRules function for rule evaluation
- Add import of OverloadResolutionRules module to ConstraintSolver.fs
- Update FSharp.Compiler.Service.fsproj with new files

Rules documented with priority, name, and description:
1. NoTDC - Prefer no type-directed conversions
2. LessTDC - Prefer less type-directed conversion
3. NullableTDC - Prefer nullable TDC only
4. NoWarnings - Prefer no 'less generic' warnings
5. NoParamArray - Prefer no param array usage
6. PreciseParamArray - Prefer more precise param array type
7. NoOutArgs - Prefer no out args
8. NoOptionalArgs - Prefer no optional args
9. UnnamedArgs - Compare unnamed args by subsumption
10. PreferNonExtension - Prefer intrinsic over extension
11. ExtensionPriority - Prefer recently opened extension
12. PreferNonGeneric - Prefer non-generic methods
13. MoreConcrete (placeholder) - Most concrete tiebreaker from RFC
14. NullableOptionalInterop - F# 5.0 all args comparison
15. PropertyOverride - Prefer more derived property type
Add the core type concreteness comparison algorithm as specified in section-algorithm.md:
- compareTypeConcreteness: Compares types under the 'more concrete' partial ordering
- aggregateComparisons: Implements dominance rule for pairwise comparisons
- countTyparConstraints: Helper to count constraints on type parameters

Handles all 8 TType cases:
- TType_var: Compares by constraint count
- TType_app: Compares type arguments pairwise when constructors match
- TType_tuple: Compares elements pairwise
- TType_fun: Compares domain and range
- TType_anon: Compares anonymous record fields
- TType_measure: Measures are equal or incomparable
- TType_forall: Compares body with aligned bound variables
- Default: Different structural forms are incomparable

Implements subtask 4 of the RFC FS-XXXX: Most Concrete Tiebreaker.
- Add new tiebreaker rule after 'prefer non-generic methods' (rule 12)
- Only activates when BOTH candidates have non-empty CalledTyArgs
- Uses compareTypeConcreteness to compare type arguments
- Applies dominance rule: better in at least one position, not worse in any
- Positioned before F# 5.0 NullableOptionalInterop rule
- Update DSL placeholder documentation to reflect implementation
- Create tests/FSharp.Compiler.ComponentTests/Conformance/Tiebreakers/ directory
- Add TiebreakerTests.fs with module structure and helper functions
- Include placeholder test and RFC example tests
- Configure for Debug build and net10 TFM
- All 3 tests pass
- Example 1: Option<'t> vs Option<int> - resolves to more concrete
- Example 2: 't vs Option<'t> - expects ambiguity (structural comparison not yet implemented)
- Example 3: Option<Option<'t>> vs Option<Option<int>> - resolves to nested int
- Example 4: list<Option<Result<'t,exn>>> vs list<Option<Result<int,exn>>> - deep nesting works

All 7 tiebreaker tests pass with dotnet test --filter Tiebreakers -c Debug
- Add test for Result<int,string> selecting fully concrete overload (Example 5)
- Add test for incomparable types (Result<int,'e> vs Result<'t,string>) producing FS0041 (Example 6)
- Add tests verifying partial order behavior with helpful error messages
- Document current limitation: partial concreteness comparison between
  fully generic and partially concrete types remains ambiguous
- Add additional tests for tuple-like scenarios and three-way comparisons

All 14 tiebreaker tests pass.
Implement tests for the primary motivating use cases:

- Example 7: ValueTask constructor scenario (Task<'T> vs 'T disambiguation)
- Example 8: CE Source overloads (FsToolkit AsyncResult pattern)
- Example 9: CE Bind with Task types (TaskBuilder pattern)

Tests document both working cases (Task<'a> vs 't with wrapped types)
and cases that remain ambiguous pending structural comparison implementation
('T vs Task<'T> when type shapes differ).

Additional real-world pattern tests:
- Source with Result types vs generic
- Nested Task<Result<...>> types

All 22 tiebreaker tests pass.
- Example 10: Verify existing Rule 8 (prefer no optional) takes priority
  over the 'more concrete' tiebreaker
- Example 11: When both overloads have optional params, concreteness
  breaks the tie (Option<int> vs Option<'t>)
- Example 12: ParamArray with generic element types - concreteness
  resolves Option<int>[] vs Option<'t>[]

Additional coverage:
- Multiple optional params test
- Nested generics with optional params
- ParamArray with Result element types
- Combined optional and ParamArray scenarios
- ParamArray vs explicit array ambiguity documentation

All 33 Tiebreaker tests pass.
Tests covering:
- Intrinsic methods ALWAYS preferred over extensions (Rule 8)
- Less concrete intrinsic still wins over more concrete extension
- Same-module extensions resolved by concreteness
- SRTP resolution following same rules
- C# style extensions in F#
- Extension priority precedence over concreteness
- Incomparable concreteness remains ambiguous
- FsToolkit pattern documentation
Implements subtask 12: byref and Span tests from section-byref-span.md

Tests added:
- Span<byte> vs Span<'T> selecting concrete element type
- ReadOnlySpan element type comparison (concrete vs generic)
- T > inref<T> adhoc rule verification
- Span with nested generics (Option<int> vs Option<'T>)
- inref with nested generics (Result<int,exn> vs Result<'T,exn>)
- Adhoc rule priority over concreteness test

All 53 tiebreaker tests pass.
Implements subtask 13: Adds tests for constraint and TDC interaction:

- Example 15 constrained type variable tests (documents FS0438 limitation)
- TDC priority tests (no TDC > less TDC > concreteness)
- Func adhoc rule interaction tests
- Nullable adhoc rule interaction tests (T > Nullable<T>)

Tests verify TDC rules have higher priority than concreteness tiebreaker,
and adhoc rules (Func, inref, Nullable) apply before concreteness.

All 65 tiebreaker tests pass.
Adds 27 new test scenarios beyond RFC examples, covering:
- SRTPs with generic vs concrete (3 tests)
- Byref/inref/outref combinations (4 tests)
- Anonymous record types (3 tests)
- Units of measure (4 tests)
- F#-specific types: Async, MailboxProcessor, Lazy, Choice, ValueOption, ValueTask (8 tests)
- Computation expressions: seq, list, async (3 tests)
- Discriminated unions: Result, custom Tree (2 tests)

All 91 tiebreaker tests pass.
…ameter types

Sprint 1: The core algorithm fix

Changes:
- Compare formal (uninstantiated) parameter types using FormalMethodInst instead of
  comparing CalledTyArgs (which are already instantiated after type inference)
- This enables proper comparison for cases like:
  - 't vs Option<'t> (Option wins - more concrete structure)
  - Task<'T> vs 'T (Task wins for Task<int> argument)
  - Async<Result<'ok,'e>> vs Async<'t> (Result wins for Result argument)
  - Result<int,'e> vs Result<'ok,'e> (partial concreteness - int wins)

The fix correctly resolves overloads where one method's parameter type
has more concrete structure than another, even when both methods are generic.

Updated 7 tests from shouldFail to shouldSucceed:
- Example 2: 't vs Option<'t>
- Example 5: Partial concreteness (int ok, string error)
- Example 7: ValueTask Task<T> vs T
- Example 8: CE Source FsToolkit pattern
- Real-world pattern: Source with Result types
- FsToolkit pattern: same module extensions

All 95 tiebreaker tests pass.
…gured

Verified DoD:
- Build succeeds with 0 errors
- All 91 tiebreaker tests pass
- Example 2,5,7,8: use shouldSucceed (correctly)
- Example 6: uses shouldFail for incomparable case (correctly)

No code changes needed - Sprint 1 already configured expectations properly.
- Add FSComp.txt entries for FS3575 (tcMoreConcreteTiebreakerUsed) and FS3576 (tcGenericOverloadBypassed)
- Register both warnings as off by default in CompilerDiagnostics.fs
- Add wasConcretenessTiebreaker helper in ConstraintSolver.fs to detect when concreteness rule decided
- Emit warning when concreteness tiebreaker is used and --warnon:3575 is enabled
- Add tests verifying warning is emitted when enabled and not emitted by default
- Add MoreConcreteTiebreaker to LanguageFeature enum (F# 10.0)
- Gate concreteness tiebreaker logic in better() with SupportsFeature check
- Gate wasConcretenessTiebreaker helper similarly
- Add feature string to FSComp.txt
Sprint 2 audit: Verified all 14 implementable RFC examples from section-examples.md
have corresponding tests in TiebreakerTests.fs.

Coverage mapping:
- Examples 1-14: All tested with explicit test names and line numbers
- Example 15: Confirmed deferred (FS0438 language limitation)

93/93 tiebreaker tests passing.
Sprint 3: Documentation-only changes to track deferred future work:

1. TiebreakerTests.fs: Enhanced comment block for Example 15 test
   explaining F# language limitation (FS0438 prevents constraint-only
   overloading)

2. ConstraintSolver.fs: Added TODO comment at aggregateComparisons
   function for future enhanced FS0041 error message that explains
   why types are incomparable

3. VISION.md: Updated deferred items with code location cross-references

No functional changes - all 93 tiebreaker tests pass.
- Remove .ralph/ folder (workflow artifacts not needed in final PR)
- Add docs/TIEBREAKERS_DESIGN.md with comprehensive feature documentation
- Release notes have PR number placeholders (to be updated when PR is created)
…utionRules

Sprint 1 deliverables:
- Add aggregateComparisons helper for dominance-based comparison
- Add compareTypeConcreteness function for type concreteness ordering
- Export both functions in signature file
- OverloadResolutionContext already has all needed fields (g, amap, m, ndeep)

These functions are now available for use by the rule engine in future
sprints. The implementation matches the existing algorithm in
ConstraintSolver.fs but is now accessible from OverloadResolutionRules.fs.
- Replace ~140 lines of duplicated if-then-else chains in ConstraintSolver.fs
  with calls to evaluateTiebreakRules() and wasDecidedByRule() from OverloadResolutionRules module
- Fix moreConcreteRule to actually perform the comparison (was placeholder returning 0)
- Add wasDecidedByRule helper to check if a specific rule was the deciding factor
- Remove unused local helper functions from ConstraintSolver.fs
- All 93 tiebreaker tests pass

This eliminates the code duplication identified in VISION.md between better() and wasConcretenessTiebreaker().
This function computes position-by-position comparison results when
two types are incomparable under the concreteness ordering. It returns
Some with a list of (position, ty1Arg, ty2Arg, comparison) tuples when
types have mixed results (incomparable), or None when one type dominates
or they are equal.

This is Sprint 1 of the enhanced FS0041 error message implementation.
- Add IncomparableConcretenessInfo type and explainIncomparableMethodConcreteness function
- Extend PossibleCandidates to carry incomparable concreteness details
- Format enhanced message in CompilerDiagnostics.fs showing which type args favor each method
- Add csIncomparableConcreteness message resource to FSComp.txt
- Update test to verify enhanced message content

When overload resolution fails due to incomparable type concreteness, the error message now explains:
'Neither candidate is strictly more concrete than the other:
  - Compare is more concrete at position 1
  - Compare is more concrete at position 2'
All DoD items verified:
- Build succeeds with 0 errors
- Enhanced FS0041 message shows per-position concreteness details
- FSComp.txt string resource csIncomparableConcreteness added
- All 93 TiebreakerTests pass

The functionality was already implemented in earlier sprints.
DoD verified:
- Build succeeds with 0 errors
- Test at TiebreakerTests.fs:252-268 verifies enhanced FS0041 message
- Test checks for 'Neither candidate is strictly more concrete'
- Test checks for position-specific concreteness explanation
- All 93 TiebreakerTests pass
… 15)

- Add countTypeParamConstraints helper to count effective constraints
  (CoercesTo, IsNonNullableStruct, IsReferenceType, MayResolveMember, etc.)
- Update compareTypeConcreteness to compare constraint counts when both
  types are type variables (RFC section-algorithm.md lines 136-146)
- Remove 'deferred' comments as constraint comparison is now implemented
- Update Example 15 test to expect success (constrained overload wins)

All 97 tiebreaker tests pass.
@T-Gro
T-Gro marked this pull request as ready for review July 26, 2026 07:06
@T-Gro
T-Gro requested a review from a team as a code owner July 26, 2026 07:06
@T-Gro
T-Gro requested a review from abonie July 26, 2026 07:06
T-Gro and others added 3 commits July 26, 2026 11:23
…dings

CI fix (net472 Desktop jobs):
- TiebreakerTests: mark "overload resolution priority still wins over
  concreteness" [<FactForNETCOREAPP>]; its body uses
  [<OverloadResolutionPriority>], a .NET 9+ BCL type that cannot be
  authored on net472, so it must skip there like its siblings.

Quality (3 cross-model rubber ducks: clarity/compactness/no-fluff):
- OverloadResolutionRules: extract shared methodMentionsSRTP helper used by
  both moreConcreteRule's firing gate and the FS0041 diagnostic explainer,
  so their SRTP exclusion can no longer drift (the explainer previously
  only checked parameter-type SRTP, missing method type params / called
  type args). Behaviour-identical for the rule; strictly narrows the
  diagnostic to cases the rule actually ranks.
- OverloadResolutionRules(.fsi): correct the TiebreakRuleId doc — the
  integer values are report-only conceptual identifiers; evaluation order
  is the list order of allTiebreakRules (which runs MoreConcrete last).
- infos: document that GetOverloadResolutionPriority returns 0 for F#
  override members (priority is fixed by the base declaration, matching C#).
- Tests: hoist the two duplicated embedded sources (incomparable-
  concreteness pair; ORPA-on-override pair) into shared bindings; strip
  leftover hypothesis/phase tags from comments.

Verified: build 0/0; ORPA 16/16; Tiebreaker 123/123; fantomas clean;
differential default-vs-preview safety gate 65 snippets, 0 flips.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Bring in main (incl. .NET 11 migration #20080). Two auto-merge fixes:
- FSharp.Compiler.Service.fsproj + ComponentTests.fsproj: git dropped
  main's new <Compile Include> entries (EncMethodDebugInformation,
  GeneratedNames, CompilerGeneratedNameMapState, SynthesizedTypeMaps,
  Spreads, + 11 test files). Rebased both ItemGroups on main and
  re-inserted this branch's additions.
- FSComp.txt.*.xlf (13): regenerated via XliffTasks so each is the union
  of main's strings and this branch's 9 new trans-units (+9 vs main,
  nothing removed).

Validated on net11: full Arcade build 0/0; ORPA 16/16; Tiebreaker 123/123;
fantomas --check clean.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…aging

The merge commit took main's raw fsprojs (staged by 'git checkout main --')
but my re-inserted <Compile Include> entries were only in the working tree,
never git add-ed. CI's clean build failed (OverloadResolutionRules module
'not defined' from ConstraintSolver.fs). Local build passed because it used
the working tree. Stage the 2 compiler + 3 test entries.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@T-Gro T-Gro changed the title WIP :: Feature :: Overloading :: [ORPA; Tiebreakers] Support OverloadResolutionPriority and a most-concrete tiebreaker Aug 3, 2026
T-Gro and others added 6 commits August 3, 2026 21:44
The "more concrete at position N" breakdown named the wrong candidate
whenever the two ambiguous parameters shared a top-level type constructor
(e.g. Result<int,'e> vs Result<'o,string>). collectComparisons' same-
constructor branch used `args1 |> List.mapi2 f args2`, which the pipe
evaluates as `List.mapi2 f args2 args1`, swapping the two candidates'
type-arguments inside the lambda and inverting the c>0 => meth1 convention
used just below. Overload selection was unaffected (moreConcreteRule and
compareTypeConcreteness pass their lists in the correct order); only this
diagnostic detail was wrong.

Use `(args1, args2) ||> List.mapi2 f` so arg1 comes from meth1 and arg2
from meth2, matching the different-constructor branch and the winner logic.
Strengthen Example 6 to pin the correct type-argument positions.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…oads

The enhanced FS0041 "neither is strictly more concrete" breakdown flattened
per-type-argument indices across all formal parameters, so a method with
several parameters printed ambiguous, duplicated position numbers such as
"is more concrete at positions 1, 1" — where one "1" meant parameter 1 and
the other meant type argument 1 of a later same-constructor parameter.

Report positions at the granularity moreConcreteRule itself compares:
 - single parameter: decompose a same-constructor application into its
   type-argument positions (the flagship Result<int,'e> vs Result<'o,string>
   case), unambiguous because every position is that one parameter's type args;
 - multiple parameters: one comparison per formal parameter, reporting the
   parameter index. A same-constructor parameter that is internally
   incomparable is neutral and drops out.

Add a regression test pinning clean per-parameter positions for a 3-parameter
incomparable pair; the single-parameter flagship output is unchanged.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Rework the two draft RFC documents in this PR into publishable form and
assign their fslang-design FS numbers:

- FS-1340 "most concrete" tiebreaker (was FS-XXXX-most-concrete-tiebreaker.md)
- FS-1338 OverloadResolutionPriorityAttribute (new)

Both were iterated through four adversarial, cross-model review rounds
(accuracy / completeness / clarity) with every normative claim grounded in
the compiler implementation and, where behavioural, a compiled repro. The
rounds also surfaced two real diagnostic defects, fixed separately earlier
in this PR.

Notable accuracy corrections in this pass:
- Tiebreak ordering: type-directed-conversion preferences run ahead of the
  published rules 1-8; the nullable/optional-interop and property-override
  rules run after rule 8; most-concrete is last.
- ORPA prunes only among *applicable* candidates, so an inapplicable
  high-priority overload never shadows an applicable lower-priority one; the
  Drawbacks text now matches the body and the tests.
- Cross-language parity holds for method and constructor calls; indexers
  diverge on an identical applicable set (F# resolves by specificity), while
  a C#-only conversion diverges because the applicable set itself differs.
- Portability example: an argument type annotation does not make a call
  portable (still FS0041 pre-preview), only an explicit type argument does.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Rewrite FS-1340 (258->131) and FS-1338 (255->118) in the concise
FS-1137 register: one example per point, near-empty Pragmatics, and no
re-quoting of published spec rules 1-8. Every normative claim and all
prior accuracy fixes (rule ordering, applicable-set pruning, indexer vs
C#-conversion parity split, portability) are preserved; verified by a
claim-by-claim accuracy pass.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Rewrite both RFCs in a plainer, more declarative register: flatter
sentences, far fewer em-dashes (34 -> 0) and semicolons, no internal
compiler identifiers in the prose, and no meta-asides. All normative
content and prior accuracy fixes are preserved.

Also close the two gaps found by the standalone RFC review:
- FS-1340 now states multi-candidate selection (a candidate wins only
  if strictly preferred over every other applicable candidate; otherwise
  FS0041).
- FS-1338 reconciles override priority: an F# override is always 0
  (the attribute on it is FS3586), while a C#/IL override takes the
  priority of its least-derived declaration.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Nine cross-model reviewers (voice, compactness, clarity; three model
families each) audited the RFCs standalone, without the implementation.

Voice (2/3): drop the enumeration of internal, non-published betterness
rules from both RFCs and describe the ordering by observable behaviour;
remove two meta asides ("intentionally narrow", "the author makes
visibly").

Compactness (3/3): remove the duplicated "constructor has no C# analogue"
sentence in FS-1340; tighten the SRTP/rule-ordering gloss; collapse the
FS-1338 prior-art corner-case list and the triple out-of-scope note.

Clarity (3/3, verified against OverloadResolutionRules.fs): FS-1340 now
states that parameter counting uses the declared arity (params array
counts once, optionals count once) on the uninstantiated formal
signatures, and that extension members take part only after the existing
extension preferences. FS-1338 now notes the single override divergence
(an F# override of an already-prioritized base member stays 0).

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
T-Gro added a commit to fsharp/fslang-design that referenced this pull request Aug 4, 2026
Replace the earlier draft with the compacted, reviewed version aligned
with the implementation in dotnet/fsharp#19277.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
T-Gro and others added 6 commits August 4, 2026 12:37
The FS-1338 and FS-1340 RFCs now live in fsharp/fslang-design
(PR #828 and PR #834); they do not belong in the implementation PR.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The branch's copies of the FCS 11.0.100 and Language preview release
notes had dropped ~13 unrelated entries (Spread operator, NotNullIfNotNull,
reflectionfree ToString, StructLayout, module-rec attrs, the
SynInterpolatedStringPart breaking change, etc.). Restore both files from
the merge-base and re-add only the two intended ORPA/tiebreaker entries,
now linked to their fslang-design RFC PRs (#834, #828).

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The ORPA read path hand-rolled three option-taking helpers
(TryFindFSharpAttributeOpt, TryFindFSharpInt32AttributeOpt,
TryDecodeILAttributeOpt) that scanned a method's full attribute list on
every candidate on every overload resolution — a hot path. Adopt the
existing O(1) cached WellKnownAttribs bitflag mechanism instead:

- Add OverloadResolutionPriorityAttribute to WellKnownILAttributes (il)
  and WellKnownValAttributes, with the matching classify cases.
- Rewrite MethInfo.GetOverloadResolutionPriority to gate on the cached
  HasWellKnownAttribute / ValHasWellKnownAttribute flag, decoding the
  Int32 only on the rare present case via the existing (|ILAttribDecoded|_|)
  and (|ValAttribInt|_|).
- Route the FS3586 override diagnostic through attribsHaveValFlag.
- Delete the three bespoke helpers and the now-unused TcGlobals
  attrib_OverloadResolutionPriorityAttribute slot.

Recognition is now by attribute name/path rather than gated on the type
being resolvable in a framework assembly, matching C#'s name-based ORPA
recognition (a referenced polyfilled attribute on a down-level target is
now honored). Net -11 LOC; behaviour unchanged on targets whose BCL
carries the attribute (ORPA 16/16 + Tiebreaker 124/124 + SurfaceArea
green).

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The WellKnownAttribs adoption recognises OverloadResolutionPriority by name
instead of gating on the framework-resolved TcGlobals slot, so it now honours
the attribute even when the target framework lacks it (a referenced polyfill),
matching Roslyn. This case is unreachable by the existing net-core tests (their
framework always resolves the slot). The guard uses an F# polyfill library and
consumer both targeting netstandard2.0 (slot = None) with return-type-divergent
overloads, so the selection is observable at compile time. Verified RED on the
pre-adoption commit (picks the concrete int overload -> string annotation fails)
and GREEN after it.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Source (OverloadResolutionRules.fs, behavior-preserving):
- extract a getCached HOF so getCachedParamData/getCachedHasSRTP stop
  duplicating the cache dance
- add a preferFlagRule factory and collapse the 9 simple projection rules
  (NoTDC/LessTDC/NullableTDC/NoWarnings/NoParamArray/NoOutArgs/NoOptionalArgs/
  PreferNonExtension/PreferNonGeneric) onto it
- reuse resolveAggregation in compareTypeConcreteness' TType_fun branch
- drop decorative section-banner comments and war-story parentheticals

Tests:
- hoist the copy-pasted per-sequence 'case' helper to one module-level def
- replace the three non-exercising 'Example 13' typecheck-only tests (whose
  extension members had different names, so no overload competition ever
  happened) with one runtime-observed test: an intrinsic Process('t) vs a
  same-named more-concrete extension Process(int), asserting the intrinsic
  executes -- pinning that the last-running most-concrete tiebreaker does not
  override the earlier PreferNonExtension choice
- strip decorative divider banners from ORPTestRunner.fs

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Split the single down-level ORPA recognition test into a shared consumer
helper plus two branch-specific polyfill libraries -- a C# lib (exercises the
ILMeth classification path) and an F# lib (exercises the FSMeth/Val path) --
each defining its own netstandard2.0 OverloadResolutionPriorityAttribute so the
TcGlobals slot is None and name-based recognition must fire. Enable C# libs at
netstandard2.0 (Compiler.fs asNetStandard20).

Also drop the standalone 'override uses least-derived base priority' Fact: its
single assertion is a strict subset of ORPTestRunner.testVirtualBaseOrpa, which
already covers the base, derived-object, and negative-priority-int cases.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@github-project-automation github-project-automation Bot moved this from New to In Progress in F# Compiler and Tooling Aug 5, 2026
T-Gro and others added 3 commits August 6, 2026 13:17
…bmodules

Restructure TiebreakerTests.fs into three sibling modules that make the
langversion gating of the most-concrete tiebreaker self-documenting:

- TiebreakerFixtures: source fixtures shared across the modules (the case
  helper, the incomparable/warning sources, and the flip source lists/
  builders extracted from the former inline Facts).
- AgnosticOfTieBreakerFeature: tests whose outcome is identical with or
  without the feature - either an earlier rule decides them, or they stay
  ambiguous even under preview (incomparable / SRTP-only differences).
- WithTieBreakerFeature: flip sources at --langversion:preview -> resolve.
- WithoutTieBreakerFeature: the EXACT same flip sources pinned to
  langversion 10.0 (feature off) -> FS0041.

Every With test has a 1:1 Without mirror over a shared source, so the
pair proves the resolution is feature-gated rather than incidental. The
suite is the classification oracle: all Without mirrors produce FS0041 at
10.0 and all With tests resolve at preview, empirically confirming each
moved source genuinely flips. No agnostic coverage was dropped.

Tiebreakers 148/148, ORPA 17/17 green.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
… overload won)

Three adversarial reviews found that 5 WithTieBreakerFeature tests only asserted
'typecheck |> shouldSucceed' - they proved the call became unambiguous under
preview but NOT that the most-concrete overload was the one selected. An inverted
tiebreaker that picked the generic overload would have left them green.

Strengthen each to discriminate the winner, downstream of the already-generalized
binding so the feature-off FS0041 mirrors stay intact:

- moreConcretDisabledAmbiguousCases / realWorldSource: append a runtime guard
  (if result <> <concrete-marker> then failwith ...) and run via asExe/compileAndRun.
- example7Source (Task<'T> vs 'T): match the DU result - FromTask expected, FromResult fails.
- example8Source / fsToolkitSource (CE Source): RunSynchronously and match Ok 42, which
  only type-checks/evaluates when the concrete Async<Result<_,_>> overload was chosen.
- 'Multiple bypassed' warning: assert BOTH bypassed generic signatures are named
  (Process: value: 't -> and Process: value: Option<'t> ->), so 'multiple' is verified,
  not just 'at least one FS3576'.

Verified the harness enforces these: an unhandled exception from failwith is caught as
Outcome.Failure (CompilerAssert.executeAssemblyEntryPoint) -> CompilationResult.Failure ->
shouldSucceed throws. Mutation-tested every new discriminator: flipping each expected
value turns exactly its own With-test red (7 cases) while all Without mirrors stay green,
proving the oracles have teeth and remain inert at langversion 10.0.

Tiebreakers 148/148, ORPA 17/17 green.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@T-Gro
T-Gro enabled auto-merge (squash) August 6, 2026 12:42
# Conflicts:
#	src/Compiler/xlf/FSComp.txt.cs.xlf
#	src/Compiler/xlf/FSComp.txt.de.xlf
#	src/Compiler/xlf/FSComp.txt.es.xlf
#	src/Compiler/xlf/FSComp.txt.fr.xlf
#	src/Compiler/xlf/FSComp.txt.it.xlf
#	src/Compiler/xlf/FSComp.txt.ja.xlf
#	src/Compiler/xlf/FSComp.txt.ko.xlf
#	src/Compiler/xlf/FSComp.txt.pl.xlf
#	src/Compiler/xlf/FSComp.txt.pt-BR.xlf
#	src/Compiler/xlf/FSComp.txt.ru.xlf
#	src/Compiler/xlf/FSComp.txt.tr.xlf
#	src/Compiler/xlf/FSComp.txt.zh-Hans.xlf
#	src/Compiler/xlf/FSComp.txt.zh-Hant.xlf
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: In Progress

Development

Successfully merging this pull request may close these issues.

2 participants