openjdk.ruOpenJDK на русском

JEP 216: Process Import Statements Correctly

Корректная обработка объявлений import

ОтветственныйJan Lahoda
ТипFeature
ОбластьImplementation
СтатусClosed / Delivered
Выпуск9
Компонентtools / javac
Обсуждениеcompiler dash dev at openjdk dot java dot net
ТрудоёмкостьS
ДлительностьM
Связан сJEP 215: Tiered Attribution for javac
РецензентыAlex Buckley, Brian Goetz, Jonathan Gibbons, Maurizio Cimadamore
ОдобренBrian Goetz
Создан2014/08/26 13:05
Обновлён2016/07/12 20:59
Задача8056066

Аннотация

Исправить javac, чтобы он правильно принимал и отклонял программы независимо от порядка инструкций import и конструкций extends и implements.

Мотивация

В некоторых случаях javac принимает исходный код с одним порядком импортов и отклоняет тот же исходный код, в котором импорты просто переставлены (например, JDK-7177813). Это неправильно и сбивает с толку.

Описание

При компиляции классов javac проходит несколько этапов. Для обработки import важны два из них:

  • разрешение типов, при котором в переданном AST ищутся объявления классов и интерфейсов, и

  • разрешение членов, которое включает:

    • (1a) если T — класс верхнего уровня, обрабатываются import в исходном файле, где определён T, и импортированные члены добавляются в область видимости T;
    • (1b) если T — вложенный класс, выполняется разрешение класса, непосредственно объемлющего T (если такой есть);
    • (2) выполняется проверка типов в конструкциях extends/implements класса T;
    • (3) выполняется проверка типов для переменных типа T.

Эти этапы входят в процесс разрешения классов в javac, который включает определение супертипов, переменных типа и членов класса.

Чтобы увидеть этот процесс в действии, рассмотрим следующий код:

package P;

import static P.Outer.Nested.*;
import P.Q.*;

public class Outer {
    public static class Nested implements I {
    }
}

package P.Q;
public interface I {
}

На этапе разрешения типов выясняется, что существуют типы P.Outer, P.Outer.Nested и P.Q.I. Затем, если нужно проанализировать класс P.Outer, этап разрешения членов проходит так:

1.Начинается разрешение P.Outer
2.Согласно 1a, начинается обработка import static P.Outer.Nested.*;, то есть ищутся члены P.Outer.Nested и его супертипов, в том числе транзитивных.
3.Начинается разрешение класса P.Outer.Nested (статические импорты могут импортировать и унаследованные типы)
4.Запускается разрешение P.Outer, но оно пропускается, так как уже выполняется
5.Выполняется проверка типов для I (конструкция implements), но I не удаётся разрешить, потому что его ещё нет в области видимости.
6.Начинается разрешение import P.Q.*, при котором все типы-члены P.Q (включая интерфейс I) импортируются в область видимости текущего файла
7.Разрешение P.Outer и других классов продолжается

Если поменять импорты местами, шаг 6 выполняется раньше шага 5, и поэтому I находится на шаге 5.

Это не единственная проблема, связанная с обработкой import. Другая известная проблема: границы параметров типа класса могут корректно ссылаться на возможные внутренние классы объявляющего их класса. Сейчас в некоторых случаях это приводит к неразрешимым циклам, например:

package P;

import static P.Outer.Nested.*;

public class Outer {
    public static class Nested<T extends I> {
        static class I { }
    }
}

Предполагаемое решение этой проблемы — разделить нынешний первый этап разрешения членов в javac на три. Первый будет анализировать импорты объемлющего файла, второй — только строить иерархию классов и интерфейсов, без параметров типа, аннотаций и т. д., а третий — полноценно анализировать заголовки классов, включая параметры типа.

Ожидается, что после этого изменения javac будет принимать программы, которые сейчас отклоняет, но не будет отклонять программы, которые сейчас принимает.