Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions docs/release-notes/.FSharp.Compiler.Service/11.0.100.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
* Fix `YieldFromFinal`/`ReturnFromFinal` being incorrectly called in non-tail positions (`for`, `use`, `use!`, `try/with` handler). ([Issue #19402](https://github.com/dotnet/fsharp/issues/19402), [PR #19403](https://github.com/dotnet/fsharp/pull/19403))
* Fixed how the source ranges of warn directives are reported (as trivia) in the parser output (by not reporting leading spaces). ([Issue #19405](https://github.com/dotnet/fsharp/issues/19405), [PR #19408]((https://github.com/dotnet/fsharp/pull/19408)))
* Fix UoM value type `ToString()` returning garbage values when `--checknulls+` is enabled, caused by double address-taking in codegen. ([Issue #19435](https://github.com/dotnet/fsharp/issues/19435), [PR #19440](https://github.com/dotnet/fsharp/pull/19440))
* Fix accessibility and type-matching for CE and 'use' extension method lookups. ([Issue #19349](https://github.com/dotnet/fsharp/issues/19349), [PR #19536](https://github.com/dotnet/fsharp/pull/19536))

### Added

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ let inline noTailCall ceenv = { ceenv with tailCall = false }

let inline TryFindIntrinsicOrExtensionMethInfo collectionSettings (cenv: cenv) (env: TcEnv) m ad nm ty =
AllMethInfosOfTypeInScope collectionSettings cenv.infoReader env.NameEnv (Some nm) ad IgnoreOverrides m ty
|> List.filter (IsExtensionMethCompatibleWithTy cenv.g cenv.amap m ty)

/// Ignores an attribute
let inline IgnoreAttribute _ = None
Expand Down
1 change: 1 addition & 0 deletions src/Compiler/Checking/Expressions/CheckExpressions.fs
Original file line number Diff line number Diff line change
Expand Up @@ -3121,6 +3121,7 @@ let BuildPossiblyConditionalMethodCall (cenv: cenv) env isMutable m isProp minfo

let TryFindIntrinsicOrExtensionMethInfo collectionSettings (cenv: cenv) (env: TcEnv) m ad nm ty =
AllMethInfosOfTypeInScope collectionSettings cenv.infoReader env.NameEnv (Some nm) ad IgnoreOverrides m ty
|> List.filter (IsExtensionMethCompatibleWithTy cenv.g cenv.amap m ty)

let TryFindFSharpSignatureInstanceGetterProperty (cenv: cenv) (env: TcEnv) m nm ty (sigTys: TType list) =
let g = cenv.g
Expand Down
42 changes: 34 additions & 8 deletions src/Compiler/Checking/NameResolution.fs
Original file line number Diff line number Diff line change
Expand Up @@ -727,8 +727,11 @@ let SelectMethInfosFromExtMembers (infoReader: InfoReader) optFilter apparentTy
]

/// Query the available extension methods of a type (including extension methods for inherited types)
let ExtensionMethInfosOfTypeInScope (collectionSettings: ResultCollectionSettings) (infoReader: InfoReader) (nenv: NameResolutionEnv) optFilter isInstanceFilter m ty =
let extMemsDangling = SelectMethInfosFromExtMembers infoReader optFilter ty m nenv.eUnindexedExtensionMembers
let ExtensionMethInfosOfTypeInScope (collectionSettings: ResultCollectionSettings) (infoReader: InfoReader) (nenv: NameResolutionEnv) ad optFilter isInstanceFilter m ty =
let amap = infoReader.amap

let extMemsDangling = SelectMethInfosFromExtMembers infoReader optFilter ty m nenv.eUnindexedExtensionMembers

if collectionSettings = ResultCollectionSettings.AtMostOneResult && not (isNil extMemsDangling) then
extMemsDangling
else
Expand All @@ -743,6 +746,9 @@ let ExtensionMethInfosOfTypeInScope (collectionSettings: ResultCollectionSetting
| _ -> [])
extMemsDangling @ extMemsFromHierarchy
|> List.filter (fun minfo ->
let isAccesible = AccessibilityLogic.IsMethInfoAccessible amap m ad minfo

isAccesible &&
match isInstanceFilter with
| LookupIsInstance.Ambivalent -> true
| LookupIsInstance.Yes -> minfo.IsInstance
Expand All @@ -754,7 +760,27 @@ let AllMethInfosOfTypeInScope collectionSettings infoReader nenv optFilter ad fi
if collectionSettings = ResultCollectionSettings.AtMostOneResult && not (isNil intrinsic) then
intrinsic
else
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?

not minfo.IsExtensionMember ||
match minfo.GetObjArgTypes(amap, m, []) with
| thisTy :: _ ->
let ty1 = thisTy |> stripTyEqns g
let ty2 = ty |> stripTyEqns g

match ty1, ty2 with
| TType_var (tp1, _), _ ->
tp1.Constraints |> List.exists (function
| TyparConstraint.CoercesTo(targetCTy, _) ->
let cTy = targetCTy |> stripTyEqns g
TypeRelations.TypeFeasiblySubsumesType 0 g amap m cTy TypeRelations.CanCoerce ty2
| _ -> false)
| _, TType_var _ -> true
| _ ->
TypeRelations.TypeFeasiblySubsumesType 0 g amap m ty1 TypeRelations.CanCoerce ty2
| _ ->
true

//-------------------------------------------------------------------------
// Helpers to do with building environments
Expand Down Expand Up @@ -1184,7 +1210,7 @@ let rec AddStaticContentOfTypeToNameEnv (g:TcGlobals) (amap: Import.ImportMap) a
[|
// Extension methods
yield!
ExtensionMethInfosOfTypeInScope ResultCollectionSettings.AllResults infoReader nenv None LookupIsInstance.No m ty
ExtensionMethInfosOfTypeInScope ResultCollectionSettings.AllResults infoReader nenv ad None LookupIsInstance.No m ty
|> ChooseMethInfosForNameEnv g m ty

// Extension properties
Expand Down Expand Up @@ -2827,7 +2853,7 @@ let rec ResolveLongIdentInTypePrim (ncenv: NameResolver) nenv lookupKind (resInf
| _ ->
// lookup in-scope extension methods
// to keep in sync with the same expression in `| Some(MethodItem msets) when isLookupExpr` below
match ExtensionMethInfosOfTypeInScope ResultCollectionSettings.AllResults ncenv.InfoReader nenv optFilter isInstanceFilter m ty with
match ExtensionMethInfosOfTypeInScope ResultCollectionSettings.AllResults ncenv.InfoReader nenv ad optFilter isInstanceFilter m ty with
| [] -> success [resInfo, x, rest]
| methods ->
let extensionMethods = Item.MakeMethGroup(nm, methods)
Expand All @@ -2841,7 +2867,7 @@ let rec ResolveLongIdentInTypePrim (ncenv: NameResolver) nenv lookupKind (resInf
let minfos = msets |> ExcludeHiddenOfMethInfos g ncenv.amap m

// fold the available extension members into the overload resolution
let extensionMethInfos = ExtensionMethInfosOfTypeInScope ResultCollectionSettings.AllResults ncenv.InfoReader nenv optFilter isInstanceFilter m ty
let extensionMethInfos = ExtensionMethInfosOfTypeInScope ResultCollectionSettings.AllResults ncenv.InfoReader nenv ad optFilter isInstanceFilter m ty

success [resInfo, Item.MakeMethGroup (nm, minfos@extensionMethInfos), rest]

Expand All @@ -2860,7 +2886,7 @@ let rec ResolveLongIdentInTypePrim (ncenv: NameResolver) nenv lookupKind (resInf

if not (isNil pinfos) && isLookUpExpr then OneResult(success (resInfo, Item.Property (nm, pinfos, None), rest)) else

let minfos = ExtensionMethInfosOfTypeInScope ResultCollectionSettings.AllResults ncenv.InfoReader nenv optFilter isInstanceFilter m ty
let minfos = ExtensionMethInfosOfTypeInScope ResultCollectionSettings.AllResults ncenv.InfoReader nenv ad optFilter isInstanceFilter m ty

if not (isNil minfos) && isLookUpExpr then
success [resInfo, Item.MakeMethGroup (nm, minfos), rest]
Expand Down Expand Up @@ -2898,7 +2924,7 @@ let rec ResolveLongIdentInTypePrim (ncenv: NameResolver) nenv lookupKind (resInf
for p in ExtensionPropInfosOfTypeInScope ResultCollectionSettings.AllResults ncenv.InfoReader nenv None LookupIsInstance.Ambivalent ad m ty do
addToBuffer p.PropertyName

for m in ExtensionMethInfosOfTypeInScope ResultCollectionSettings.AllResults ncenv.InfoReader nenv None LookupIsInstance.Ambivalent m ty do
for m in ExtensionMethInfosOfTypeInScope ResultCollectionSettings.AllResults ncenv.InfoReader nenv ad None LookupIsInstance.Ambivalent m ty do
addToBuffer m.DisplayName

for p in GetIntrinsicPropInfosOfType ncenv.InfoReader None ad AllowMultiIntfInstantiations.No findFlag m ty do
Expand Down
9 changes: 9 additions & 0 deletions src/Compiler/Checking/NameResolution.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -689,6 +689,15 @@ val internal AllMethInfosOfTypeInScope:
ty: TType ->
MethInfo list

/// Check whether the 'this' argument of an extension method is compatible with the target type
val internal IsExtensionMethCompatibleWithTy:
g: TcGlobals ->
amap: ImportMap ->
m: range ->
ty: TType ->
minfo: MethInfo ->
bool

/// Used to report an error condition where name resolution failed due to an indeterminate type
exception internal IndeterminateType of range

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
module Conformance.BasicGrammarElements.UseBindExtensionMethodCapture

open Xunit
open FSharp.Test.Compiler

[<Fact>]
let ``Use binding doesn't capture an extension method with generic type``() =
FSharp """
open System
open System.Runtime.CompilerServices

type FooClass() = class end

type Disposable() =
interface IDisposable with
member _.Dispose() = ()

[<Extension>]
type PublicExtensions =
[<Extension>]
static member inline Dispose(this: #FooClass) =
this

let foo() =
use a = new Disposable()
()

foo()
"""
|> asExe
|> compile
|> shouldSucceed
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
module Conformance.Expressions.CEExtensionMethodCapture

open Xunit
open FSharp.Test.Compiler

[<Fact>]
let ``CE doesn't capture an extension method beyond the access domain``() =
FSharp """
open System.Runtime.CompilerServices

type AsyncSeq<'T>(i: 'T) =
class
let l = [i]
member this.Data = l
end

type AsyncSeqBuilder() =
member _.Yield(x: 'T) : AsyncSeq<'T> =
AsyncSeq(x)

[<Extension>]
type PrivateExtensions =
[<Extension>]
static member inline private Run(this: AsyncSeqBuilder) =
this

let asyncSeq = AsyncSeqBuilder()

let xs : AsyncSeq<int> =
asyncSeq {
yield 1
}
"""
|> asExe
|> compile
|> shouldSucceed

[<Fact>]
let ``CE doesn't capture an extension method with generic type``() =
FSharp """
open System.Runtime.CompilerServices

type FooClass = class end

type AsyncSeq<'T>(i: 'T) =
class
let l = [i]
member this.Data = l
end

type AsyncSeqBuilder() =
member _.Yield(x: 'T) : AsyncSeq<'T> =
AsyncSeq(x)

[<Extension>]
type PublicExtensions =
[<Extension>]
static member inline Run(this: #FooClass) =
this

let asyncSeq = AsyncSeqBuilder()

let xs : AsyncSeq<int> =
asyncSeq {
yield 1
}
"""
|> asExe
|> compile
|> shouldSucceed

// Deliberately trigger an error to ensure that a method is captured
[<Fact>]
let ``CE captures a public extension method and procudes an error due to invalid args``() =
FSharp """
open System.Runtime.CompilerServices

type AsyncSeq<'T>(i: 'T) =
class
let l = [i]
member this.Data = l
end

type AsyncSeqBuilder() =
member _.Yield(x: 'T) : AsyncSeq<'T> =
AsyncSeq(x)

[<Extension>]
type PublicExtensions =
[<Extension>]
static member inline Run(this: AsyncSeqBuilder, invalidArg: string) =
this

let asyncSeq = AsyncSeqBuilder()

let xs : AsyncSeq<int> =
asyncSeq {
yield 1
}
"""
|> asExe
|> compile
|> shouldFail

// Deliberately trigger an error to ensure that a method is captured
[<Fact>]
let ``CE captures a public extension method with valid generic constrainted type and procudes an error due to invalid args``() =
FSharp """
open System.Runtime.CompilerServices

type AsyncSeq<'T>(i: 'T) =
class
let l = [i]
member this.Data = l
end

type AsyncSeqBuilder() =
member _.Yield(x: 'T) : AsyncSeq<'T> =
AsyncSeq(x)

[<Extension>]
type PublicExtensions =
[<Extension>]
static member inline Run(this: #AsyncSeqBuilder, invalidArg: string) =
this

let asyncSeq = AsyncSeqBuilder()

let xs : AsyncSeq<int> =
asyncSeq {
yield 1
}
"""
|> asExe
|> compile
|> shouldFail
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@
<Compile Include="Conformance\BasicGrammarElements\ValueRestriction\ValueRestriction.fs" />
<Compile Include="Conformance\BasicGrammarElements\UseBindings\UseBindings.fs" />
<Compile Include="Conformance\BasicGrammarElements\UseBindings\UseBangBindings.fs" />
<Compile Include="Conformance\BasicGrammarElements\UseBindings\UseBindingsAndExtensionMembers.fs" />
<Compile Include="Conformance\Constraints\Unmanaged.fs" />
<Compile Include="Conformance\GeneratedEqualityHashingComparison\Attributes\Diags\Attributes_Diags.fs" />
<Compile Include="Conformance\GeneratedEqualityHashingComparison\Attributes\Legacy\Attributes_Legacy.fs" />
Expand All @@ -85,6 +86,7 @@
<Compile Include="Conformance\Expressions\ApplicationExpressions\Ctor.fs" />
<Compile Include="Conformance\Expressions\BindingExpressions\BindingExpressions.fs" />
<Compile Include="Conformance\Expressions\ComputationExpressions\ComputationExpressions.fs" />
<Compile Include="Conformance\Expressions\ComputationExpressions\CEExtensionMethodCapture.fs" />
<Compile Include="Conformance\Expressions\ObjectExpressions\ObjectExpressions.fs" />
<Compile Include="Conformance\Expressions\ObjectExpressions\StructObjectExpression.fs" />
<Compile Include="Conformance\Expressions\ControlFlowExpressions\PatternMatching\PatternMatching.fs" />
Expand Down
Loading