2
\$\begingroup\$

I'm learning F# and for an exercise, I took a roslyn analyzer in C# and ported it to F#.

In the excerpt below the GetParameterInfo function:

  • takes in ArgumentSyntax which describes an argument passed to a C# method at an invocation site.
  • And finds the corresponding IParameterSymbol which describes the method's parameter at the point in code where the method is defined.

Here it is:

module UseNamedArgs.ParameterInfo

open System
open System.Collections.Immutable
open Microsoft.CodeAnalysis
open Microsoft.CodeAnalysis.CSharp.Syntax
open UseNamedArgs.CSharpAdapters
open UseNamedArgs.MaybeBuilder

type ParameterInfo = {
    MethodOrProperty : ISymbol;
    Parameter : IParameterSymbol }

type ISymbol with
    member symbol.GetParameters() =
        match symbol with
        | :? IMethodSymbol as s   -> s.Parameters
        | :? IPropertySymbol as s -> s.Parameters
        | _                       -> ImmutableArray<IParameterSymbol>.Empty
        |> Seq.toList

/// <summary>
/// To be able to convert positional arguments to named we need to find
/// corresponding <see cref="IParameterSymbol" /> for each argument.
/// </summary>
type SemanticModel with
    member sema.GetParameterInfo (argument: ArgumentSyntax) =
        maybe {
            let argList = argument.Parent :?> ArgumentListSyntax
            let exprSyntax = argList.Parent  :?> ExpressionSyntax
            let methodOrProperty = sema.GetSymbolInfo(exprSyntax).Symbol
            let! parameters = methodOrProperty.GetParameters() |> Option.ofList
            if isNull argument.NameColon then
                // A positional argument.
                match argList.Arguments.IndexOf(argument) with
                | index when index >= 0 && index < parameters.Length -> 
                    return { MethodOrProperty = methodOrProperty;
                             Parameter = parameters.[index] }
                | index when index >= parameters.Length 
                             && parameters.[parameters.Length - 1].IsParams ->
                    return { MethodOrProperty = methodOrProperty;
                             Parameter = parameters.[parameters.Length - 1] }
                | _ -> return! None
            else 
                // Potentially, this is a named argument.
                let! name = argument.NameColon.Name |> Option.ofObj
                let! nameText = name.Identifier.ValueText |> Option.ofObj
                // Yes, it's a named argument.
                let! parameter = parameters |> Seq.tryFind (fun param -> 
                    String.Equals(param.Name, 
                                  nameText, 
                                  StringComparison.Ordinal))

                return { MethodOrProperty = methodOrProperty;
                         Parameter = parameter }
}
  • The original C# code of this snippet was adapted from CSharpEssentials
  • The maybe computational expression is defined in MaybeBuilder — but it's likely pretty much what you'd expect a maybe to be.

How can I change the code so it'll be more idiomatic F#?
And is it even anywhere in the ballpark of idiomatic F#?

\$\endgroup\$
1
\$\begingroup\$

I feel we can traverse the Roslyn AST but it's chatty and boring for write with a lot of non aspect code fragment :(

My suggestion for reference is my experimental project "F# Active pattern library for Roslyn Compiler Platform" can ease for use Roslyn AST directly F# pattern matching syntax.

https://github.com/kekyo/Microsoft.CodeAnalysis.ActivePatterns

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.