From d83428f5293ea3910aebd931d01d43ceb2db025f Mon Sep 17 00:00:00 2001 From: nojaf Date: Mon, 5 Sep 2022 09:38:29 +0200 Subject: [PATCH 01/10] Allow extension methods without type attribute. --- src/Compiler/Checking/NameResolution.fs | 100 +++++++++--------- .../FSharp.Compiler.ComponentTests.fsproj | 1 + .../Language/ExtensionMethodTests.fs | 73 +++++++++++++ 3 files changed, 125 insertions(+), 49 deletions(-) create mode 100644 tests/FSharp.Compiler.ComponentTests/Language/ExtensionMethodTests.fs diff --git a/src/Compiler/Checking/NameResolution.fs b/src/Compiler/Checking/NameResolution.fs index 56002f6e0e9..721732497e5 100644 --- a/src/Compiler/Checking/NameResolution.fs +++ b/src/Compiler/Checking/NameResolution.fs @@ -517,60 +517,62 @@ let IsMethInfoPlainCSharpStyleExtensionMember g m isEnclExtTy (minfo: MethInfo) /// Get the info for all the .NET-style extension members listed as static members in the type. let private GetCSharpStyleIndexedExtensionMembersForTyconRef (amap: Import.ImportMap) m (tcrefOfStaticClass: TyconRef) = let g = amap.g + let ty = generalizedTyconRef g tcrefOfStaticClass - if IsTyconRefUsedForCSharpStyleExtensionMembers g m tcrefOfStaticClass then + let minfos = + GetImmediateIntrinsicMethInfosOfType (None, AccessorDomain.AccessibleFromSomeFSharpCode) g amap m ty + |> List.filter (IsMethInfoPlainCSharpStyleExtensionMember g m true) + + if IsTyconRefUsedForCSharpStyleExtensionMembers g m tcrefOfStaticClass || not minfos.IsEmpty then let pri = NextExtensionMethodPriority() - let ty = generalizedTyconRef g tcrefOfStaticClass - let minfos = GetImmediateIntrinsicMethInfosOfType (None, AccessorDomain.AccessibleFromSomeFSharpCode) g amap m ty [ for minfo in minfos do - if IsMethInfoPlainCSharpStyleExtensionMember g m true minfo then - let ilExtMem = ILExtMem (tcrefOfStaticClass, minfo, pri) - - // The results are indexed by the TyconRef of the first 'this' argument, if any. - // So we need to go and crack the type of the 'this' argument. - // - // This is convoluted because we only need the ILTypeRef of the first argument, and we don't - // want to read any other metadata as it can trigger missing-assembly errors. It turns out ImportILTypeRef - // is less eager in reading metadata than GetParamTypes. - // - // We don't use the index for the IL extension method for tuple of F# function types (e.g. if extension - // methods for tuple occur in C# code) - let thisTyconRef = - try - let rs = - match metadataOfTycon tcrefOfStaticClass.Deref, minfo with - | ILTypeMetadata (TILObjectReprData(scoref, _, _)), ILMeth(_, ILMethInfo(_, _, _, ilMethod, _), _) -> - match ilMethod.ParameterTypes with - | firstTy :: _ -> - match firstTy with - | ILType.Boxed tspec | ILType.Value tspec -> - let tref = (tspec |> rescopeILTypeSpec scoref).TypeRef - if Import.CanImportILTypeRef amap m tref then - let tcref = tref |> Import.ImportILTypeRef amap m - if isCompiledTupleTyconRef g tcref || tyconRefEq g tcref g.fastFunc_tcr then None - else Some tcref - else None - | _ -> None - | _ -> None - | _ -> - // The results are indexed by the TyconRef of the first 'this' argument, if any. - // So we need to go and crack the type of the 'this' argument. - let thisTy = minfo.GetParamTypes(amap, m, generalizeTypars minfo.FormalMethodTypars).Head.Head - match thisTy with - | AppTy g (tcrefOfTypeExtended, _) when not (isByrefTy g thisTy) -> Some tcrefOfTypeExtended + let ilExtMem = ILExtMem (tcrefOfStaticClass, minfo, pri) + + // The results are indexed by the TyconRef of the first 'this' argument, if any. + // So we need to go and crack the type of the 'this' argument. + // + // This is convoluted because we only need the ILTypeRef of the first argument, and we don't + // want to read any other metadata as it can trigger missing-assembly errors. It turns out ImportILTypeRef + // is less eager in reading metadata than GetParamTypes. + // + // We don't use the index for the IL extension method for tuple of F# function types (e.g. if extension + // methods for tuple occur in C# code) + let thisTyconRef = + try + let rs = + match metadataOfTycon tcrefOfStaticClass.Deref, minfo with + | ILTypeMetadata (TILObjectReprData(scoref, _, _)), ILMeth(_, ILMethInfo(_, _, _, ilMethod, _), _) -> + match ilMethod.ParameterTypes with + | firstTy :: _ -> + match firstTy with + | ILType.Boxed tspec | ILType.Value tspec -> + let tref = (tspec |> rescopeILTypeSpec scoref).TypeRef + if Import.CanImportILTypeRef amap m tref then + let tcref = tref |> Import.ImportILTypeRef amap m + if isCompiledTupleTyconRef g tcref || tyconRefEq g tcref g.fastFunc_tcr then None + else Some tcref + else None | _ -> None - - Some rs - - with e -> // Import of the ILType may fail, if so report the error and skip on - errorRecovery e m - None - - match thisTyconRef with - | None -> () - | Some (Some tcref) -> yield Choice1Of2(tcref, ilExtMem) - | Some None -> yield Choice2Of2 ilExtMem ] + | _ -> None + | _ -> + // The results are indexed by the TyconRef of the first 'this' argument, if any. + // So we need to go and crack the type of the 'this' argument. + let thisTy = minfo.GetParamTypes(amap, m, generalizeTypars minfo.FormalMethodTypars).Head.Head + match thisTy with + | AppTy g (tcrefOfTypeExtended, _) when not (isByrefTy g thisTy) -> Some tcrefOfTypeExtended + | _ -> None + + Some rs + + with e -> // Import of the ILType may fail, if so report the error and skip on + errorRecovery e m + None + + match thisTyconRef with + | None -> () + | Some (Some tcref) -> yield Choice1Of2(tcref, ilExtMem) + | Some None -> yield Choice2Of2 ilExtMem ] else [] diff --git a/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj b/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj index 75e0f8a0323..13f4629c5b8 100644 --- a/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj +++ b/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj @@ -172,6 +172,7 @@ + diff --git a/tests/FSharp.Compiler.ComponentTests/Language/ExtensionMethodTests.fs b/tests/FSharp.Compiler.ComponentTests/Language/ExtensionMethodTests.fs new file mode 100644 index 00000000000..522104cb9cf --- /dev/null +++ b/tests/FSharp.Compiler.ComponentTests/Language/ExtensionMethodTests.fs @@ -0,0 +1,73 @@ +namespace FSharp.Compiler.ComponentTests.Language + +open Xunit +open FSharp.Test.Compiler + +module ExtensionMethodTests = + + [] + let ``Extension method without toplevel attribute on type`` () = + Fsx + """ +open System.Runtime.CompilerServices + +[] +type Foo = + [] + static member PlusOne (a:int) : int = a + 1 + +let f (b:int) = b.PlusOne() + """ + |> compile + |> shouldSucceed + + [] + let ``Extension method with toplevel attribute on type also still works`` () = + Fsx + """ +open System.Runtime.CompilerServices + +type Foo = + [] + static member PlusOne (a:int) : int = a + 1 + +let f (b:int) = b.PlusOne() + """ + |> compile + |> shouldSucceed + + [] + let ``F# CSharpStyleExtensionMethod consumed in C#`` () = + let fsharp = + FSharp + """ +module Hello + +open System.Runtime.CompilerServices + +type Foo = + [] + static member PlusOne (a:int) : int = a + 1 +""" + |> withName "FSLib" + + let csharp = + CSharp + """ +namespace Consumer +{ + using static Hello.Foo; + + public class Class1 + { + public Class1() + { + var meh = 1.PlusOne(); + } + } +} +""" + |> withName "CSLib" + |> withReferences [ fsharp ] + + csharp |> compile |> shouldSucceed From c1fbfc141561a18d2b93117085b9f63413ef3daa Mon Sep 17 00:00:00 2001 From: nojaf Date: Fri, 9 Sep 2022 17:49:58 +0200 Subject: [PATCH 02/10] Add Extension attribute to type if any member has the attribute. --- src/Compiler/Checking/CheckDeclarations.fs | 31 +++++++ .../Language/ExtensionMethodTests.fs | 80 ++++++++++++++++++- 2 files changed, 109 insertions(+), 2 deletions(-) diff --git a/src/Compiler/Checking/CheckDeclarations.fs b/src/Compiler/Checking/CheckDeclarations.fs index ac0080ca829..eb514150a27 100644 --- a/src/Compiler/Checking/CheckDeclarations.fs +++ b/src/Compiler/Checking/CheckDeclarations.fs @@ -4195,6 +4195,37 @@ module TcDeclarations = // Check the members and decide on representations for types with implicit constructors. let withBindings, envFinal = TcMutRecDefns_Phase2 cenv envInitial m scopem mutRecNSInfo envMutRecPrelimWithReprs withEnvs + // If any of the types has a member with the System.Runtime.CompilerServices.ExtensionAttribute, + // that type should also received the ExtensionAttribute if it is not yet present. + // Example: + // open System.Runtime.CompilerServices + // + // type Int32Extensions = + // [] + // static member PlusOne (a:int) : int = a + 1 + let withBindings = + let tryFindExtensionAttribute (attribs: Attrib list) = + List.tryFind + (fun (a: Attrib) -> + a.TyconRef.CompiledRepresentationForNamedType.BasicQualifiedName = "System.Runtime.CompilerServices.ExtensionAttribute") + attribs + + withBindings + |> List.map (function + | MutRecShape.Tycon (Some tycon, bindings) -> + if Option.isSome (tryFindExtensionAttribute tycon.Attribs) then + MutRecShape.Tycon (Some tycon, bindings) + else + let extensionAttribute = + tycon.MembersOfFSharpTyconSorted + |> Seq.choose (fun m -> tryFindExtensionAttribute m.Attribs) + |> Seq.tryHead + + match extensionAttribute with + | None -> MutRecShape.Tycon (Some tycon, bindings) + | Some a -> MutRecShape.Tycon (Some { tycon with entity_attribs = a :: tycon.Attribs }, bindings) + | shape -> shape) + // Generate the hash/compare/equality bindings for all tycons. // // Note: generating these bindings must come after generating the members, since some in the case of structs some fields diff --git a/tests/FSharp.Compiler.ComponentTests/Language/ExtensionMethodTests.fs b/tests/FSharp.Compiler.ComponentTests/Language/ExtensionMethodTests.fs index 522104cb9cf..b97256a3c63 100644 --- a/tests/FSharp.Compiler.ComponentTests/Language/ExtensionMethodTests.fs +++ b/tests/FSharp.Compiler.ComponentTests/Language/ExtensionMethodTests.fs @@ -6,7 +6,7 @@ open FSharp.Test.Compiler module ExtensionMethodTests = [] - let ``Extension method without toplevel attribute on type`` () = + let ``Extension method with toplevel attribute on type`` () = Fsx """ open System.Runtime.CompilerServices @@ -22,7 +22,7 @@ let f (b:int) = b.PlusOne() |> shouldSucceed [] - let ``Extension method with toplevel attribute on type also still works`` () = + let ``Extension method without toplevel attribute on type`` () = Fsx """ open System.Runtime.CompilerServices @@ -71,3 +71,79 @@ namespace Consumer |> withReferences [ fsharp ] csharp |> compile |> shouldSucceed + + [] + let ``F# CSharpStyleExtensionMethod defined in top level module with attribute consumed in C#`` () = + let fsharp = + FSharp + """ +namespace Hello + +open System.Runtime.CompilerServices + +[] +module Foo = + [] + let PlusOne (a:int) : int = a + 1 +""" + |> withName "FSLib" + + let csharp = + CSharp + """ +namespace Consumer +{ + using static Hello.Foo; + + public class Class1 + { + public Class1() + { + var meh = 1.PlusOne(); + } + } +} +""" + |> withName "CSLib" + |> withReferences [ fsharp ] + + csharp |> compile |> shouldSucceed + + [] + let ``F# CSharpStyleExtensionMethod defined in top level module without attribute consumed in C#`` () = + let fsharp = + FSharp + """ +namespace Hello + +open System.Runtime.CompilerServices + +module Foo = + [] + let PlusOne (a:int) : int = a + 1 +""" + |> withName "FSLib" + + let csharp = + CSharp + """ +namespace Consumer +{ + using static Hello.Foo; + + public class Class1 + { + public Class1() + { + var meh = 1.PlusOne(); + } + } +} +""" + |> withName "CSLib" + |> withReferences [ fsharp ] + + csharp |> compile |> shouldSucceed + +// TODO: verify nested module versus toplevel modules, recursive modules versus non recursive modules +// This may require some refactoring to extract some shared logic. \ No newline at end of file From c7b0f578f904c59b942f8aedd336ad2034f63d94 Mon Sep 17 00:00:00 2001 From: nojaf Date: Fri, 16 Sep 2022 16:01:55 +0200 Subject: [PATCH 03/10] Handle C# extensions as let bindings in a module. --- src/Compiler/Checking/CheckDeclarations.fs | 70 +++++++--- src/Compiler/TypedTree/TypedTreeOps.fs | 20 ++- src/Compiler/TypedTree/TypedTreeOps.fsi | 4 + .../Language/ExtensionMethodTests.fs | 131 +++++++++++++++++- 4 files changed, 204 insertions(+), 21 deletions(-) diff --git a/src/Compiler/Checking/CheckDeclarations.fs b/src/Compiler/Checking/CheckDeclarations.fs index eb514150a27..fbe73720523 100644 --- a/src/Compiler/Checking/CheckDeclarations.fs +++ b/src/Compiler/Checking/CheckDeclarations.fs @@ -4196,34 +4196,45 @@ module TcDeclarations = let withBindings, envFinal = TcMutRecDefns_Phase2 cenv envInitial m scopem mutRecNSInfo envMutRecPrelimWithReprs withEnvs // If any of the types has a member with the System.Runtime.CompilerServices.ExtensionAttribute, - // that type should also received the ExtensionAttribute if it is not yet present. + // or a recursive module has a binding with the System.Runtime.CompilerServices.ExtensionAttribute, + // that type/recursive module should also received the ExtensionAttribute if it is not yet present. // Example: // open System.Runtime.CompilerServices // // type Int32Extensions = // [] // static member PlusOne (a:int) : int = a + 1 + // + // // or + // + // module recFoo + // + // [] + // let PlusOne (a:int) = a + 1 let withBindings = - let tryFindExtensionAttribute (attribs: Attrib list) = - List.tryFind - (fun (a: Attrib) -> - a.TyconRef.CompiledRepresentationForNamedType.BasicQualifiedName = "System.Runtime.CompilerServices.ExtensionAttribute") - attribs - withBindings |> List.map (function | MutRecShape.Tycon (Some tycon, bindings) -> - if Option.isSome (tryFindExtensionAttribute tycon.Attribs) then - MutRecShape.Tycon (Some tycon, bindings) - else - let extensionAttribute = - tycon.MembersOfFSharpTyconSorted - |> Seq.choose (fun m -> tryFindExtensionAttribute m.Attribs) - |> Seq.tryHead - - match extensionAttribute with - | None -> MutRecShape.Tycon (Some tycon, bindings) - | Some a -> MutRecShape.Tycon (Some { tycon with entity_attribs = a :: tycon.Attribs }, bindings) + let tycon = + tryAddExtensionAttributeIfNotAlreadyPresent + (fun tryFindExtensionAttribute -> + tycon.MembersOfFSharpTyconSorted + |> Seq.choose (fun m -> tryFindExtensionAttribute m.Attribs) + |> Seq.tryHead + ) + tycon + MutRecShape.Tycon (Some tycon, bindings) + | MutRecShape.Module ((MutRecDefnsPhase2DataForModule(moduleOrNamespaceType, entity), env), shapes) -> + let entity = + tryAddExtensionAttributeIfNotAlreadyPresent + (fun tryFindExtensionAttribute -> + moduleOrNamespaceType.Value.AllValsAndMembers + |> Seq.choose (fun v -> tryFindExtensionAttribute v.Attribs) + |> Seq.tryHead + ) + entity + + MutRecShape.Module ((MutRecDefnsPhase2DataForModule(moduleOrNamespaceType, entity), env), shapes) | shape -> shape) // Generate the hash/compare/equality bindings for all tycons. @@ -4762,6 +4773,29 @@ let rec TcModuleOrNamespaceElementNonMutRec (cenv: cenv) parent typeNames scopem // Get the inferred type of the decls and record it in the modul. moduleEntity.entity_modul_type <- MaybeLazy.Strict moduleTyAcc.Value + + // If any of the let bindings inside the module has the System.Runtime.CompilerServices.ExtensionAttribute, + // that module should also received the ExtensionAttribute if it is not yet present. + // Example: + // module Foo + // + //[] + //let PlusOne (a:int) = a + 1 + let moduleEntity = + tryAddExtensionAttributeIfNotAlreadyPresent + (fun tryFindExtensionAttribute -> + match moduleContents with + | ModuleOrNamespaceContents.TMDefs(defs) -> + defs + |> Seq.choose (function + | ModuleOrNamespaceContents.TMDefLet (Binding.TBind(var = v),_) -> + tryFindExtensionAttribute v.Attribs + | _ -> None) + |> Seq.tryHead + | _ -> None + ) + moduleEntity + let moduleDef = TMDefRec(false, [], [], [ModuleOrNamespaceBinding.Module(moduleEntity, moduleContents)], m) PublishModuleDefn cenv env moduleEntity diff --git a/src/Compiler/TypedTree/TypedTreeOps.fs b/src/Compiler/TypedTree/TypedTreeOps.fs index f57954f7bf5..6fa70a5903e 100644 --- a/src/Compiler/TypedTree/TypedTreeOps.fs +++ b/src/Compiler/TypedTree/TypedTreeOps.fs @@ -10410,4 +10410,22 @@ let (|EmptyModuleOrNamespaces|_|) (moduleOrNamespaceContents: ModuleOrNamespaceC Some emptyModuleOrNamespaces else None - | _ -> None \ No newline at end of file + | _ -> None + +let tryAddExtensionAttributeIfNotAlreadyPresent + (tryFindExtensionAttributeIn: (Attrib list -> Attrib option) -> Attrib option) + (entity: Entity) + : Entity + = + let tryFindExtensionAttribute (attribs: Attrib list): Attrib option = + List.tryFind + (fun (a: Attrib) -> + a.TyconRef.CompiledRepresentationForNamedType.BasicQualifiedName = "System.Runtime.CompilerServices.ExtensionAttribute") + attribs + + if Option.isSome (tryFindExtensionAttribute entity.Attribs) then + entity + else + match tryFindExtensionAttributeIn tryFindExtensionAttribute with + | None -> entity + | Some extensionAttrib -> { entity with entity_attribs = extensionAttrib :: entity.Attribs } diff --git a/src/Compiler/TypedTree/TypedTreeOps.fsi b/src/Compiler/TypedTree/TypedTreeOps.fsi index 00d838e04d4..ae58019683a 100755 --- a/src/Compiler/TypedTree/TypedTreeOps.fsi +++ b/src/Compiler/TypedTree/TypedTreeOps.fsi @@ -2687,3 +2687,7 @@ type TraitConstraintInfo with /// This will match anything that does not have any types or bindings. val (|EmptyModuleOrNamespaces|_|): moduleOrNamespaceContents: ModuleOrNamespaceContents -> (ModuleOrNamespace list) option + +/// Add an System.Runtime.CompilerServices.ExtensionAttribute to the Entity if found via predicate and not already present. +val tryAddExtensionAttributeIfNotAlreadyPresent: + tryFindExtensionAttributeIn: ((Attrib list -> Attrib option) -> Attrib option) -> entity: Entity -> Entity diff --git a/tests/FSharp.Compiler.ComponentTests/Language/ExtensionMethodTests.fs b/tests/FSharp.Compiler.ComponentTests/Language/ExtensionMethodTests.fs index b97256a3c63..f0994d449f0 100644 --- a/tests/FSharp.Compiler.ComponentTests/Language/ExtensionMethodTests.fs +++ b/tests/FSharp.Compiler.ComponentTests/Language/ExtensionMethodTests.fs @@ -145,5 +145,132 @@ namespace Consumer csharp |> compile |> shouldSucceed -// TODO: verify nested module versus toplevel modules, recursive modules versus non recursive modules -// This may require some refactoring to extract some shared logic. \ No newline at end of file + [] + let ``Toplevel named module with Extension attribute and top level let binding with Extension attribute`` () = + let fsharp = + FSharp """ + [] + module Foo + + [] + let PlusOne (a:int) = a + 1 + """ + |> withName "FSLib" + + let csharp = + CSharp """ + namespace Consumer + { + using static Foo; + + public class Class1 + { + public Class1() + { + var meh = 1.PlusOne(); + } + } + } + """ + + |> withName "CSLib" + |> withReferences [ fsharp ] + + csharp |> compile |> shouldSucceed + + [] + let ``Toplevel named module without Extension attribute and top level let binding with Extension attribute`` () = + let fsharp = + FSharp """ + module Foo + + [] + let PlusOne (a:int) = a + 1 + """ + |> withName "FSLib" + + let csharp = + CSharp """ + namespace Consumer + { + using static Foo; + + public class Class1 + { + public Class1() + { + var meh = 1.PlusOne(); + } + } + } + """ + + |> withName "CSLib" + |> withReferences [ fsharp ] + + csharp |> compile |> shouldSucceed + + [] + let ``Recursive toplevel named module with Extension attribute and top level let binding with Extension attribute`` () = + let fsharp = + FSharp """ + [] + module rec Foo + + [] + let PlusOne (a:int) = a + 1 + """ + |> withName "FSLib" + + let csharp = + CSharp """ + namespace Consumer + { + using static Foo; + + public class Class1 + { + public Class1() + { + var meh = 1.PlusOne(); + } + } + } + """ + + |> withName "CSLib" + |> withReferences [ fsharp ] + + csharp |> compile |> shouldSucceed + + [] + let ``Recursive toplevel named module without Extension attribute and top level let binding with Extension attribute`` () = + let fsharp = + FSharp """ + module rec Foo + + [] + let PlusOne (a:int) = a + 1 + """ + |> withName "FSLib" + + let csharp = + CSharp """ + namespace Consumer + { + using static Foo; + + public class Class1 + { + public Class1() + { + var meh = 1.PlusOne(); + } + } + } + """ + + |> withName "CSLib" + |> withReferences [ fsharp ] + + csharp |> compile |> shouldSucceed \ No newline at end of file From 7fa66cff759844105233decad856da05cae6f676 Mon Sep 17 00:00:00 2001 From: nojaf Date: Fri, 30 Sep 2022 15:53:38 +0200 Subject: [PATCH 04/10] Add test for recursive types. --- .../Language/ExtensionMethodTests.fs | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/tests/FSharp.Compiler.ComponentTests/Language/ExtensionMethodTests.fs b/tests/FSharp.Compiler.ComponentTests/Language/ExtensionMethodTests.fs index f0994d449f0..db45d742074 100644 --- a/tests/FSharp.Compiler.ComponentTests/Language/ExtensionMethodTests.fs +++ b/tests/FSharp.Compiler.ComponentTests/Language/ExtensionMethodTests.fs @@ -36,6 +36,24 @@ let f (b:int) = b.PlusOne() |> compile |> shouldSucceed + [] + let ``Extension method without toplevel attribute on recursive type`` () = + Fsx + """ +open System.Runtime.CompilerServices + +type Foo = + class + end +and Bar = + [] + static member PlusOne (a:int) : int = a + 1 + +let f (b:int) = b.PlusOne() + """ + |> compile + |> shouldSucceed + [] let ``F# CSharpStyleExtensionMethod consumed in C#`` () = let fsharp = @@ -72,6 +90,45 @@ namespace Consumer csharp |> compile |> shouldSucceed + [] + let ``F# CSharpStyleExtensionMethod in recursive type consumed in C#`` () = + let fsharp = + FSharp + """ +module Hello + +open System.Runtime.CompilerServices + +type Foo = + class + end +and Bar = + [] + static member PlusOne (a:int) : int = a + 1 +""" + |> withName "FSLib" + + let csharp = + CSharp + """ +namespace Consumer +{ + using static Hello.Bar; + + public class Class1 + { + public Class1() + { + var meh = 1.PlusOne(); + } + } +} +""" + |> withName "CSLib" + |> withReferences [ fsharp ] + + csharp |> compile |> shouldSucceed + [] let ``F# CSharpStyleExtensionMethod defined in top level module with attribute consumed in C#`` () = let fsharp = From 710bedd4a1617a49c42100f06b0e5f5f8f7ec0d0 Mon Sep 17 00:00:00 2001 From: nojaf Date: Fri, 30 Sep 2022 17:43:45 +0200 Subject: [PATCH 05/10] Add tests for types in recursive modules. --- src/Compiler/Checking/CheckDeclarations.fs | 17 +- .../Language/ExtensionMethodTests.fs | 170 +++++++++++++++++- 2 files changed, 183 insertions(+), 4 deletions(-) diff --git a/src/Compiler/Checking/CheckDeclarations.fs b/src/Compiler/Checking/CheckDeclarations.fs index fbe73720523..5e47e858a92 100644 --- a/src/Compiler/Checking/CheckDeclarations.fs +++ b/src/Compiler/Checking/CheckDeclarations.fs @@ -954,6 +954,19 @@ module MutRecBindingChecking = | rest -> rest let prelimRecValues = [ for x in defnAs do match x with Phase2AMember bind -> yield bind.RecBindingInfo.Val | _ -> () ] + + let tyconOpt = + tyconOpt + |> Option.map (fun tycon -> + tryAddExtensionAttributeIfNotAlreadyPresent + (fun tryFindExtensionAttribute -> + tycon.MembersOfFSharpTyconSorted + |> Seq.choose (fun m -> tryFindExtensionAttribute m.Attribs) + |> Seq.tryHead + ) + tycon + ) + let defnAs = MutRecShape.Tycon(TyconBindingsPhase2A(tyconOpt, declKind, prelimRecValues, tcref, copyOfTyconTypars, thisTy, defnAs)) defnAs, (tpenv, recBindIdx, uncheckedBindsRev)) @@ -4205,9 +4218,9 @@ module TcDeclarations = // [] // static member PlusOne (a:int) : int = a + 1 // - // // or + // or // - // module recFoo + // module rec Foo // // [] // let PlusOne (a:int) = a + 1 diff --git a/tests/FSharp.Compiler.ComponentTests/Language/ExtensionMethodTests.fs b/tests/FSharp.Compiler.ComponentTests/Language/ExtensionMethodTests.fs index db45d742074..4be30456b09 100644 --- a/tests/FSharp.Compiler.ComponentTests/Language/ExtensionMethodTests.fs +++ b/tests/FSharp.Compiler.ComponentTests/Language/ExtensionMethodTests.fs @@ -1,5 +1,6 @@ namespace FSharp.Compiler.ComponentTests.Language +open FSharp.Test open Xunit open FSharp.Test.Compiler @@ -299,7 +300,7 @@ namespace Consumer |> withReferences [ fsharp ] csharp |> compile |> shouldSucceed - + [] let ``Recursive toplevel named module without Extension attribute and top level let binding with Extension attribute`` () = let fsharp = @@ -330,4 +331,169 @@ namespace Consumer |> withName "CSLib" |> withReferences [ fsharp ] - csharp |> compile |> shouldSucceed \ No newline at end of file + csharp |> compile |> shouldSucceed + + [] + let ``Foobar `` () = + let fsharp = + FSharp """ +module rec Foo + +[] +type Bar = + [] + static member PlusOne (a:int) = a + 1 + """ + |> withName "FSLib" + + let csharp = + CSharp """ + namespace Consumer + { + using static Foo.Bar; + + public class Class1 + { + public Class1() + { + var meh = 1.PlusOne(); + } + } + } + """ + |> withName "CSLib" + |> withReferences [ fsharp ] + + csharp |> compile |> shouldSucceed + + [] + let ``Recursive named module with type with CSharp style extension can be consumed in CSharp`` () = + let fsharp = + FSharp """ +module rec Foo + +type Bar = + [] + static member PlusOne (a:int) = a + 1 + """ + |> withName "FSLib" + + let csharp = + CSharp """ + namespace Consumer + { + using static Foo.Bar; + + public class Class1 + { + public Class1() + { + var meh = 1.PlusOne(); + } + } + } + """ + |> withName "CSLib" + |> withReferences [ fsharp ] + + csharp |> compile |> shouldSucceed + + [] + let ``CSharp style extension method in F# type backed by a signature`` () = + let implementation = + SourceCodeFileKind.Create( + "Source.fs", + """ +module Foo + +open System.Runtime.CompilerServices + +type Bar = + [] + static member PlusOne (a:int) : int = a + 1 +""" + ) + + let fsharp = + Fsi """ +module Foo + +open System.Runtime.CompilerServices + +[] +type Bar = + [] + static member PlusOne: a: int -> int +""" + |> withAdditionalSourceFile implementation + |> withName "FSLib" + + let csharp = + CSharp """ + namespace Consumer + { + using static Foo.Bar; + + public class Class1 + { + public Class1() + { + var meh = 1.PlusOne(); + } + } + } + """ + |> withName "CSLib" + |> withReferences [ fsharp ] + + csharp |> compile |> shouldSucceed + + [] + let ``CSharp style extension method in F# type backed by a signature in a recursive module`` () = + let implementation = + SourceCodeFileKind.Create( + "Source.fs", + """ +module rec Foo + +open System.Runtime.CompilerServices + +type Bar = + [] + static member PlusOne (a:int) : int = a + 1 +""" + ) + + let fsharp = + Fsi """ +module rec Foo + +open System.Runtime.CompilerServices + +[] +type Bar = + [] + static member PlusOne: a: int -> int +""" + |> withAdditionalSourceFile implementation + |> withName "FSLib" + + let csharp = + CSharp """ + namespace Consumer + { + using static Foo.Bar; + + public class Class1 + { + public Class1() + { + var meh = 1.PlusOne(); + } + } + } + """ + |> withName "CSLib" + |> withReferences [ fsharp ] + + csharp |> compile |> shouldSucceed From 9a853ecfcbec3eb0372e8f60eb51af1da1803557 Mon Sep 17 00:00:00 2001 From: nojaf Date: Fri, 7 Oct 2022 15:03:11 +0200 Subject: [PATCH 06/10] Only add attribute to module when module declaration has attribute. --- src/Compiler/Checking/CheckDeclarations.fs | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/Compiler/Checking/CheckDeclarations.fs b/src/Compiler/Checking/CheckDeclarations.fs index 5e47e858a92..81ed9a8234f 100644 --- a/src/Compiler/Checking/CheckDeclarations.fs +++ b/src/Compiler/Checking/CheckDeclarations.fs @@ -961,8 +961,7 @@ module MutRecBindingChecking = tryAddExtensionAttributeIfNotAlreadyPresent (fun tryFindExtensionAttribute -> tycon.MembersOfFSharpTyconSorted - |> Seq.choose (fun m -> tryFindExtensionAttribute m.Attribs) - |> Seq.tryHead + |> Seq.tryPick (fun m -> tryFindExtensionAttribute m.Attribs) ) tycon ) @@ -4232,8 +4231,7 @@ module TcDeclarations = tryAddExtensionAttributeIfNotAlreadyPresent (fun tryFindExtensionAttribute -> tycon.MembersOfFSharpTyconSorted - |> Seq.choose (fun m -> tryFindExtensionAttribute m.Attribs) - |> Seq.tryHead + |> Seq.tryPick (fun m -> tryFindExtensionAttribute m.Attribs) ) tycon MutRecShape.Tycon (Some tycon, bindings) @@ -4242,8 +4240,8 @@ module TcDeclarations = tryAddExtensionAttributeIfNotAlreadyPresent (fun tryFindExtensionAttribute -> moduleOrNamespaceType.Value.AllValsAndMembers - |> Seq.choose (fun v -> tryFindExtensionAttribute v.Attribs) - |> Seq.tryHead + |> Seq.filter(fun v -> v.IsModuleBinding) + |> Seq.tryPick (fun v -> tryFindExtensionAttribute v.Attribs) ) entity @@ -4800,11 +4798,10 @@ let rec TcModuleOrNamespaceElementNonMutRec (cenv: cenv) parent typeNames scopem match moduleContents with | ModuleOrNamespaceContents.TMDefs(defs) -> defs - |> Seq.choose (function + |> Seq.tryPick (function | ModuleOrNamespaceContents.TMDefLet (Binding.TBind(var = v),_) -> tryFindExtensionAttribute v.Attribs | _ -> None) - |> Seq.tryHead | _ -> None ) moduleEntity From c0c34259b8e9c21f2265f3d1781de9cf61dc72a4 Mon Sep 17 00:00:00 2001 From: nojaf Date: Fri, 7 Oct 2022 15:17:06 +0200 Subject: [PATCH 07/10] Add additional test. --- .../Language/ExtensionMethodTests.fs | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/tests/FSharp.Compiler.ComponentTests/Language/ExtensionMethodTests.fs b/tests/FSharp.Compiler.ComponentTests/Language/ExtensionMethodTests.fs index 4be30456b09..dc883736b5a 100644 --- a/tests/FSharp.Compiler.ComponentTests/Language/ExtensionMethodTests.fs +++ b/tests/FSharp.Compiler.ComponentTests/Language/ExtensionMethodTests.fs @@ -497,3 +497,38 @@ type Bar = |> withReferences [ fsharp ] csharp |> compile |> shouldSucceed + + [] + let ``Multiple top level let binding with Extension attribute`` () = + let fsharp = + FSharp """ + module Foo + + [] + let PlusOne (a:int) = a + 1 + + [] + let MinusOne (a:int) = a - 1 + """ + |> withName "FSLib" + + let csharp = + CSharp """ + namespace Consumer + { + using static Foo; + + public class Class1 + { + public Class1() + { + var meh = 1.PlusOne().MinusOne(); + } + } + } + """ + + |> withName "CSLib" + |> withReferences [ fsharp ] + + csharp |> compile |> shouldSucceed From a336508020cb5b4b8327f6bd9a48990bead50e6a Mon Sep 17 00:00:00 2001 From: Edgar Gonzalez Date: Sat, 8 Oct 2022 11:59:08 +0200 Subject: [PATCH 08/10] Add CSharp Extension Attribute Not Required as a Preview Language Feature --- src/Compiler/Checking/CheckDeclarations.fs | 144 +++++++-------- src/Compiler/Checking/NameResolution.fs | 165 ++++++++++++------ src/Compiler/FSComp.txt | 1 + src/Compiler/Facilities/LanguageFeatures.fs | 4 + src/Compiler/Facilities/LanguageFeatures.fsi | 1 + src/Compiler/xlf/FSComp.txt.cs.xlf | 5 + src/Compiler/xlf/FSComp.txt.de.xlf | 5 + src/Compiler/xlf/FSComp.txt.es.xlf | 5 + src/Compiler/xlf/FSComp.txt.fr.xlf | 5 + src/Compiler/xlf/FSComp.txt.it.xlf | 5 + src/Compiler/xlf/FSComp.txt.ja.xlf | 5 + src/Compiler/xlf/FSComp.txt.ko.xlf | 5 + src/Compiler/xlf/FSComp.txt.pl.xlf | 5 + src/Compiler/xlf/FSComp.txt.pt-BR.xlf | 5 + src/Compiler/xlf/FSComp.txt.ru.xlf | 5 + src/Compiler/xlf/FSComp.txt.tr.xlf | 5 + src/Compiler/xlf/FSComp.txt.zh-Hans.xlf | 5 + src/Compiler/xlf/FSComp.txt.zh-Hant.xlf | 5 + .../Language/ExtensionMethodTests.fs | 83 ++++++++- 19 files changed, 336 insertions(+), 127 deletions(-) diff --git a/src/Compiler/Checking/CheckDeclarations.fs b/src/Compiler/Checking/CheckDeclarations.fs index 81ed9a8234f..6768214e203 100644 --- a/src/Compiler/Checking/CheckDeclarations.fs +++ b/src/Compiler/Checking/CheckDeclarations.fs @@ -956,16 +956,18 @@ module MutRecBindingChecking = let prelimRecValues = [ for x in defnAs do match x with Phase2AMember bind -> yield bind.RecBindingInfo.Val | _ -> () ] let tyconOpt = - tyconOpt - |> Option.map (fun tycon -> - tryAddExtensionAttributeIfNotAlreadyPresent - (fun tryFindExtensionAttribute -> - tycon.MembersOfFSharpTyconSorted - |> Seq.tryPick (fun m -> tryFindExtensionAttribute m.Attribs) - ) - tycon - ) - + if cenv.g.langVersion.SupportsFeature(LanguageFeature.CSharpExtensionAttributeNotRequired) then + tyconOpt + |> Option.map (fun tycon -> + tryAddExtensionAttributeIfNotAlreadyPresent + (fun tryFindExtensionAttribute -> + tycon.MembersOfFSharpTyconSorted + |> Seq.tryPick (fun m -> tryFindExtensionAttribute m.Attribs) + ) + tycon + ) + else + tyconOpt let defnAs = MutRecShape.Tycon(TyconBindingsPhase2A(tyconOpt, declKind, prelimRecValues, tcref, copyOfTyconTypars, thisTy, defnAs)) defnAs, (tpenv, recBindIdx, uncheckedBindsRev)) @@ -4207,46 +4209,49 @@ module TcDeclarations = // Check the members and decide on representations for types with implicit constructors. let withBindings, envFinal = TcMutRecDefns_Phase2 cenv envInitial m scopem mutRecNSInfo envMutRecPrelimWithReprs withEnvs - // If any of the types has a member with the System.Runtime.CompilerServices.ExtensionAttribute, - // or a recursive module has a binding with the System.Runtime.CompilerServices.ExtensionAttribute, - // that type/recursive module should also received the ExtensionAttribute if it is not yet present. - // Example: - // open System.Runtime.CompilerServices - // - // type Int32Extensions = - // [] - // static member PlusOne (a:int) : int = a + 1 - // - // or - // - // module rec Foo - // - // [] - // let PlusOne (a:int) = a + 1 let withBindings = - withBindings - |> List.map (function - | MutRecShape.Tycon (Some tycon, bindings) -> - let tycon = - tryAddExtensionAttributeIfNotAlreadyPresent - (fun tryFindExtensionAttribute -> - tycon.MembersOfFSharpTyconSorted - |> Seq.tryPick (fun m -> tryFindExtensionAttribute m.Attribs) - ) - tycon - MutRecShape.Tycon (Some tycon, bindings) - | MutRecShape.Module ((MutRecDefnsPhase2DataForModule(moduleOrNamespaceType, entity), env), shapes) -> - let entity = - tryAddExtensionAttributeIfNotAlreadyPresent - (fun tryFindExtensionAttribute -> - moduleOrNamespaceType.Value.AllValsAndMembers - |> Seq.filter(fun v -> v.IsModuleBinding) - |> Seq.tryPick (fun v -> tryFindExtensionAttribute v.Attribs) - ) - entity - - MutRecShape.Module ((MutRecDefnsPhase2DataForModule(moduleOrNamespaceType, entity), env), shapes) - | shape -> shape) + if cenv.g.langVersion.SupportsFeature(LanguageFeature.CSharpExtensionAttributeNotRequired) then + // If any of the types has a member with the System.Runtime.CompilerServices.ExtensionAttribute, + // or a recursive module has a binding with the System.Runtime.CompilerServices.ExtensionAttribute, + // that type/recursive module should also received the ExtensionAttribute if it is not yet present. + // Example: + // open System.Runtime.CompilerServices + // + // type Int32Extensions = + // [] + // static member PlusOne (a:int) : int = a + 1 + // + // or + // + // module rec Foo + // + // [] + // let PlusOne (a:int) = a + 1 + withBindings + |> List.map (function + | MutRecShape.Tycon (Some tycon, bindings) -> + let tycon = + tryAddExtensionAttributeIfNotAlreadyPresent + (fun tryFindExtensionAttribute -> + tycon.MembersOfFSharpTyconSorted + |> Seq.tryPick (fun m -> tryFindExtensionAttribute m.Attribs) + ) + tycon + MutRecShape.Tycon (Some tycon, bindings) + | MutRecShape.Module ((MutRecDefnsPhase2DataForModule(moduleOrNamespaceType, entity), env), shapes) -> + let entity = + tryAddExtensionAttributeIfNotAlreadyPresent + (fun tryFindExtensionAttribute -> + moduleOrNamespaceType.Value.AllValsAndMembers + |> Seq.filter(fun v -> v.IsModuleBinding) + |> Seq.tryPick (fun v -> tryFindExtensionAttribute v.Attribs) + ) + entity + + MutRecShape.Module ((MutRecDefnsPhase2DataForModule(moduleOrNamespaceType, entity), env), shapes) + | shape -> shape) + else + withBindings // Generate the hash/compare/equality bindings for all tycons. // @@ -4785,27 +4790,30 @@ let rec TcModuleOrNamespaceElementNonMutRec (cenv: cenv) parent typeNames scopem // Get the inferred type of the decls and record it in the modul. moduleEntity.entity_modul_type <- MaybeLazy.Strict moduleTyAcc.Value - // If any of the let bindings inside the module has the System.Runtime.CompilerServices.ExtensionAttribute, - // that module should also received the ExtensionAttribute if it is not yet present. - // Example: - // module Foo - // - //[] - //let PlusOne (a:int) = a + 1 let moduleEntity = - tryAddExtensionAttributeIfNotAlreadyPresent - (fun tryFindExtensionAttribute -> - match moduleContents with - | ModuleOrNamespaceContents.TMDefs(defs) -> - defs - |> Seq.tryPick (function - | ModuleOrNamespaceContents.TMDefLet (Binding.TBind(var = v),_) -> - tryFindExtensionAttribute v.Attribs - | _ -> None) - | _ -> None - ) + if cenv.g.langVersion.SupportsFeature(LanguageFeature.CSharpExtensionAttributeNotRequired) then + // If any of the let bindings inside the module has the System.Runtime.CompilerServices.ExtensionAttribute, + // that module should also received the ExtensionAttribute if it is not yet present. + // Example: + // module Foo + // + //[] + //let PlusOne (a:int) = a + 1 + tryAddExtensionAttributeIfNotAlreadyPresent + (fun tryFindExtensionAttribute -> + match moduleContents with + | ModuleOrNamespaceContents.TMDefs(defs) -> + defs + |> Seq.tryPick (function + | ModuleOrNamespaceContents.TMDefLet (Binding.TBind(var = v),_) -> + tryFindExtensionAttribute v.Attribs + | _ -> None) + | _ -> None + ) + moduleEntity + else moduleEntity - + let moduleDef = TMDefRec(false, [], [], [ModuleOrNamespaceBinding.Module(moduleEntity, moduleContents)], m) PublishModuleDefn cenv env moduleEntity diff --git a/src/Compiler/Checking/NameResolution.fs b/src/Compiler/Checking/NameResolution.fs index 721732497e5..644a9df6811 100644 --- a/src/Compiler/Checking/NameResolution.fs +++ b/src/Compiler/Checking/NameResolution.fs @@ -517,65 +517,118 @@ let IsMethInfoPlainCSharpStyleExtensionMember g m isEnclExtTy (minfo: MethInfo) /// Get the info for all the .NET-style extension members listed as static members in the type. let private GetCSharpStyleIndexedExtensionMembersForTyconRef (amap: Import.ImportMap) m (tcrefOfStaticClass: TyconRef) = let g = amap.g - let ty = generalizedTyconRef g tcrefOfStaticClass + + if g.langVersion.SupportsFeature(LanguageFeature.CSharpExtensionAttributeNotRequired) then + let ty = generalizedTyconRef g tcrefOfStaticClass + + let minfos = + GetImmediateIntrinsicMethInfosOfType (None, AccessorDomain.AccessibleFromSomeFSharpCode) g amap m ty + |> List.filter (IsMethInfoPlainCSharpStyleExtensionMember g m true) - let minfos = - GetImmediateIntrinsicMethInfosOfType (None, AccessorDomain.AccessibleFromSomeFSharpCode) g amap m ty - |> List.filter (IsMethInfoPlainCSharpStyleExtensionMember g m true) - - if IsTyconRefUsedForCSharpStyleExtensionMembers g m tcrefOfStaticClass || not minfos.IsEmpty then - let pri = NextExtensionMethodPriority() - - [ for minfo in minfos do - let ilExtMem = ILExtMem (tcrefOfStaticClass, minfo, pri) - - // The results are indexed by the TyconRef of the first 'this' argument, if any. - // So we need to go and crack the type of the 'this' argument. - // - // This is convoluted because we only need the ILTypeRef of the first argument, and we don't - // want to read any other metadata as it can trigger missing-assembly errors. It turns out ImportILTypeRef - // is less eager in reading metadata than GetParamTypes. - // - // We don't use the index for the IL extension method for tuple of F# function types (e.g. if extension - // methods for tuple occur in C# code) - let thisTyconRef = - try - let rs = - match metadataOfTycon tcrefOfStaticClass.Deref, minfo with - | ILTypeMetadata (TILObjectReprData(scoref, _, _)), ILMeth(_, ILMethInfo(_, _, _, ilMethod, _), _) -> - match ilMethod.ParameterTypes with - | firstTy :: _ -> - match firstTy with - | ILType.Boxed tspec | ILType.Value tspec -> - let tref = (tspec |> rescopeILTypeSpec scoref).TypeRef - if Import.CanImportILTypeRef amap m tref then - let tcref = tref |> Import.ImportILTypeRef amap m - if isCompiledTupleTyconRef g tcref || tyconRefEq g tcref g.fastFunc_tcr then None - else Some tcref - else None + if IsTyconRefUsedForCSharpStyleExtensionMembers g m tcrefOfStaticClass || not minfos.IsEmpty then + let pri = NextExtensionMethodPriority() + + [ for minfo in minfos do + let ilExtMem = ILExtMem (tcrefOfStaticClass, minfo, pri) + + // The results are indexed by the TyconRef of the first 'this' argument, if any. + // So we need to go and crack the type of the 'this' argument. + // + // This is convoluted because we only need the ILTypeRef of the first argument, and we don't + // want to read any other metadata as it can trigger missing-assembly errors. It turns out ImportILTypeRef + // is less eager in reading metadata than GetParamTypes. + // + // We don't use the index for the IL extension method for tuple of F# function types (e.g. if extension + // methods for tuple occur in C# code) + let thisTyconRef = + try + let rs = + match metadataOfTycon tcrefOfStaticClass.Deref, minfo with + | ILTypeMetadata (TILObjectReprData(scoref, _, _)), ILMeth(_, ILMethInfo(_, _, _, ilMethod, _), _) -> + match ilMethod.ParameterTypes with + | firstTy :: _ -> + match firstTy with + | ILType.Boxed tspec | ILType.Value tspec -> + let tref = (tspec |> rescopeILTypeSpec scoref).TypeRef + if Import.CanImportILTypeRef amap m tref then + let tcref = tref |> Import.ImportILTypeRef amap m + if isCompiledTupleTyconRef g tcref || tyconRefEq g tcref g.fastFunc_tcr then None + else Some tcref + else None + | _ -> None + | _ -> None + | _ -> + // The results are indexed by the TyconRef of the first 'this' argument, if any. + // So we need to go and crack the type of the 'this' argument. + let thisTy = minfo.GetParamTypes(amap, m, generalizeTypars minfo.FormalMethodTypars).Head.Head + match thisTy with + | AppTy g (tcrefOfTypeExtended, _) when not (isByrefTy g thisTy) -> Some tcrefOfTypeExtended | _ -> None - | _ -> None - | _ -> - // The results are indexed by the TyconRef of the first 'this' argument, if any. - // So we need to go and crack the type of the 'this' argument. - let thisTy = minfo.GetParamTypes(amap, m, generalizeTypars minfo.FormalMethodTypars).Head.Head - match thisTy with - | AppTy g (tcrefOfTypeExtended, _) when not (isByrefTy g thisTy) -> Some tcrefOfTypeExtended - | _ -> None - - Some rs - - with e -> // Import of the ILType may fail, if so report the error and skip on - errorRecovery e m - None - - match thisTyconRef with - | None -> () - | Some (Some tcref) -> yield Choice1Of2(tcref, ilExtMem) - | Some None -> yield Choice2Of2 ilExtMem ] - else - [] + Some rs + + with e -> // Import of the ILType may fail, if so report the error and skip on + errorRecovery e m + None + + match thisTyconRef with + | None -> () + | Some (Some tcref) -> yield Choice1Of2(tcref, ilExtMem) + | Some None -> yield Choice2Of2 ilExtMem ] + else + [] + else + if IsTyconRefUsedForCSharpStyleExtensionMembers g m tcrefOfStaticClass then + let pri = NextExtensionMethodPriority() + let ty = generalizedTyconRef g tcrefOfStaticClass + let minfos = GetImmediateIntrinsicMethInfosOfType (None, AccessorDomain.AccessibleFromSomeFSharpCode) g amap m ty + + [ for minfo in minfos do + if IsMethInfoPlainCSharpStyleExtensionMember g m true minfo then + let ilExtMem = ILExtMem (tcrefOfStaticClass, minfo, pri) + // The results are indexed by the TyconRef of the first 'this' argument, if any. + // So we need to go and crack the type of the 'this' argument. + // + // This is convoluted because we only need the ILTypeRef of the first argument, and we don't + // want to read any other metadata as it can trigger missing-assembly errors. It turns out ImportILTypeRef + // is less eager in reading metadata than GetParamTypes. + // + // We don't use the index for the IL extension method for tuple of F# function types (e.g. if extension + // methods for tuple occur in C# code) + let thisTyconRef = + try + let rs = + match metadataOfTycon tcrefOfStaticClass.Deref, minfo with + | ILTypeMetadata (TILObjectReprData(scoref, _, _)), ILMeth(_, ILMethInfo(_, _, _, ilMethod, _), _) -> + match ilMethod.ParameterTypes with + | firstTy :: _ -> + match firstTy with + | ILType.Boxed tspec | ILType.Value tspec -> + let tref = (tspec |> rescopeILTypeSpec scoref).TypeRef + if Import.CanImportILTypeRef amap m tref then + let tcref = tref |> Import.ImportILTypeRef amap m + if isCompiledTupleTyconRef g tcref || tyconRefEq g tcref g.fastFunc_tcr then None + else Some tcref + else None + | _ -> None + | _ -> None + | _ -> + // The results are indexed by the TyconRef of the first 'this' argument, if any. + // So we need to go and crack the type of the 'this' argument. + let thisTy = minfo.GetParamTypes(amap, m, generalizeTypars minfo.FormalMethodTypars).Head.Head + match thisTy with + | AppTy g (tcrefOfTypeExtended, _) when not (isByrefTy g thisTy) -> Some tcrefOfTypeExtended + | _ -> None + Some rs + with e -> // Import of the ILType may fail, if so report the error and skip on + errorRecovery e m + None + match thisTyconRef with + | None -> () + | Some (Some tcref) -> yield Choice1Of2(tcref, ilExtMem) + | Some None -> yield Choice2Of2 ilExtMem ] + else + [] /// Query the declared properties of a type (including inherited properties) let IntrinsicPropInfosOfTypeInScope (infoReader: InfoReader) optFilter ad findFlag m ty = diff --git a/src/Compiler/FSComp.txt b/src/Compiler/FSComp.txt index 80c356d31db..ba40bd54f40 100644 --- a/src/Compiler/FSComp.txt +++ b/src/Compiler/FSComp.txt @@ -1555,6 +1555,7 @@ featureRequiredProperties,"support for required properties" featureInitProperties,"support for consuming init properties" featureLowercaseDUWhenRequireQualifiedAccess,"Allow lowercase DU when RequireQualifiedAccess attribute" featureMatchNotAllowedForUnionCaseWithNoData,"Pattern match discard is not allowed for union case that takes no data." +featureCSharpExtensionAttributeNotRequired,"Allow implicit Extension attribute on declaring types, modules" 3353,fsiInvalidDirective,"Invalid directive '#%s %s'" 3354,tcNotAFunctionButIndexerNamedIndexingNotYetEnabled,"This value supports indexing, e.g. '%s.[index]'. The syntax '%s[index]' requires /langversion:preview. See https://aka.ms/fsharp-index-notation." 3354,tcNotAFunctionButIndexerIndexingNotYetEnabled,"This expression supports indexing, e.g. 'expr.[index]'. The syntax 'expr[index]' requires /langversion:preview. See https://aka.ms/fsharp-index-notation." diff --git a/src/Compiler/Facilities/LanguageFeatures.fs b/src/Compiler/Facilities/LanguageFeatures.fs index 8644fbe9971..00ddd74b6c5 100644 --- a/src/Compiler/Facilities/LanguageFeatures.fs +++ b/src/Compiler/Facilities/LanguageFeatures.fs @@ -55,6 +55,7 @@ type LanguageFeature = | InterfacesWithAbstractStaticMembers | SelfTypeConstraints | MatchNotAllowedForUnionCaseWithNoData + | CSharpExtensionAttributeNotRequired /// LanguageVersion management type LanguageVersion(versionText) = @@ -126,6 +127,8 @@ type LanguageVersion(versionText) = // F# preview LanguageFeature.FromEndSlicing, previewVersion LanguageFeature.MatchNotAllowedForUnionCaseWithNoData, previewVersion + LanguageFeature.CSharpExtensionAttributeNotRequired, previewVersion + ] static let defaultLanguageVersion = LanguageVersion("default") @@ -233,6 +236,7 @@ type LanguageVersion(versionText) = | LanguageFeature.InterfacesWithAbstractStaticMembers -> FSComp.SR.featureInterfacesWithAbstractStaticMembers () | LanguageFeature.SelfTypeConstraints -> FSComp.SR.featureSelfTypeConstraints () | LanguageFeature.MatchNotAllowedForUnionCaseWithNoData -> FSComp.SR.featureMatchNotAllowedForUnionCaseWithNoData () + | LanguageFeature.CSharpExtensionAttributeNotRequired -> FSComp.SR.featureCSharpExtensionAttributeNotRequired () /// 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 694a6ae73fd..1b3d68e8f2a 100644 --- a/src/Compiler/Facilities/LanguageFeatures.fsi +++ b/src/Compiler/Facilities/LanguageFeatures.fsi @@ -45,6 +45,7 @@ type LanguageFeature = | InterfacesWithAbstractStaticMembers | SelfTypeConstraints | MatchNotAllowedForUnionCaseWithNoData + | CSharpExtensionAttributeNotRequired /// LanguageVersion management type LanguageVersion = diff --git a/src/Compiler/xlf/FSComp.txt.cs.xlf b/src/Compiler/xlf/FSComp.txt.cs.xlf index cd3a34b921b..7362c800a86 100644 --- a/src/Compiler/xlf/FSComp.txt.cs.xlf +++ b/src/Compiler/xlf/FSComp.txt.cs.xlf @@ -137,6 +137,11 @@ automatické generování vlastnosti Message pro deklarace exception + + Allow implicit Extension attribute on declaring types, modules + Allow implicit Extension attribute on declaring types, modules + + default interface member consumption využití člena výchozího rozhraní diff --git a/src/Compiler/xlf/FSComp.txt.de.xlf b/src/Compiler/xlf/FSComp.txt.de.xlf index 01a5cade733..5a0db029a25 100644 --- a/src/Compiler/xlf/FSComp.txt.de.xlf +++ b/src/Compiler/xlf/FSComp.txt.de.xlf @@ -137,6 +137,11 @@ Automatische Generierung der Eigenschaft „Message“ für „exception“-Deklarationen + + Allow implicit Extension attribute on declaring types, modules + Allow implicit Extension attribute on declaring types, modules + + default interface member consumption standardmäßige Schnittstellenmembernutzung diff --git a/src/Compiler/xlf/FSComp.txt.es.xlf b/src/Compiler/xlf/FSComp.txt.es.xlf index 81faedb0e94..bf70af15b4e 100644 --- a/src/Compiler/xlf/FSComp.txt.es.xlf +++ b/src/Compiler/xlf/FSComp.txt.es.xlf @@ -137,6 +137,11 @@ generación automática de la propiedad 'Message' para declaraciones 'exception' + + Allow implicit Extension attribute on declaring types, modules + Allow implicit Extension attribute on declaring types, modules + + default interface member consumption consumo de miembros de interfaz predeterminados diff --git a/src/Compiler/xlf/FSComp.txt.fr.xlf b/src/Compiler/xlf/FSComp.txt.fr.xlf index 366890f7566..effcd57d1b7 100644 --- a/src/Compiler/xlf/FSComp.txt.fr.xlf +++ b/src/Compiler/xlf/FSComp.txt.fr.xlf @@ -137,6 +137,11 @@ génération automatique de la propriété « Message » pour les déclarations « exception » + + Allow implicit Extension attribute on declaring types, modules + Allow implicit Extension attribute on declaring types, modules + + default interface member consumption consommation par défaut des membres d'interface diff --git a/src/Compiler/xlf/FSComp.txt.it.xlf b/src/Compiler/xlf/FSComp.txt.it.xlf index b9922d16945..402f7ed2397 100644 --- a/src/Compiler/xlf/FSComp.txt.it.xlf +++ b/src/Compiler/xlf/FSComp.txt.it.xlf @@ -137,6 +137,11 @@ generazione automatica della proprietà 'Messaggio' per le dichiarazioni 'eccezione' + + Allow implicit Extension attribute on declaring types, modules + Allow implicit Extension attribute on declaring types, modules + + default interface member consumption utilizzo predefinito dei membri di interfaccia diff --git a/src/Compiler/xlf/FSComp.txt.ja.xlf b/src/Compiler/xlf/FSComp.txt.ja.xlf index b711c9d9f3c..81e3bcce1a4 100644 --- a/src/Compiler/xlf/FSComp.txt.ja.xlf +++ b/src/Compiler/xlf/FSComp.txt.ja.xlf @@ -137,6 +137,11 @@ `exception` 宣言の `Message` プロパティの自動生成 + + Allow implicit Extension attribute on declaring types, modules + Allow implicit Extension attribute on declaring types, modules + + default interface member consumption 既定のインターフェイス メンバーの消費 diff --git a/src/Compiler/xlf/FSComp.txt.ko.xlf b/src/Compiler/xlf/FSComp.txt.ko.xlf index 40d1b9c30bc..af64acb23db 100644 --- a/src/Compiler/xlf/FSComp.txt.ko.xlf +++ b/src/Compiler/xlf/FSComp.txt.ko.xlf @@ -137,6 +137,11 @@ 'exception' 선언에 대한 'Message' 속성 자동 생성 + + Allow implicit Extension attribute on declaring types, modules + Allow implicit Extension attribute on declaring types, modules + + default interface member consumption 기본 인터페이스 멤버 사용 diff --git a/src/Compiler/xlf/FSComp.txt.pl.xlf b/src/Compiler/xlf/FSComp.txt.pl.xlf index 23d950d1899..375fb924170 100644 --- a/src/Compiler/xlf/FSComp.txt.pl.xlf +++ b/src/Compiler/xlf/FSComp.txt.pl.xlf @@ -137,6 +137,11 @@ Automatyczne generowanie właściwości „Wiadomość“ dla deklaracji „Wyjątek“ + + Allow implicit Extension attribute on declaring types, modules + Allow implicit Extension attribute on declaring types, modules + + default interface member consumption domyślne użycie składowej interfejsu diff --git a/src/Compiler/xlf/FSComp.txt.pt-BR.xlf b/src/Compiler/xlf/FSComp.txt.pt-BR.xlf index 2a6eb368bea..55f2fef3e94 100644 --- a/src/Compiler/xlf/FSComp.txt.pt-BR.xlf +++ b/src/Compiler/xlf/FSComp.txt.pt-BR.xlf @@ -137,6 +137,11 @@ geração automática da propriedade 'Message' para declarações de 'exception' + + Allow implicit Extension attribute on declaring types, modules + Allow implicit Extension attribute on declaring types, modules + + default interface member consumption consumo de membro da interface padrão diff --git a/src/Compiler/xlf/FSComp.txt.ru.xlf b/src/Compiler/xlf/FSComp.txt.ru.xlf index 3b4cafe834c..6768e10c6be 100644 --- a/src/Compiler/xlf/FSComp.txt.ru.xlf +++ b/src/Compiler/xlf/FSComp.txt.ru.xlf @@ -137,6 +137,11 @@ автоматическое создание свойства “Message” для объявлений “exception” + + Allow implicit Extension attribute on declaring types, modules + Allow implicit Extension attribute on declaring types, modules + + default interface member consumption использование элемента интерфейса по умолчанию diff --git a/src/Compiler/xlf/FSComp.txt.tr.xlf b/src/Compiler/xlf/FSComp.txt.tr.xlf index e0e4df0357d..fd00094a481 100644 --- a/src/Compiler/xlf/FSComp.txt.tr.xlf +++ b/src/Compiler/xlf/FSComp.txt.tr.xlf @@ -137,6 +137,11 @@ 'exception' bildirimleri için 'Message' özelliğinin otomatik olarak oluşturulması + + Allow implicit Extension attribute on declaring types, modules + Allow implicit Extension attribute on declaring types, modules + + default interface member consumption varsayılan arabirim üyesi tüketimi diff --git a/src/Compiler/xlf/FSComp.txt.zh-Hans.xlf b/src/Compiler/xlf/FSComp.txt.zh-Hans.xlf index 10dc4f098c0..2c7706ea863 100644 --- a/src/Compiler/xlf/FSComp.txt.zh-Hans.xlf +++ b/src/Compiler/xlf/FSComp.txt.zh-Hans.xlf @@ -137,6 +137,11 @@ 自动生成“异常”声明的“消息”属性 + + Allow implicit Extension attribute on declaring types, modules + Allow implicit Extension attribute on declaring types, modules + + default interface member consumption 默认接口成员消耗 diff --git a/src/Compiler/xlf/FSComp.txt.zh-Hant.xlf b/src/Compiler/xlf/FSComp.txt.zh-Hant.xlf index 59f18485051..f36703ee082 100644 --- a/src/Compiler/xlf/FSComp.txt.zh-Hant.xlf +++ b/src/Compiler/xlf/FSComp.txt.zh-Hant.xlf @@ -137,6 +137,11 @@ 自動產生 'exception' 宣告的 'Message' 屬性 + + Allow implicit Extension attribute on declaring types, modules + Allow implicit Extension attribute on declaring types, modules + + default interface member consumption 預設介面成員使用 diff --git a/tests/FSharp.Compiler.ComponentTests/Language/ExtensionMethodTests.fs b/tests/FSharp.Compiler.ComponentTests/Language/ExtensionMethodTests.fs index dc883736b5a..1ad91f999c3 100644 --- a/tests/FSharp.Compiler.ComponentTests/Language/ExtensionMethodTests.fs +++ b/tests/FSharp.Compiler.ComponentTests/Language/ExtensionMethodTests.fs @@ -19,6 +19,7 @@ type Foo = let f (b:int) = b.PlusOne() """ + |> withLangVersionPreview |> compile |> shouldSucceed @@ -34,8 +35,28 @@ type Foo = let f (b:int) = b.PlusOne() """ + |> withLangVersionPreview |> compile |> shouldSucceed + + [] + let ``Extension method without toplevel attribute on type lang version 7`` () = + Fsx + """ +open System.Runtime.CompilerServices + +type Foo = + [] + static member PlusOne (a:int) : int = a + 1 + +let f (b:int) = b.PlusOne() + """ + |> withLangVersion70 + |> compile + |> shouldFail + |> withDiagnostics [ + (Error 39, Line 8, Col 19, Line 8, Col 26, "The type 'Int32' does not define the field, constructor or member 'PlusOne'.") + ] [] let ``Extension method without toplevel attribute on recursive type`` () = @@ -52,6 +73,7 @@ and Bar = let f (b:int) = b.PlusOne() """ + |> withLangVersionPreview |> compile |> shouldSucceed @@ -68,6 +90,7 @@ type Foo = [] static member PlusOne (a:int) : int = a + 1 """ + |> withLangVersionPreview |> withName "FSLib" let csharp = @@ -90,6 +113,48 @@ namespace Consumer |> withReferences [ fsharp ] csharp |> compile |> shouldSucceed + + [] + let ``F# lang version 7 CSharpStyleExtensionMethod consumed in C#`` () = + let fsharp = + FSharp + """ +module Hello + +open System.Runtime.CompilerServices + +type Foo = + [] + static member PlusOne (a:int) : int = a + 1 +""" + |> withLangVersion70 + |> withName "FSLib" + + let csharp = + CSharp + """ +namespace Consumer +{ + using static Hello.Foo; + + public class Class1 + { + public Class1() + { + var meh = 1.PlusOne(); + } + } +} +""" + |> withName "CSLib" + |> withReferences [ fsharp ] + + csharp + |> compile + |> shouldFail + |> withDiagnostics [ + (Error 1061, Line 9, Col 25, Line 9, Col 32, "'int' does not contain a definition for 'PlusOne' and no accessible extension method 'PlusOne' accepting a first argument of type 'int' could be found (are you missing a using directive or an assembly reference?)") + ] [] let ``F# CSharpStyleExtensionMethod in recursive type consumed in C#`` () = @@ -107,6 +172,7 @@ and Bar = [] static member PlusOne (a:int) : int = a + 1 """ + |> withLangVersionPreview |> withName "FSLib" let csharp = @@ -144,6 +210,7 @@ module Foo = [] let PlusOne (a:int) : int = a + 1 """ + |> withLangVersionPreview |> withName "FSLib" let csharp = @@ -180,6 +247,7 @@ module Foo = [] let PlusOne (a:int) : int = a + 1 """ + |> withLangVersionPreview |> withName "FSLib" let csharp = @@ -213,6 +281,7 @@ namespace Consumer [] let PlusOne (a:int) = a + 1 """ + |> withLangVersionPreview |> withName "FSLib" let csharp = @@ -245,6 +314,7 @@ namespace Consumer [] let PlusOne (a:int) = a + 1 """ + |> withLangVersionPreview |> withName "FSLib" let csharp = @@ -278,6 +348,7 @@ namespace Consumer [] let PlusOne (a:int) = a + 1 """ + |> withLangVersionPreview |> withName "FSLib" let csharp = @@ -310,6 +381,7 @@ namespace Consumer [] let PlusOne (a:int) = a + 1 """ + |> withLangVersionPreview |> withName "FSLib" let csharp = @@ -344,6 +416,7 @@ type Bar = [] static member PlusOne (a:int) = a + 1 """ + |> withLangVersionPreview |> withName "FSLib" let csharp = @@ -376,7 +449,8 @@ type Bar = [] static member PlusOne (a:int) = a + 1 """ - |> withName "FSLib" + |> withLangVersionPreview + |> withName "FSLib" let csharp = CSharp """ @@ -425,7 +499,8 @@ type Bar = [] static member PlusOne: a: int -> int """ - |> withAdditionalSourceFile implementation + |> withLangVersionPreview + |> withAdditionalSourceFile implementation |> withName "FSLib" let csharp = @@ -475,7 +550,8 @@ type Bar = [] static member PlusOne: a: int -> int """ - |> withAdditionalSourceFile implementation + |> withLangVersionPreview + |> withAdditionalSourceFile implementation |> withName "FSLib" let csharp = @@ -510,6 +586,7 @@ type Bar = [] let MinusOne (a:int) = a - 1 """ + |> withLangVersionPreview |> withName "FSLib" let csharp = From c058bb60e8726bab3297389fb44f4565feba26db Mon Sep 17 00:00:00 2001 From: Tomas Grosup Date: Thu, 27 Oct 2022 16:59:35 +0200 Subject: [PATCH 09/10] Passing propper Fsharp.Core version to executed tests in legacy FsharpSuite --- tests/fsharp/single-test.fs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/fsharp/single-test.fs b/tests/fsharp/single-test.fs index 3e1ab4390bc..0438f04451a 100644 --- a/tests/fsharp/single-test.fs +++ b/tests/fsharp/single-test.fs @@ -392,7 +392,7 @@ let singleVersionedNegTest (cfg: TestConfig) version testname = let cfg = { cfg with - fsc_flags = sprintf "%s %s --preferreduilang:en-US --define:NEGATIVE" cfg.fsc_flags options + fsc_flags = sprintf """%s %s --preferreduilang:en-US --define:NEGATIVE --simpleresolution --noframework /r:"%s" """ cfg.fsc_flags options cfg.FSCOREDLLPATH fsi_flags = sprintf "%s --preferreduilang:en-US %s" cfg.fsi_flags options } From e62423c90642423526079df1e79326e3633608d6 Mon Sep 17 00:00:00 2001 From: Tomas Grosup Date: Thu, 3 Nov 2022 14:03:20 +0100 Subject: [PATCH 10/10] Fix for tests that use NetFramework types (like winforms brushes etc.) --- tests/fsharp/single-test.fs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/fsharp/single-test.fs b/tests/fsharp/single-test.fs index 0438f04451a..6d7b401e003 100644 --- a/tests/fsharp/single-test.fs +++ b/tests/fsharp/single-test.fs @@ -392,7 +392,7 @@ let singleVersionedNegTest (cfg: TestConfig) version testname = let cfg = { cfg with - fsc_flags = sprintf """%s %s --preferreduilang:en-US --define:NEGATIVE --simpleresolution --noframework /r:"%s" """ cfg.fsc_flags options cfg.FSCOREDLLPATH + fsc_flags = sprintf """%s %s --preferreduilang:en-US --define:NEGATIVE --simpleresolution /r:"%s" """ cfg.fsc_flags options cfg.FSCOREDLLPATH fsi_flags = sprintf "%s --preferreduilang:en-US %s" cfg.fsi_flags options }