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
87 changes: 55 additions & 32 deletions src/Compiler/Checking/infos.fs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
module internal FSharp.Compiler.Infos

open System
open System.Collections.Generic
open System.Runtime.CompilerServices
open Internal.Utilities.Library
open Internal.Utilities.Library.Extras
open FSharp.Compiler
Expand Down Expand Up @@ -658,6 +660,56 @@ type ILMethInfo =
|> GetFSharpViewOfReturnType amap.g


// Module-level, not a local closure, so cache hits in GetParamAttribs allocate nothing.
let private ComputeILMethodParamAttribs g (ilMethInfo: ILMethInfo) amap m =
[ [ for p in ilMethInfo.ParamMetadata do
let attrs = p.CustomAttrs
let isParamArrayArg = p.CustomAttrsStored.HasWellKnownAttribute(g, WellKnownILAttributes.ParamArrayAttribute)
let reflArgInfo =
match attrs with
| ILAttribDecoded WellKnownILAttributes.ReflectedDefinitionAttribute ([ILAttribElem.Bool b ], _) -> ReflectedArgInfo.Quote b
| ILAttribDecoded WellKnownILAttributes.ReflectedDefinitionAttribute _ -> ReflectedArgInfo.Quote false
| _ -> ReflectedArgInfo.None
let isOutArg = (p.IsOut && not p.IsIn)
let isInArg = (p.IsIn && not p.IsOut)
// Note: we get default argument values from VB and other .NET language metadata
let optArgInfo = OptionalArgInfo.FromILParameter g amap m ilMethInfo.MetadataScope ilMethInfo.DeclaringTypeInst p

let isCallerLineNumberArg = p.CustomAttrsStored.HasWellKnownAttribute(g, WellKnownILAttributes.CallerLineNumberAttribute)
let isCallerFilePathArg = p.CustomAttrsStored.HasWellKnownAttribute(g, WellKnownILAttributes.CallerFilePathAttribute)
let isCallerMemberNameArg = p.CustomAttrsStored.HasWellKnownAttribute(g, WellKnownILAttributes.CallerMemberNameAttribute)

let callerInfo =
match isCallerLineNumberArg, isCallerFilePathArg, isCallerMemberNameArg with
| false, false, false -> NoCallerInfo
| true, false, false -> CallerLineNumber
| false, true, false -> CallerFilePath
| false, false, true -> CallerMemberName
| _, _, _ ->
// if multiple caller info attributes are specified, pick the "wrong" one here
// so that we get an error later
if p.Type.TypeRef.FullName = "System.Int32" then CallerFilePath
else CallerLineNumber

ParamAttribs(isParamArrayArg, isInArg, isOutArg, optArgInfo, callerInfo, reflArgInfo) ] ]

// Extension view drops the object arg from ParamMetadata, so IsILExtensionMethod is part of the key.
let private getILMethodParamAttribsTable =
WeakMap.getOrCreate (fun (amap: ImportMap) ->
MemoizationTable(
"ilMethodParamAttribs",
(fun (struct (ilMethInfo: ILMethInfo, m)) -> ComputeILMethodParamAttribs amap.g ilMethInfo amap m),
keyComparer =
{ new IEqualityComparer<struct (ILMethInfo * range)> with

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.

Does dropping the range in the methods below create a risk of reporting a diagnostic with a wrong range later on?

member _.GetHashCode(struct (mi, _)) =
RuntimeHelpers.GetHashCode mi.RawMetadata ^^^ (if mi.IsILExtensionMethod then 1 else 0)

member _.Equals(struct (mi1, _), struct (mi2, _)) =
mi1.IsILExtensionMethod = mi2.IsILExtensionMethod
&& mi1.RawMetadata === mi2.RawMetadata },
canMemoize = fun (struct (mi, _)) -> mi.DeclaringTypeInst.IsEmpty))


/// Describes an F# use of a method
[<System.Diagnostics.DebuggerDisplay("{DebuggerDisplayName}")>]
[<NoComparison; NoEquality>]
Expand Down Expand Up @@ -1335,39 +1387,10 @@ type MethInfo =
#endif

/// Get the parameter attributes of a method info, which get combined with the parameter names and types
member x.GetParamAttribs(amap, m) =
member x.GetParamAttribs(amap: ImportMap, m) =
match x with
| ILMeth(g, ilMethInfo, _) ->
[ [ for p in ilMethInfo.ParamMetadata do
let attrs = p.CustomAttrs
let isParamArrayArg = p.CustomAttrsStored.HasWellKnownAttribute(g, WellKnownILAttributes.ParamArrayAttribute)
let reflArgInfo =
match attrs with
| ILAttribDecoded WellKnownILAttributes.ReflectedDefinitionAttribute ([ILAttribElem.Bool b ], _) -> ReflectedArgInfo.Quote b
| ILAttribDecoded WellKnownILAttributes.ReflectedDefinitionAttribute _ -> ReflectedArgInfo.Quote false
| _ -> ReflectedArgInfo.None
let isOutArg = (p.IsOut && not p.IsIn)
let isInArg = (p.IsIn && not p.IsOut)
// Note: we get default argument values from VB and other .NET language metadata
let optArgInfo = OptionalArgInfo.FromILParameter g amap m ilMethInfo.MetadataScope ilMethInfo.DeclaringTypeInst p

let isCallerLineNumberArg = p.CustomAttrsStored.HasWellKnownAttribute(g, WellKnownILAttributes.CallerLineNumberAttribute)
let isCallerFilePathArg = p.CustomAttrsStored.HasWellKnownAttribute(g, WellKnownILAttributes.CallerFilePathAttribute)
let isCallerMemberNameArg = p.CustomAttrsStored.HasWellKnownAttribute(g, WellKnownILAttributes.CallerMemberNameAttribute)

let callerInfo =
match isCallerLineNumberArg, isCallerFilePathArg, isCallerMemberNameArg with
| false, false, false -> NoCallerInfo
| true, false, false -> CallerLineNumber
| false, true, false -> CallerFilePath
| false, false, true -> CallerMemberName
| _, _, _ ->
// if multiple caller info attributes are specified, pick the "wrong" one here
// so that we get an error later
if p.Type.TypeRef.FullName = "System.Int32" then CallerFilePath
else CallerLineNumber

ParamAttribs(isParamArrayArg, isInArg, isOutArg, optArgInfo, callerInfo, reflArgInfo) ] ]
| ILMeth(_, ilMethInfo, _) ->
(getILMethodParamAttribsTable amap).Apply(struct (ilMethInfo, m))

| FSMeth(g, _, vref, _) ->
GetArgInfosOfMember x.IsCSharpStyleExtensionMember g vref
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -844,3 +844,16 @@ module CompiledExtensions =
"""
|> compile
|> shouldSucceed

[<Fact>]
let ``IL extension method used with both instance and static call syntax`` () =
Fsx
"""
open System.Linq
let xs : System.Collections.Generic.IEnumerable<int> = Seq.ofList [ 1; 2; 3 ]
let a = xs.Select(fun x -> x + 1) |> Seq.length
let b = System.Linq.Enumerable.Select(xs, (fun x -> x + 1)) |> Seq.length
if a <> b then failwith "unexpected"

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.

This is a runtime check, but the test only asserts that compilation should succeed.

"""
|> compile
|> shouldSucceed
Loading