Skip to content

Enhance the NameResolution System#19536

Open
evgTSV wants to merge 13 commits intodotnet:mainfrom
evgTSV:fix-overloads-checking
Open

Enhance the NameResolution System#19536
evgTSV wants to merge 13 commits intodotnet:mainfrom
evgTSV:fix-overloads-checking

Conversation

@evgTSV
Copy link
Copy Markdown
Contributor

@evgTSV evgTSV commented Mar 31, 2026

Fixes #19349

-> Fix accessibility and type-matching for pattern-based extension lookups (CE, Dispose, etc.)

Summary

This PR fixes an issue where pattern-based extension method lookups (specifically for Computation Expressions and use bindings) would incorrectly resolve methods that are either inaccessible (private/internal) or belong to incompatible types.

Problem

Previously, the compiler's name resolution for CE-specific methods (like Bind, Delay, Run, etc.) and the Dispose pattern for resource cleanup was overly optimistic. It often bypassed accessibility checks, leading to incorrect method resolution or even compilation of calls to private extension methods from outside their scope.

Checklist

  • Test cases added
  • Release notes entry updated:

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 31, 2026

❗ Release notes required


✅ Found changes and release notes in following paths:

Change path Release notes path Description
src/Compiler docs/release-notes/.FSharp.Compiler.Service/11.0.100.md

@evgTSV evgTSV force-pushed the fix-overloads-checking branch from 3f7dd89 to 170baf1 Compare March 31, 2026 21:21
@evgTSV
Copy link
Copy Markdown
Contributor Author

evgTSV commented Apr 1, 2026

It seems that my changes broke FSharp.Core. I suppose that we have to adjust type equality check to work with the core code.

intrinsic @ ExtensionMethInfosOfTypeInScope collectionSettings infoReader nenv optFilter LookupIsInstance.Ambivalent m ty
intrinsic @ ExtensionMethInfosOfTypeInScope collectionSettings infoReader nenv ad optFilter LookupIsInstance.Ambivalent m ty

let IsExtensionMethCompatibleWithTy g amap m (ty: TType) (minfo: MethInfo) =
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm wondering how is it already done in other cases? Is there a place that this function could be unified with?

| TyparConstraint.CoercesTo(targetCTy, _) ->
let cTy = targetCTy |> stripTyEqns g
TypeRelations.TypeFeasiblySubsumesType 0 g amap m cTy TypeRelations.CanCoerce ty2
| _ -> false)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Breaking Changes] When ty1 is a TType_var with no CoercesTo constraint, List.exists returns false and the extension method is silently excluded. This incorrectly filters out:

  • C# extensions like public static T Run<T>(this T x) (unconstrained generic this)
  • F# extensions like static member Run(this: 'T) with non-CoercesTo constraints (e.g. 'T : struct)
  • Any extension where the this parameter is a type variable constrained only by member/struct/equality constraints

An unconstrained (or non-CoercesTo-constrained) type variable should match any target type. Consider:

| TType_var (tp1, _), _ ->
    let coercesToConstraints =
        tp1.Constraints |> List.choose (function
            | TyparConstraint.CoercesTo(targetCTy, _) -> Some targetCTy
            | _ -> None)
    match coercesToConstraints with
    | [] -> true  // No CoercesTo constraint means it could match anything
    | constraints ->
        constraints |> List.exists (fun targetCTy ->
            let cTy = targetCTy |> stripTyEqns g
            TypeRelations.TypeFeasiblySubsumesType 0 g amap m cTy TypeRelations.CanCoerce ty2)

"""
|> asExe
|> compile
|> shouldFail
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Test Coverage] The existing tests all use #SomeType (which produces a CoercesTo constraint). Please add a test for a C# or F# extension with a truly unconstrained generic this parameter, e.g.:

[<Extension>]
type GenericExtensions =
    [<Extension>]
    static member inline Run(this: 'T) = this

This would currently fail due to the TType_var / no-CoercesTo bug noted above.

Copy link
Copy Markdown
Member

@T-Gro T-Gro left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Automated Copilot Review — AI-assisted initial review. Feedback welcome.

[Breaking Changes] IsExtensionMethCompatibleWithTy has a logic gap in the TType_var branch: when the extension method's this parameter is a type variable with no CoercesTo constraint (unconstrained generic, or constrained only by struct/member/equality), the current code returns false and the method is silently excluded. This affects C# extensions like public static T Run<T>(this T x) and F# extensions with (this: 'T) or (this: 'T when 'T : struct). A type variable with no CoercesTo bounds should match any target type — the [] -> true fallback is missing.

[Test Coverage] All CE extension method tests use #SomeType (which always has a CoercesTo constraint). A test with a genuinely unconstrained 'T on the this parameter would catch the above bug.

@T-Gro T-Gro added the AI-reviewed PR reviewed by AI review council label Apr 10, 2026
@T-Gro T-Gro self-requested a review April 10, 2026 13:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

AI-reviewed PR reviewed by AI review council

Projects

Status: New

Development

Successfully merging this pull request may close these issues.

Unexpected type inference for Run method - generic constraints are checked too late

3 participants