在 ScopeProvider 中调用引用对象时出错 "Cyclic linking detected"

Error "Cyclic linking detected" while calling a referenced object in a ScopeProvider

我目前正在为我的 Xtext dsl 实现交叉引用。一个 dsl 文件可以包含多个 XImportSection,在某些特殊情况下,XImportSection 不一定包含所有导入语句。这意味着我需要将 "XImportSectionNamespaceScopeProvider" 自定义为 find/build 正确的 XimportSection。在实施过程中,我发现了编辑器 and/or 一些验证的意外行为。

我使用以下 dsl 代码片段来测试我的实现:

delta MyDelta {
    adds {
        package my.pkg;
        import java.util.List;
        public class MyClass 
                implements List
                                {
        } 
    }
    modifies my.pkg.MyClass { // (1)

        adds import java.util.ArrayList;
        adds superclass ArrayList<String>;
    }
}

dsl源码由以下语法规则描述(不完整!):

AddsUnit:
    {AddsUnit} 'adds' '{' unit=JavaCompilationUnit? '}';

ModifiesUnit:
    'modifies' unit=[ClassOrInterface|QualifiedName] '{'
    modifiesPackage=ModifiesPackage?
    modifiesImports+=ModifiesImport*
    modifiesSuperclass=ModifiesInheritance?
    '}';

JavaCompilationUnit:
    => (annotations+=Annotation*
    'package' name=QualifiedName EOL)?
    importSection=XImportSection?
    typeDeclarations+=ClassOrInterfaceDeclaration;

ClassOrInterfaceDeclaration:
    annotations+=Annotation* modifiers+=Modifier* classOrInterface=ClassOrInterface;

ClassOrInterface: // (2a)
    ClassDeclaration | InterfaceDeclaration | EnumDeclaration | AnnotationTypeDeclaration;

ClassDeclaration: // (2b)
    'class' name=QualifiedName typeParameters=TypeParameters?
    ('extends' superClass=JvmTypeReference)?
    ('implements' interfaces=Typelist)?
    body=ClassBody;

为了提供更好的工具支持,ModifiesUnit引用了修改后的class。此 Xtext 特定实现启用到 class.

的超链接

我目前正在开发定制的 XImportSectionScopeProvider,它为 ModifiesUnit 提供所有命名空间范围。默认实现包含一种方法 protected List<ImportNormalizer> internalGetImportedNamespaceResolvers(EObject context, boolean ignoreCase) 假定源文件中只有一个 class 类元素。但是对于我的语言来说,可能不止一个。为此,我必须对其进行自定义。

我现在的想法是如下实现(使用Xtend编程语言):

override List<ImportNormalizer> internalGetImportedNamespaceResolvers(EObject context, boolean ignoreCase) {
    switch (context) {
        ModifiesUnit: context.buildImportSection
        default: // ... anything else
    }
}

在我开始这项工作之前,参考工作正常,没有发生任何意外情况。我现在的目标是为 ModifiesUnit 构建一个自定义的 XImportSection,Xbase 使用它来解析对 JVM 类型的引用。为此,我需要引用 ClassOrInterface 的 XImportSection 的副本。为了访问 XImportSection,我首先调用 ModifiesUnit.getUnit()。执行此调用后,编辑器立即显示意外行为。导致错误的最小实现如下所示:

def XImportSection buildImportSection(ModifiesUnit u) {
    val ci = u.unit // Since this expression is executed, the error occurs!
    // ...
}

这里,不知道内部是怎么回事!但是它计算出一个错误。编辑器在 (1) 的限定名称上显示以下错误:"Cyclic linking detected : ModifiesUnit.unit->ModifiesUnit.unit".

我的问题是:这是什么意思?为什么 Xtext 显示此错误?为什么访问引用的对象会出现?

我还发现了一件奇怪的事情:在我的第一种方法中,我的代码抛出了一个 NullPointerException。好的,我试图通过打印对象 ci 来找出原因。结果是:

org.deltaj.scoping.deltaJ.impl.ClassOrInterfaceImpl@4642f064 (eProxyURI: platform:/resource/Test/src/My.dj#xtextLink_::0.0.0.1.1::0::/2)
org.deltaj.scoping.deltaJ.impl.ClassDeclarationImpl@1c70366 (name: MyClass)

好的,看来是这个方法执行了两次,Xtext在第一次和第二次执行之间解析代理。只要接收到的对象是正确的一次,对我来说就可以了。我用 if-instanceof 语句处理它。

但是为什么我在那里得到两个引用?它是否依赖于 ParserRule ClassOrInterface (2a) 这只是 ClassDeclaration (2b) 的抽象超级规则?但为什么 Xtext 无法解析 ClassOrInterface 的引用?

好的,现在我找到了解决问题的方法。在我试验我的实现时,我发现 "Problems" 视图仍然包含未解析的引用。这就是重新考虑我的实施所做的事情的原因。起初,我决定直接构建返回的列表 List<ImportNormalizer 而不是构建一个 XImportSection 然后将转换为这个列表。在实现这个过程中,我注意到我只为 ModifiesUnit 元素构建了范围,而不是需要 ModifiesUnit 范围内的元素。这就是循环链接错误的原因。现在,我只在需要时才构建列表。结果是不再发生循环链接错误,所有对 JVM 类型的引用都正确解析,问题视图中没有任何错误。

我的实现现在看起来像这样:

class DeltaJXImportSectionNamespaceScopeProvider extends XImportSectionNamespaceScopeProvider {

    override List<ImportNormalizer> internalGetImportedNamespaceResolvers(EObject context, boolean ignoreCase) {

        // A scope will only be provided for elements which really need a scope. A scope is only necessary for elements
        // which are siblings of a JavaCompilationUnit or a ModifiesUnit.
        if (context.checkElement) { // (1)
            return Collections.emptyList
        }

        // Finding the container which contains the import section
        val container = context.jvmUnit // (2)

        // For a non null container create the import normalizer list depending of returned element. If the container is
        // null, no scope is needed.
        return if (container != null) { // (3)
            switch (container) {
                JavaCompilationUnit: container.provideJcuImportNormalizerList(ignoreCase)
                ModifiesUnit: container.provideMcuImportNormalizerList(ignoreCase)
            }
        } else {
            Collections.emptyList
        }

    }

    // Iterates upwards through the AST until a ModifiesUnit or a JavaCompilationUnit is found. (2)
    def EObject jvmUnit(EObject o) {
        switch (o) {
            ModifiesUnit: o
            JavaCompilationUnit: o
            default: o.eContainer.jvmUnit
        }
    }

    // Creates the list with all imports of a JCU (3a)
    def List<ImportNormalizer> provideJcuImportNormalizerList(JavaCompilationUnit jcu, boolean ignoreCase) {
        val is = jcu.importSection
        return if (is != null) {
            is.getImportedNamespaceResolvers(ignoreCase)
        } else {
            Collections.emptyList
        }
    }

    // Creates the list of all imports of a ModifiesUnit. This implementation is similar to 
    // getImportedNamespaceResolvers(XImportSection, boolean) // (3b)
    def List<ImportNormalizer> provideMcuImportNormalizerList(ModifiesUnit mu, boolean ignoreCase) {
        val List<ImportNormalizer> result = Lists.newArrayList
        result.addAll((mu.unit.jvmUnit as JavaCompilationUnit).provideJcuImportNormalizerList(ignoreCase))
        for (imp : mu.modifiesImports) {
            if (imp instanceof AddsImport) {
                val decl = imp.importDeclaration
                if (!decl.static) {
                    result.add(decl.transform(ignoreCase))
                }
            }
        }
        result
    }

    // Creates an ImportNormalizer for a given XImportSection
    def ImportNormalizer transform(XImportDeclaration decl, boolean ignoreCase) {
        var value = decl.importedNamespace
        if (value == null) {
            value = decl.importedTypeName
        }
        return value.createImportedNamespaceResolver(ignoreCase)
    }

    // Determines whether an element needs to be processed. (1)
    def checkElement(EObject o) {
        return o instanceof DeltaJUnit || o instanceof Delta || o instanceof AddsUnit || o instanceof ModifiesUnit ||
            o instanceof RemovesUnit
    }
}

如您所见,不需要命名空间来实现正确范围的元素将被忽略 (1)。

对于可能需要正确范围的命名空间的每个元素,确定直接包含导入的 n-father 元素 (2)。

使用正确的父元素,可以为 JCU (3a) 和 MU (3b) 计算 (3) 命名空间列表。