Skip to content
Merged
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
83 changes: 83 additions & 0 deletions src/Compiler/Checking/CheckDeclarations.fs
Original file line number Diff line number Diff line change
Expand Up @@ -969,6 +969,20 @@ module MutRecBindingChecking =
| rest -> rest

let prelimRecValues = [ for x in defnAs do match x with Phase2AMember bind -> yield bind.RecBindingInfo.Val | _ -> () ]

let tyconOpt =
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))

Expand Down Expand Up @@ -4228,6 +4242,50 @@ 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 isMutRec

let withBindings =
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 =
// [<Extension>]
// static member PlusOne (a:int) : int = a + 1
//
// or
//
// module rec Foo
//
// [<System.Runtime.CompilerServices.Extension>]
// 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.
//
// Note: generating these bindings must come after generating the members, since some in the case of structs some fields
Expand Down Expand Up @@ -4766,6 +4824,31 @@ 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

let moduleEntity =
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
//
//[<System.Runtime.CompilerServices.Extension>]
//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
Expand Down
71 changes: 63 additions & 8 deletions src/Compiler/Checking/NameResolution.fs
Original file line number Diff line number Diff line change
Expand Up @@ -530,14 +530,18 @@ 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

if IsTyconRefUsedForCSharpStyleExtensionMembers g m tcrefOfStaticClass then
let pri = NextExtensionMethodPriority()

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)
Comment on lines 535 to +539
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm concerned about the performance implications of this preview feature. If my late-night analysis is correct it seems to me we're now looking into the methods of every type in every opened namespace. This must be traversing a lot more metadata than previously. This will be very significant both from a perf question - think what happens if there are types in referenced assemblies that were previously unused that contain 100,000 methods. It also has some correctness implications see #15158.

Given the feature being implemented this is a little tricky to avoid. We really have to be looking for the ExtensionAttribute that mark the types before we traverse them, and avoiding the traversal for types that do not contain extension methods. That's really what the attributes are for.

Now, these attributes will be correctly present in compiled reference assemblies because they are either explicit or added in CheckDeclarations, so for referenced assemblies we can just look at them. However these attributes are not necessarily present yet in the assembly being compiled - e.g. in the case of namespace rec where one part of the recursive set of types opens another before the attributes have been fully stitched in.

My (late night dont-take-as-gospel) recommendation is that this code be adjusted to look for ExtensionAttribute, except in the assembly being compiled. So something like:

    if g.langVersion.SupportsFeature(LanguageFeature.CSharpExtensionAttributeNotRequired) then
        let ty = generalizedTyconRef g tcrefOfStaticClass
        let csharpStyleExtensionMembers = 
            if IsTyconRefUsedForCSharpStyleExtensionMembers g m tcrefOfStaticClass || tcrefOfStaticClass.IsLocalRef then
                GetImmediateIntrinsicMethInfosOfType (None, AccessorDomain.AccessibleFromSomeFSharpCode) g amap m ty
                |> List.filter (IsMethInfoPlainCSharpStyleExtensionMember g m true)
            else
                []
                
        if not minfos.IsEmpty then
                let pri = NextExtensionMethodPriority()


