繁体   English   中英

在Roslyn的C#编译器中合成lambda

[英]Synthesizing a lambda in Roslyn's C# compiler

我一直在试验Roslyn中最近开源的C#编译器,看看我是否可以添加语言功能。

我现在正在尝试添加一些语法糖,一个新的前缀运算符,它基本上是某种模式的简写。 现在我在预先存在捎带& “地址”,外面unsafe环境。

我想扩展的模式如下: &n相当于:

Property.Bind(v => n = v, () => n)

假设Property.Bind方法在库中可用,签名为:

public static IProperty<T> Bind<T>(Action<T> set, Func<T> get)

所以本质上我需要合成两个lambdas:

  1. 绑定到n作为左值并从其参数进行赋值,并且
  2. 没有参数并评估n 然后我需要调用Property.Bind传递这两个lambdas作为参数。

我以前的实验比这更容易,因为他们能够捎带易于查找的现有功能,所以几乎没有工作要做!

但是这次我很难找到类似于我在这里做的事情。 到目前为止,我已经逐步介绍了编译器从源代码构建BoundLambda方式,并且它正在展开一个大混乱。 我正在修改BindAddressOfExpression中的Binder_Operators.cs ,使其以安全上下文的额外if语句开头:

private BoundExpression BindAddressOfExpression(PrefixUnaryExpressionSyntax node, DiagnosticBag diagnostics)
{
    if (!this.InUnsafeRegion)
    {
        BoundExpression rValue = BindValue(node.Operand, diagnostics, BindValueKind.RValue);
        BoundExpression lValue = BindValue(node.Operand, diagnostics, BindValueKind.Assignment);

        var valueParamSymbol = new SourceSimpleParameterSymbol(null, rValue.Type, 0, RefKind.None, "__v", ImmutableArray<Location>.Empty);
        var valueParam = new BoundParameter(node, valueParamSymbol);
        var assignment = new BoundAssignmentOperator(node, lValue, valueParam, RefKind.None, rValue.Type);

        var assignmentStatement = new BoundExpressionStatement(node, assignment);
        var assignmentBlock = new BoundBlock(node, ImmutableArray<LocalSymbol>.Empty, ImmutableArray.Create<BoundStatement>(assignmentStatement)) { WasCompilerGenerated = true };
        assignmentBlock = FlowAnalysisPass.AppendImplicitReturn(assignmentBlock);

所以(大概!)现在我有第一个lambda的赋值块,但是在它周围获得一个完整的BoundLambda看起来是一个全新的挑战。

我想知道:有没有办法通过要求解析器/绑定器处理一串C#来“欺骗”这种语法糖,好像它已经出现在实际代码的位置? 这样,手动构建所有零件并将它们拼接在一起就没有必要了。 毕竟,现有的编译器非常适合这个!

更新 :我已经确定了一个名为SyntaxTemplate的新类,它是不可变的,因此可以静态创建并重用。 例如

private static readonly SyntaxTemplate _pointerIndirectionTemplate 
      = new SyntaxTemplate("p.Value");

private static readonly SyntaxTemplate _propertyReferenceTemplate
     = new SyntaxTemplate("System.Property.Bind(__v_pr__ => o = __v_pr__, () => o)");

private static readonly SyntaxTemplate _propertyReferenceTypeTemplate 
     = new SyntaxTemplate("System.IProperty<T>");

private static readonly SyntaxTemplate _enumerableTypeTemplate 
     = new SyntaxTemplate("System.Collections.Generic.IEnumerable<T>");

它内部有一个包含所有标识符的不可变字典,因此任何都可以用名称替换,例如表达式:

if (!operand.Type.IsPointerType())
    return BindExpression(
        _pointerIndirectionTemplate.Replace("p", node.Operand).Syntax, 
        diagnostics);

或者类型:

if (this.IsIndirectlyInIterator || !this.InUnsafeRegion)
    return BindNamespaceOrTypeOrAliasSymbol(
        _enumerableTypeTemplate.Replace("T", node.ElementType).Syntax,
        diagnostics, basesBeingResolved, suppressUseSiteDiagnostics);

SyntaxTemplate看起来像这样:

internal class SyntaxTemplate
{
    public ExpressionSyntax Syntax { get; private set; }

    private readonly ImmutableDictionary<string, ImmutableList<IdentifierNameSyntax>> _identifiers;

    public SyntaxTemplate(string source)
    {
        Syntax = SyntaxFactory.ParseExpression(source);

        var identifiers = ImmutableDictionary<string, ImmutableList<IdentifierNameSyntax>.Builder>.Empty.ToBuilder();

        foreach (var node in Syntax.DescendantNodes().OfType<IdentifierNameSyntax>())
        {
            ImmutableList<IdentifierNameSyntax>.Builder list;
            if (!identifiers.TryGetValue(node.Identifier.Text, out list))
                list = identifiers[node.Identifier.Text] = 
                    ImmutableList<IdentifierNameSyntax>.Empty.ToBuilder();
            list.Add(node);
        }

        _identifiers = identifiers.ToImmutableDictionary(
            p => p.Key, p => p.Value.ToImmutableList());
    }

    private SyntaxTemplate(ExpressionSyntax syntax,
        ImmutableDictionary<string, ImmutableList<IdentifierNameSyntax>> identifiers)
    {
        Syntax = syntax;
        _identifiers = identifiers;
    }

    public SyntaxTemplate Replace(string identifier, SyntaxNode value)
    {
        return new SyntaxTemplate(
            Syntax.ReplaceNodes(_identifiers[identifier], (o1, o2) => value),
            _identifiers.Remove(identifier));
    }
}

因为替换值是一个SyntaxNode ,所以您可以使用已经由解析器创建的节点,因此不会浪费两次相同的语法。

更多 :除了如果用户的源中存在错误(例如,他们在没有意义的情况下使用新语法)之外,这是有效的,那么绑定期间生成的错误指的是模板源中的位置,这是无意义的在用户的来源中。 因此IDE无法显示红色曲线等。

为了解决这个问题,您可以使用辅助方法在临时包中捕获诊断信息,然后将它们重放到真实包中,并将位置更改为用户源中使用语法的位置:

private T RedirectDiagnostics<T>(DiagnosticBag diagnostics, CSharpSyntaxNode nodeWithLocation, Func<DiagnosticBag, T> generate)
{
    var captured = new DiagnosticBag();
    var result = generate(captured);

    foreach (var diag in captured.AsEnumerable().OfType<DiagnosticWithInfo>())
        diagnostics.Add(new CSDiagnostic(diag.Info, nodeWithLocation.Location));

    return result;
}

示例用法,只是从上面包装第一个示例:

if (!operand.Type.IsPointerType())
    return RedirectDiagnostics(diagnostics, node, redirected => 
        BindExpression(_pointerIndirectionTemplate.Replace("p", node.Operand).Syntax, redirected));

现在红色波浪线正常工作(并且在真正的编译中,错误消息上的行号是正确的)。

我建议你看看查询表达式如何“扩展”到使用编译器生成的lambdas的方法调用。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM