Ошибка циклического связывания обнаружена при вызове ссылочного объекта в ScopeProvider

В настоящее время я реализую перекрестные ссылки для своего Xtext dsl. Файл dsl может содержать более одного XImportSection, и в некоторых особых случаях XImportSection не обязательно содержит все операторы импорта. Это означает, что мне нужно настроить «XImportSectionNamespaceScopeProvider», чтобы найти/создать правильный XimportSection. Во время реализации я обнаружил неожиданное поведение редактора и/или некоторую валидацию.

Я использовал следующий код 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 ссылается на класс, который изменяется. Эта конкретная реализация Xtext позволяет использовать гиперссылки на класс.

В настоящее время я работаю над настраиваемым XImportSectionScopeProvider, который предоставляет все области пространства имен для ModifiesUnit. Реализация по умолчанию содержит метод protected List<ImportNormalizer> internalGetImportedNamespaceResolvers(EObject context, boolean ignoreCase), предполагающий, что в исходном файле есть только один классоподобный элемент. Но для моего языка их может быть больше одного. По этой причине я должен настроить его.

Моя идея сейчас заключается в следующей реализации (с использованием языка программирования Xtend):

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

До того, как я начал эту работу, ссылка работала нормально, и ничего неожиданного не произошло. Теперь моя цель — создать настроенный XImportSection для ModifiesUnit, который используется Xbase для разрешения ссылок на типы JVM. Для этого мне нужна копия XImportSection указанного ClassOrInterface. Чтобы получить доступ к XImportSection, я сначала вызываю ModifiesUnit.getUnit(). Непосредственно после выполнения этого вызова редактор показывает неожиданное поведение. Минимальная реализация, приводящая к ошибке, выглядит так:

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

Здесь я не знаю, что происходит внутри! Но вычисляет ошибку. Редактор показывает следующую ошибку для квалифицированного имени в (1): «Обнаружено циклическое связывание: ModifiedUnit.unit->ModizesUnit.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?


person Joko    schedule 14.01.2015    source источник
comment
Я также задал этот вопрос на форуме сообщества Eclipse. См. eclipse.org/forums/index.php/t/943873   -  person Joko    schedule 14.01.2015


Ответы (1)


Хорошо, теперь я нашел решение своей проблемы. Во время экспериментов со своей реализацией я увидел, что представление «Проблемы» все еще содержит неразрешенные ссылки. Это послужило поводом переосмыслить то, что делала моя реализация. Сначала я решил построить возвращаемый список List<ImportNormalizer напрямую вместо создания XImportSection, который затем будет преобразован в этот список. Во время реализации этого я заметил, что я построил область только для ModifiesUnitelements вместо элементов, которым нужна область внутри 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).

При правильном отцовском элементе список пространств имен может быть рассчитан (3) для JCU (3a) и MU (3b).

person Joko    schedule 19.01.2015