let minfos = GetImmediateIntrinsicMethInfosOfType (None, AccessorDomain.AccessibleFromSomeFSharpCode) g amap m ty
[ for minfo in minfos do
if IsMethInfoPlainCSharpStyleExtensionMember g m true minfo then
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.
Expand Down Expand Up @@ -584,9 +588,60 @@ let private GetCSharpStyleIndexedExtensionMembersForTyconRef (amap: Import.Impor
| 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
Comment on lines +611 to +634
Copy link
Contributor

Choose a reason for hiding this comment

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

This seems to duplicate the code up above - could we have factored this out to prevent the duplication?

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 =
Expand Down
1 change: 1 addition & 0 deletions src/Compiler/FSComp.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1560,6 +1560,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."
Expand Down
4 changes: 4 additions & 0 deletions src/Compiler/Facilities/LanguageFeatures.fs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ type LanguageFeature =
| InterfacesWithAbstractStaticMembers
| SelfTypeConstraints
| MatchNotAllowedForUnionCaseWithNoData
| CSharpExtensionAttributeNotRequired

/// LanguageVersion management
type LanguageVersion(versionText) =
Expand Down Expand Up @@ -126,6 +127,8 @@ type LanguageVersion(versionText) =
// F# preview
LanguageFeature.FromEndSlicing, previewVersion
LanguageFeature.MatchNotAllowedForUnionCaseWithNoData, previewVersion
LanguageFeature.CSharpExtensionAttributeNotRequired, previewVersion

]

static let defaultLanguageVersion = LanguageVersion("default")
Expand Down Expand Up @@ -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 =
Expand Down
1 change: 1 addition & 0 deletions src/Compiler/Facilities/LanguageFeatures.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ type LanguageFeature =
| InterfacesWithAbstractStaticMembers
| SelfTypeConstraints
| MatchNotAllowedForUnionCaseWithNoData
| CSharpExtensionAttributeNotRequired

/// LanguageVersion management
type LanguageVersion =
Expand Down
20 changes: 19 additions & 1 deletion src/Compiler/TypedTree/TypedTreeOps.fs
Original file line number Diff line number Diff line change
Expand Up @@ -10410,4 +10410,22 @@ let (|EmptyModuleOrNamespaces|_|) (moduleOrNamespaceContents: ModuleOrNamespaceC
Some emptyModuleOrNamespaces
else
None
| _ -> None
| _ -> 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 }
4 changes: 4 additions & 0 deletions src/Compiler/TypedTree/TypedTreeOps.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -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
5 changes: 5 additions & 0 deletions src/Compiler/xlf/FSComp.txt.cs.xlf
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,11 @@
<target state="translated">automatické generování vlastnosti Message pro deklarace exception</target>
<note />
</trans-unit>
<trans-unit id="featureCSharpExtensionAttributeNotRequired">
<source>Allow implicit Extension attribute on declaring types, modules</source>
<target state="new">Allow implicit Extension attribute on declaring types, modules</target>
<note />
</trans-unit>
<trans-unit id="featureDefaultInterfaceMemberConsumption">
<source>default interface member consumption</source>
<target state="translated">využití člena výchozího rozhraní</target>
Expand Down
5 changes: 5 additions & 0 deletions src/Compiler/xlf/FSComp.txt.de.xlf
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,11 @@
<target state="translated">Automatische Generierung der Eigenschaft „Message“ für „exception“-Deklarationen</target>
<note />
</trans-unit>
<trans-unit id="featureCSharpExtensionAttributeNotRequired">
<source>Allow implicit Extension attribute on declaring types, modules</source>
<target state="new">Allow implicit Extension attribute on declaring types, modules</target>
<note />
</trans-unit>
<trans-unit id="featureDefaultInterfaceMemberConsumption">
<source>default interface member consumption</source>
<target state="translated">standardmäßige Schnittstellenmembernutzung</target>
Expand Down
5 changes: 5 additions & 0 deletions src/Compiler/xlf/FSComp.txt.es.xlf
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,11 @@
<target state="translated">generación automática de la propiedad 'Message' para declaraciones 'exception'</target>
<note />
</trans-unit>
<trans-unit id="featureCSharpExtensionAttributeNotRequired">
<source>Allow implicit Extension attribute on declaring types, modules</source>
<target state="new">Allow implicit Extension attribute on declaring types, modules</target>
<note />
</trans-unit>
<trans-unit id="featureDefaultInterfaceMemberConsumption">
<source>default interface member consumption</source>
<target state="translated">consumo de miembros de interfaz predeterminados</target>
Expand Down
5 changes: 5 additions & 0 deletions src/Compiler/xlf/FSComp.txt.fr.xlf
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,11 @@
<target state="translated">génération automatique de la propriété « Message » pour les déclarations « exception »</target>
<note />
</trans-unit>
<trans-unit id="featureCSharpExtensionAttributeNotRequired">
<source>Allow implicit Extension attribute on declaring types, modules</source>
<target state="new">Allow implicit Extension attribute on declaring types, modules</target>
<note />
</trans-unit>
<trans-unit id="featureDefaultInterfaceMemberConsumption">
<source>default interface member consumption</source>
<target state="translated">consommation par défaut des membres d'interface</target>
Expand Down
5 changes: 5 additions & 0 deletions src/Compiler/xlf/FSComp.txt.it.xlf
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,11 @@
<target state="translated">generazione automatica della proprietà 'Messaggio' per le dichiarazioni 'eccezione'</target>
<note />
</trans-unit>
<trans-unit id="featureCSharpExtensionAttributeNotRequired">
<source>Allow implicit Extension attribute on declaring types, modules</source>
<target state="new">Allow implicit Extension attribute on declaring types, modules</target>
<note />
</trans-unit>
<trans-unit id="featureDefaultInterfaceMemberConsumption">
<source>default interface member consumption</source>
<target state="translated">utilizzo predefinito dei membri di interfaccia</target>
Expand Down
5 changes: 5 additions & 0 deletions src/Compiler/xlf/FSComp.txt.ja.xlf
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,11 @@
<target state="translated">`exception` 宣言の `Message` プロパティの自動生成</target>
<note />
</trans-unit>
<trans-unit id="featureCSharpExtensionAttributeNotRequired">
<source>Allow implicit Extension attribute on declaring types, modules</source>
<target state="new">Allow implicit Extension attribute on declaring types, modules</target>
<note />
</trans-unit>
<trans-unit id="featureDefaultInterfaceMemberConsumption">
<source>default interface member consumption</source>
<target state="translated">既定のインターフェイス メンバーの消費</target>
Expand Down
5 changes: 5 additions & 0 deletions src/Compiler/xlf/FSComp.txt.ko.xlf
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,11 @@
<target state="translated">'exception' 선언에 대한 'Message' 속성 자동 생성</target>
<note />
</trans-unit>
<trans-unit id="featureCSharpExtensionAttributeNotRequired">
<source>Allow implicit Extension attribute on declaring types, modules</source>
<target state="new">Allow implicit Extension attribute on declaring types, modules</target>
<note />
</trans-unit>
<trans-unit id="featureDefaultInterfaceMemberConsumption">
<source>default interface member consumption</source>
<target state="translated">기본 인터페이스 멤버 사용</target>
Expand Down
Loading