Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is Roslyn the right tool for a compile-time Expression checking?

Tags:

c#

.net

roslyn

I have a toolkit that has many methods often taking Expression<Func<T,TProperty>> as parameters. Some can be single-level only (o=>o.Name), while some can be multi-level (o=>o.EmployeeData.Address.Street).

I want to develop something (MSBuild Task? Visual Studio Plugin? hopefully the first) that reads all the user's .cs files, and gives build errors if the given parameter is not a property-expression (but something like o=>o.Contains("foo")), or if a multi-level expression is given where only a single-level is allowed.

I tried looking at compiled IL code first but since the expression trees are a C# compiler "trick", in IL all I see is creating expression instances and such, and while I could check each if only MemberExpressions (and the correct number of them) are created, it is not so great.

Then Roslyn came to my mind. Is it possible to write something like this with Roslyn?

like image 525
TDaver Avatar asked Mar 19 '12 10:03

TDaver


People also ask

What is Roslyn used for?

Roslyn is a collection of open-source compilers, code analysis and refactoring tools which work with C# and Visual Basic source codes. This set of compilers and tools can be used to create full-fledged compilers, including, first and foremost, source code analysis tools.

What does Roslyn code analysis do?

NET Compiler Platform (Roslyn) Analyzers inspect your C# or Visual Basic code for style, quality, maintainability, design, and other issues. This inspection or analysis happens during design time in all open files. Analyzers are divided into the following groups: Code style analyzers are built into Visual Studio.

Is Roslyn based analyzer code hard?

NET Code Analyser . NET Compiler as a service. While it is relatively easy to write code, it is not so easy to write high quality maintainable code.

How do I enable Roslyn?

The simplest way is File -> Preferences -> Settings, from there search for "roslyn" and tick the setting "Enable Roslyn Analyzers". Be sure to save your settings.


2 Answers

Yes, I think Roslyn and its code issues are exactly the right tool for this. With them, you can analyze the code while you type and create errors (or warnings) that are shown as other errors in Visual Studio.

I have tried to create such code issue:

[ExportSyntaxNodeCodeIssueProvider("PropertyExpressionCodeIssue", LanguageNames.CSharp, typeof(InvocationExpressionSyntax))]
class PropertyExpressionCodeIssueProvider : ICodeIssueProvider
{
    [ImportingConstructor]
    public PropertyExpressionCodeIssueProvider()
    {}

    public IEnumerable<CodeIssue> GetIssues(IDocument document, CommonSyntaxNode node, CancellationToken cancellationToken)
    {
        var invocation = (InvocationExpressionSyntax)node;

        var semanticModel = document.GetSemanticModel(cancellationToken);

        var semanticInfo = semanticModel.GetSemanticInfo(invocation, cancellationToken);

        var methodSymbol = (MethodSymbol)semanticInfo.Symbol;

        if (methodSymbol == null)
            yield break;

        var attributes = methodSymbol.GetAttributes();

        if (!attributes.Any(a => a.AttributeClass.Name == "PropertyExpressionAttribute"))
            yield break;

        var arguments = invocation.ArgumentList.Arguments;
        foreach (var argument in arguments)
        {
            var lambdaExpression = argument.Expression as SimpleLambdaExpressionSyntax;
            if (lambdaExpression == null)
                continue;

            var parameter = lambdaExpression.Parameter;
            var memberAccess = lambdaExpression.Body as MemberAccessExpressionSyntax;
            if (memberAccess != null)
            {
                var objectIdentifierSyntax = memberAccess.Expression as IdentifierNameSyntax;

                if (objectIdentifierSyntax != null
                    && objectIdentifierSyntax.PlainName == parameter.Identifier.ValueText
                    && semanticModel.GetSemanticInfo(memberAccess, cancellationToken).Symbol is PropertySymbol)
                    continue;
            }

            yield return
                new CodeIssue(
                    CodeIssue.Severity.Error, argument.Span,
                    string.Format("Has to be simple property access of '{0}'", parameter.Identifier.ValueText));
        }
    }

    #region Unimplemented ICodeIssueProvider members

    public IEnumerable<CodeIssue> GetIssues(IDocument document, CommonSyntaxToken token, CancellationToken cancellationToken)
    {
        throw new NotImplementedException();
    }

    public IEnumerable<CodeIssue> GetIssues(IDocument document, CommonSyntaxTrivia trivia, CancellationToken cancellationToken)
    {
        throw new NotImplementedException();
    }

    #endregion
}

The usage would be like this:

[AttributeUsage(AttributeTargets.Method)]
class PropertyExpressionAttribute : Attribute
{ }

…

[PropertyExpression]
static void Foo<T>(Expression<Func<SomeType, T>> expr)
{ }

…

Foo(x => x.P);   // OK
Foo(x => x.M()); // error
Foo(x => 42);    // error

The code above has several issues:

  1. It's completely unoptimized.
  2. It probably needs some more error checking.
  3. It does not work. At least in the current CTP. The expression semanticModel.GetSemanticInfo(memberAccess, cancellationToken).Symbol near the end always returns null. This is because semantics of expressions trees is among the currently unimplemented features.
like image 77
svick Avatar answered Nov 15 '22 18:11

svick


Yes, it's totally possible. The problem is Roslyn doesn't yet support all language constructs yet, so you might run into some unsupported stuff. Expression trees are unsupported in that Roslyn cannot compile code that generates expressions, but you should be able to get far enough to make some things work.

At a high level, if you wanted to implement this as an MSBuild task, in your build task you could call Roslyn.Services.Workspace.LoadSolution or Roslyn.Services.Workspace.LoadStandaloneProject. You'd then walk through the syntax trees looking for mentions of your various methods, and then bind them to make sure it's actually the method you think you're calling. From there, you could find the lambda syntax nodes, and perform whatever syntax/semantic analysis you want from there.

There are a few sample projects in the CTP you might find useful, such as the RFxCopConsoleCS project, which implements a simple FxCop-style rule in Roslyn.

I should also mention that the parser is complete for Roslyn, so the more you can do without semantic information, the better.

like image 40
Jason Malinowski Avatar answered Nov 15 '22 18:11

Jason Malinowski