aleksey.tomin
12/10/2020, 9:23 AM@Serializable
и data class
в целом никакой нет. Но первое это библиотека + плагин, второе- часть языка.
2. После подключения @Serializable
очень сложно понять, что за код там получился- надо отрезать исходники и смотреть декомпилированный файл (или можно проще? Скажите, интересно).
3. Глядя в реализацию плагина я вижу 3 части - jvm/js/native хотя в целом там должно быть всё одинаково.
4. Плагин надо брать версии kotlin а саму библиотеку- другой версии. И можно (в теории, мне не попадалось) выбрать несовместимые варианты.
Мне давно уж казалось, что в подходе “аннотация+плагин” есть принципиальный изъян. И язык forth не давал покоя 🙂
Наконец в голове сложился пазл “как должно быть“:
1. Аннотация типа @Serializable
должна быть специального типа - не public annotation class Serializable
а public plugin class Serializable
(имя неважно) и располагаться в compile-only-time библиотеке.
2. Класс аннотации должен выполнять работу над кодом, но не IR и т.п. а над PSI или FIR - см. новый frontend compiler, например
3. После выполнения плагина надо FIR превратить обратно в исходный код и сложить в какую-то папку, типа автогенерированных классов.
4. Понятно, что в рантайме не должно быть завязки на плагин-аннотацию, но хвостик должен остаться.
Что получится?
1. Легко можно увидеть (поддержать в IDEA легко) результат применения аннотаций-плагинов, со всеми комментариями. И в дебаге можно видеть реальный код.
2. Разработка аннотаций-плагинов на порядок проще, чем обычных плагинов на основе аннотаций.
3. Т.к. IDEA всё равно должна строить FIR а аннотации-плагины не должны долго работать- IDEA будет “знать” результаты работы аннотации-плагина.
4. Если в скомпилированном коде будет “хвост” аннотации-плагина, то это позволит выполнять работу над классами-потомками - например для корректной реализации контрактов (в настоящий момент это нельзя сделать никак).
5. Можно изменять в том числе комментарий - и, соответственно, документацию.Iaroslav Postovalov
12/10/2020, 12:57 PMval x:kotlin.String=(("kuku"as kotlin.String)+(12))
и хочу получить val x = "kuku${12}"
aleksey.tomin
12/10/2020, 3:13 PMIaroslav Postovalov
12/10/2020, 3:20 PMaltavir
12/10/2020, 7:45 PMaleksey.tomin
12/11/2020, 5:04 AMиметь возможность декомпилировать fir в кодА это ж есть. Можно открыть native библиотеку
это во-первых. во-вторых, тогда уж хочется иметь всякие идеевские элементы работы с PSI типа нормализации кодаА, теперь понял (вчера с телефона читал, упустил). Надо глянуть, что там в PSI
Iaroslav Postovalov
12/11/2020, 5:24 PMval cls = buildRegularClass {
name = Name.identifier("Kuku")
}
aleksey.tomin
12/12/2020, 3:29 AMIaroslav Postovalov
12/12/2020, 3:52 PMaleksey.tomin
12/12/2020, 3:56 PMdmitriy.novozhilov
12/13/2020, 1:06 PMАннотация типа @Serializable должна быть специального типа - не public annotation class Serializable а public plugin class Serializable (имя неважно) и располагаться в compile-only-time библиотеке.О компиляторных плагинах и сейчас можно думать как оcompile-only зависимости, которая включает в себя две части: 1. библиотека с аннотациями с source retention'ом (хотя зависит и от аннотаций, т.к. кто-то может хотеть их и на рантайме тоже: тулинг/фреймворки/...) 2. jar с компиляторным плагином
Класс аннотации должен выполнять работу над кодом, но не IR и т.п. а над PSI или FIR - см. новый frontend compiler, напримерВ такой концепции это уже получается не аннотация, а декоратор. И у них возможностей гораздо меньше чем у связки "аннотации + компиляторный плагин", т.к. декоратор имеет доступ только непосредственно к той декларации, на которую его повесили. Плагин же может получать от компилятора информацию про весь компилируемый мир (с некоторыми оговорками): все декларации, проаннотированные аннотацией из плагина/аннотациями, проаннотированными мета-аннотациями из плагина, информацию про любой доступный в compile-time класс (супертипы, мемберы и т.д) и много чего ещё. Более того, то, что плагин хочет генерить может зависеть именно на эту информацию. Также соединение аннотаций и плагина в один
plugin class
не решает проблему сочетаний версий плагина и компилятора, т.к. такому плагину всё-равно придётся зависеть на компилятор определённой версии, иначе никаких компиляторных плюшек ему получить не удастся
После выполнения плагина надо FIR превратить обратно в исходный код и сложить в какую-то папку, типа автогенерированных классов.В текущей концепции на фронтенде плагинам предполагается создавать только декларации. Как именно будет создаваться контент тел ещё не ясно. Есть как минимум три различных способа: 1. создавать голый PSI/source string и прогонять весь пайплайн компилятора по этому телу 2. создавать порезолвленный FIR (со всеми проставленными типами, ссылками на функции в местах вызовов и прочим) 3. создавать уже IR на стадии бекенда (потому что фронтеду тело совершенно не нужно) В целом иметь возможность посмотреть на декомпилированный исходный код того, что нагенерили плагины -- это идея хорошая, но пока неясно, как это будет реализовано. Тут, к слову, может помочь декомпилятор IR'а, для которого у нас есть прототип (правда он сейчас заморожен, т.к. он не сильно в приоритете и нет свободных ресурсов, чтобы его завершить)
Ну так FIR в отличии от IR это дерево исходников, как PSIЭто очень грубое и неправильное суждение PSI -- это синтаксическое дерево FIR, как и IR -- это семантическое дерево, из которого выкинута всякая ненужная информация про синтаксис (пробелы, скобки, комментарии) и сахар (так все if'ы представлены как when'ы), но добавлено много различной семантической информации, которую компилятор насобирал в процессе анализа Вся разница между FIR и IR в том, что в них есть несколько разная информация, которая нужна только фронтнеду или бекенду соответственно
Легко можно увидеть (поддержать в IDEA легко) результат применения аннотаций-плагинов, со всеми комментариями. И в дебаге можно видеть реальный код.
Т.к. IDEA всё равно должна строить FIR а аннотации-плагины не должны долго работать- IDEA будет “знать” результаты работы аннотации-плагина.Видеть в дебаггере реальный код -- это наименьшая из проблем поддержки плагинов в IDE. Основнгая проблема заключается в том, как корректно отслеживать, что сгенерённые декларации меняются, если пользователь меняет какой-то свой код. Т.е. как правильно сделать такой инкрементальный и ленивый анализ, чтобы не провоцировать переанлиз всего проекта при изменении в каком-нибудь файле (API для фира мы дизайним с учётом этой проблемы, но до прототипирования IDE части пока не добрались)
Разработка аннотаций-плагинов на порядок проще, чем обычных плагинов на основе аннотаций.Почему?
Если в скомпилированном коде будет “хвост” аннотации-плагина, то это позволит выполнять работу над классами-потомками - например для корректной реализации контрактов (в настоящий момент это нельзя сделать никак).
Можно изменять в том числе комментарий - и, соответственно, документацию.Эти два пункта я не очень понял
aleksey.tomin
12/14/2020, 4:39 AMВклинюсь в дискуссию, как один из людей, которые работают над фиром в целом и компиляторном API в нём в частности.Отлично, спасибо 🙂
О компиляторных плагинах и сейчас можно думать как оcompile-only зависимости, которая включает в себя две частиСобственно вот “две части” и составляет большую проблему. У нас есть совершенно неочевидная зависимость между этими частями.
Плагин же может получать от компилятора информацию про весь компилируемый мир (с некоторыми оговорками)Не считая того, что нелокальность действия- антипатерн, но это ещё сильно осложняет работу IDE(A) - она не может показать то, что натворит плагин.
Также соединение аннотаций и плагина в одинЭто верно, если плагин входит в котлин (как kotlinx.serialization). Но для стороннего плагина это неверно- нужно правильно сочетать версию зависимости, плагина и компилятора. И не всегда это будет просто.не решает проблему сочетаний версий плагина и компилятора, т.к. такому плагину всё-равно придётся зависеть на компилятор определённой версии, иначе никаких компиляторных плюшек ему получить не удастсяplugin class
Тут, к слову, может помочь декомпилятор IR’а, для которого у нас есть прототип (правда он сейчас заморожен, т.к. он не сильно в приоритете и нет свободных ресурсов, чтобы его завершить)А разве просмотр нативных библиотек в IDEA сделана не через декомпиляцию IR?
PSI -- это синтаксическое дерево
FIR, как и IR -- это семантическое дерево, из которого выкинута всякая ненужная информация про синтаксисСтранно. Я так понимаю, сейчас в новом компиляторе идёт преобразование
Source -> PSI -> raw FIR -> FIR -> IR
.
И ошибки компиляции возможны на этапе raw FIR -> FIR
. При этом компилятору нужно знать, где показать ошибку.
Кроме того, я думал, что PSI вообще будет удалено в будущем- как-то привык к полуторопроходным цельным компиляторам и видеть четырёхпроходный фронтенд как-то необычно - думал, это временно.
Основная проблема заключается в том, как корректно отслеживать, что сгенерённые декларации меняются, если пользователь меняет какой-то свой код. Т.е. как правильно сделать такой инкрементальный и ленивый анализ, чтобы не провоцировать переанлиз всего проекта при изменении в каком-нибудь файлеДа, нелокальность плагина приводит и к этой проблеме. У меня есть некоторые мысли на эту тему, но надо будет внимательно посмотреть примеры использования нелокальных плагинов.
> Разработка аннотаций-плагинов на порядок проще, чем обычных плагинов на основе аннотаций.
Почему?Во-первых работать с деревом исходного кода проще, чем с IR (хотя я работал только с java-плагином, который сразу байткод правит). Я пробовал генерировать как source-код, так и байткод- и боли во втором случае намного больше. Если будет возможность всегда посмотреть исходный код, порождённый декоратором - то отладка такой аннотации будет очень простой. Читать IR/bytecode это совершенно другой уровень.
Эти два пункта я не очень понял
> Если в скомпилированном коде будет “хвост” аннотации-плагина, то это позволит выполнять работу над классами-потомками - например для корректной реализации контрактов (в настоящий момент это нельзя сделать никак).Я всё думаю на тему реализации контрактов, как в языке eiffel. Там описание контракта метода накладывает ограничение на всех наследников метода. Если аннотация-декоратор всё же оставляет след в виде обычной аннотации, то при работе аннотации-декораторе на наследнике метода можно получить значение из предка и гарантировать корректность кода. Второй пункт “Можно изменять в том числе комментарий - и, соответственно, документацию.” - при просмотре сторонней библиотеки я могу не видеть исходный код, а видеть только спецификацию метода и javadoc (или kotlindoc? Не знаю, как корректно назвать). Например та же
@Serializable
может добавлять в комментарий к классу- какие поля сериализуются, или ещё что-то там.Плагин же может получать от компилятора информацию про весь компилируемый мир (с некоторыми оговорками): все декларации, проаннотированные аннотацией из плагина/аннотациями, проаннотированными мета-аннотациями из плагина, информацию про любой доступный в compile-time класс (супертипы, мемберы и т.д) и много чего ещё. Более того, то, что плагин хочет генерить может зависеть именно на эту информациюВопрос ко всем - а какие хорошие примеры именно нелокальной работы плагина? Чтобы ему нужно было иметь доступ ко всему коду.
dmitriy.novozhilov
12/14/2020, 7:37 AMСобственно вот “две части” и составляет большую проблему. У нас есть совершенно неочевидная зависимость между этими частями.Это чинится синхронным версионированием плагина и библиотек или же gradle пагином, который сразу подключит нужные версии (например, в нём зашито соответствие версии плагина к версии библиотеки в случае, если последняя обновляется реже)
Это верно, если плагин входит в котлин (как kotlinx.serialization). Но для стороннего плагина это неверно- нужно правильно сочетать версию зависимости, плагина и компилятора. И не всегда это будет простоЯ так понимаю, тут вы говорите ещё и про плагин для IDE. В идеальном мире, к которому мы стремимся, плагин для IDE является необязательной частью. Если же он есть, то он должен автоматически ставиться в идею, если в зависимостях проекта используется компиляторный плагин. Правда есть ещё проблема, что ставить в идею, если в разных модулях проекта используются разные версии компиляторного плагина (и для пока непонятно, что с таким делать)
И ошибки компиляции возможны на этапе raw FIR -> FIR . При этом компилятору нужно знать, где показать ошибку.Внутри FIR мы храним ссылки на исходный код, чтобы можно было на нём репортить диагностики. При этом у нас есть два варианта представления исходников (PSI и light tree, который является сильно облегчённой версией PSI). В целом, для репортинга достаточно иметь где-нибудь строку с исходным кодом и start и end offset'ы для каждого элемента
Кроме того, я думал, что PSI вообще будет удалено в будущемНе будет, т.к. IDE часть сильно зависит на PSI
видеть четырёхпроходный фронтенд как-то необычно - думал, это временно.Опять же, это навсегда. Работать со своими внутренними представлениями (FIR, IR) гораздо проще, чем с исходником/бинарным представлением. Плюс IR структура необходима для нормальной одновременной реализации трёх (а в будущем, возможно, и большего числа) бекендов
Я всё думаю на тему реализации контрактов, как в языке eiffel. Там описание контракта метода накладывает ограничение на всех наследников метода.Наследование контрактов -- это ещё нерешённый дизайновый вопрос (поэтому контракты на
open
функциях сейчас запрещены), и об этом говорить пока рано. Но вообще контракты записываются в kotlin metadata, неважно для исходных и сгенерённых деклараций, так что вряд ли с их поднятием возникнут какие-то проблемы
Второй пункт “Можно изменять в том числе комментарий - и, соответственно, документацию.” - при просмотре сторонней библиотеки я могу не видеть исходный код, а видеть только спецификацию метода и javadoc (или kotlindoc? Не знаю, как корректно назвать).
Например та же @Serializable может добавлять в комментарий к классу- какие поля сериализуются, или ещё что-то там.Хороший юзкейс, мы о таком не думали. Спасибо
а какие хорошие примеры именно нелокальной работы плагина?Во время исследования мы рассматривали следующий юзкейс (на основе, кажется, dagger'а) - есть некий пустой класс, объявленный пользователем (например, какая-нибудь
Factory
)
- есть множество других классов, которые помечены специальной аннотацией
- плагин генерит фабричные методы в этой Factory
для всех помеченных классовaleksey.tomin
12/14/2020, 7:57 AMЭто чинится синхронным версионированием плагина и библиотек или же gradle пагином, который сразу подключит нужные версииgradle-плагин это лишняя сущность - нужен ещё maven-плагин, IDEA-плагин, потом bazel-плагин- это некрасиво. Зависимость чего угодно на версию компилятора - это нормально. А плагины- это лишнее. Моё решение вообще не требует плагинов вообще.
Наследование контрактов -- это ещё нерешённый дизайновый вопросНу вот собственно я предлагаю решение 🙂 Хочется довести kotlin до идеала- и не хватает идей и forth и eiffel. Вот предлагаю 🙂 Хочется и делать- но как хобби это нереально- это только пока семьи нет можно написать forth-машину или кросс-компилятор долгими зимними вечерами…
Не будет, т.к. IDE часть сильно зависит на PSIДа, понятно - PSI для IDEA, IR для backend-компилятора, FIR для фронтенд. Я так понимаю, IDEA, как самый старый продукт, сложнее всего изменить и заменить PSI. на raw-FIR слишком дорого стоит.
Во время исследования мы рассматривали следующий юзкейс (на основе, кажется, dagger’а)Да, об этом я думал. Т.к. “декоратор” это объект класса, выполняемый внутри jvm компилятора, то там может быть либо некоторое хранилище (предоставляемое компилятором), либо просто
object
(хотя это путь в адъ утечек памяти).
Соответственно аннотация на классе будет добавлять ссылку на класс в это хранилище, а @Factory
- вытаскивать всё и создавать нужное.
Появляются два вопроса:
• как сделать так, чтобы фактори выполнилась строго после всех остальных классов.
• что тут может показать IDEA.
Ответ на первый вопрос - можно на (имя условное) @Service
добавить @Factory
в список зависимых.dmitriy.novozhilov
12/14/2020, 8:13 AMМоё решение вообще не требует плагинов вообще.Но при этом оно тащит весь компилятор на compile class path. Либо же порождает новый тип зависимости для билд систем, что, опять же, порождает плагины для них
Ну вот собственно я предлагаю решениеМне приятен ваш энтузиазм, но проблема с контрактами лежит в несколько другой плоскости: наследование контрактов и возможность декларировать свои контракты в плагинах -- это две совершенно разные вещи, которые друг на друга никак не зависят (для второго у нас даже есть парочка рабочих прототипов)
как сделать так, чтобы фактори выполнилась строго после всех остальных классов.Есть ещё важный момент, что работу плагинов необходимо уложить в текущую архитектуру FIR'а, которая в свою очередь состоит из n-го количества проходов (фаз) по всему миру. И при этом соблюдается контракт, что к моменту фазы
n
все декларации уже порезолвлены до фазы n-1
Собственно, сами фазы:
1. Raw FIR (его построение)
2. резолв импортов во всех файлах
3. резолв супертипов всех классов
4. резолв явно указаных типов на нелокальных декларациях (функции, проперти, конструкторы)
5. резолв статусов всех деклараций (visibility, modality, модификаторы)
6. резолв контрактов
7. резолв тел функций/пропертей без явно указанного return типа
8. резолв всех остальных тел
И вот в эту модель плагины надо впихивать очень аккуратно, т.к. если плагин генерит какой-то новый класс, то он должен это делать в три захода
- сначала сгенерить саму декларацию класса перед фазой 2
- потом догенерить методы перед фазой 5 (т.к. от этого класса может наследоваться пользовательский код, что повлияет на резолв overrid'ов)
- после чего сгенерить тела методов после фазы 8 или уже в IR'еalex009
12/14/2020, 1:10 PMdmitriy.novozhilov
12/14/2020, 1:14 PMaleksey.tomin
12/14/2020, 2:17 PMНо при этом оно тащит весь компилятор на compile class path.Только
org.jetbrains.kotlin.psi
по сути своей.
На вход методу отдаётся текущее дерево класса- он там что-то ковыряет и отдаёт дальше.
Под “psi” я понимаю его вместе с map’ой (ссылаясь на то же видео).
наследование контрактов и возможность декларировать свои контракты в плагинах…А, кстати, я пропустил появление https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.contracts/ - надо почитать внимательнее...
Есть ещё важный момент, что работу плагинов необходимо уложить в текущую архитектуру FIR’а, которая в свою очередь состоит из n-го количества проходов (фаз) по всему миру.Я собственно хочу вклинится сразу после генерации PSI - максимально близко к исходному коду. Кстати, а есть ли описание этих 8 шагов FIR’а в доступной извне документации?
dmitriy.novozhilov
12/14/2020, 3:18 PMТолько org.jetbrains.kotlin.psi по сути своей.
На вход методу отдаётся текущее дерево класса- он там что-то ковыряет и отдаёт дальше.
Под “psi” я понимаю его вместе с map’ой (ссылаясь на то же видео).
BindingContext
(мапа) притащит за собой всё остальное
https://github.com/JetBrains/kotlin/blob/master/compiler/frontend/src/org/jetbrains/kotlin/resolve/BindingContext.java
Плюс на FIR это никак не ляжет, а делать compiler plugin API для текущего фронтенда мы не планируем
Я собственно хочу вклинится сразу после генерации PSI - максимально близко к исходному коду.На данном этапе никакой информации от компилятора не будет. Похожую идею реализовали в #arrow-meta
Кстати, а есть ли описание этих 8 шагов FIR’а в доступной извне документации?У нас по компилятору в целом никакой документации нету, ни внутренней, ни общей
aleksey.tomin
12/14/2020, 3:27 PMПонятно, спасибо! Я так понимаю, плагин с IR меньше всего тащит?(мапа) притащит за собой всё остальноеBindingContext
Плюс на FIR это никак не ляжет, а делать compiler plugin API для текущего фронтенда мы не планируемПонятно, что старый компилятор +- заморожен. Но в новом же тоже будет PSI?
У нас по компилятору в целом никакой документации нету, ни внутренней, ни общей (edited)Т.е. то интервью это максимум? Может ещё где-то были интересные беседы?
Если хотите, могу показать entry point’ы в коде, по которым можно начать изучать компиляторный пайплайнДа, спасибо, пойду читать...
Похожую идею реализовали в #arrow-metaО как. Минимум надо изучить эту штуку, спасибо!
dmitriy.novozhilov
12/14/2020, 3:31 PMПонятно, спасибо!
Я так понимаю, плагин с IR меньше всего тащит?Всё также немало. Но при разделении плагина на, собственно, плагин и библиотеку с аннотациями пользователю на classpath попадает только последняя, которая ни на что не завсит
Но в новом же тоже будет PSI?Не обязательно. У нас есть режим компиляции с парсером, который производит не PSI, а его упрощённое представление (это ускоряет парсинг примерно в два раза, если я правильно помню)
Т.е. то интервью это максимум? Может ещё где-то были интересные беседы?На недавнем джокере Семён выступал с более подробным докладом на эту тему, но я не уверен, что его запись есть где-то в открытом доступе
aleksey.tomin
12/16/2020, 3:22 PMFirElement
и KtFile
внезапно тащат за собой библиотеки IDEA. Причём попытка получить исходник библиотеки приводит к ошибке
Could not find kotlin.build:intellij-core:202.7660.26
(изучаю мастер обновлённый на этой неделе).dmitriy.novozhilov
12/16/2020, 4:37 PM~/.gradle/kotlin-build-dependencies/repo/kotlin.build/sources/intellij-core:202.7660.26-sources.jar
aleksey.tomin
12/30/2020, 3:40 PMif -> when
решает.
Не знаю даже, можно ли первоначальную задумку реализовать, пока не пройтись по PSI “огнём и мечом“.
Может за праздники что-то и придёт в голову.data class
, serializer
и многих других вещей. При этом такой “локальный” плагин должен работать быстрее и, самое главное, отлично ладит с инкрементальной компиляцией.
3. Сейчас плагин serializer
работает над байткодом (и аналогами для native/js) - лучше, если (все/большинство) плагины будут работать над платформенно-независимой частью дерева.
Есть ли какое-то описанное представление по новой схеме компиляторных плагинов? Сейчас видится предложение как “создание локальных плагинов/декораторов, основанных на аннотациях и работающих на PSI/FIR с созданием файлов исходного кода в generated
по соответствующей настройке“, но вдруг это уже есть в планах?
PS: а почему парсинг исходников и генерация PSI не рассматривается как часть фронтэнда?dmitriy.novozhilov
01/07/2021, 12:24 PMПри этом такой “локальный” плагин должен работать быстрее и, самое главное, отлично ладит с инкрементальной компиляцией.Мы дизайним новый API плагинов так, чтобы любой плагин дружил с IDE и IC из коробки
лучше, если (все/большинство) плагины будут работать над платформенно-независимой частью дереваСейчас уже есть extension point'ы, которые позволяют работать с backend IR вместо байткода на бекенде, и этой концепции мы и планируем придерживаться
Есть ли какое-то описанное представление по новой схеме компиляторных плагинов? Сейчас видится предложение как “создание локальных плагинов/декораторов, основанных на аннотациях и работающих на PSI/FIR с созданием файлов исходного кода в generated по соответствующей настройке“, но вдруг это уже есть в планах?Пока что мы работаем над низкоуровневым API, которое позволит генерировать новые декларации/ограниченно модифицировать существующие/добавлять новые проверки/модифицировать то, что в итоге будет генериться, и при этом чтобы это всё было совместимо с IC и IDE, как я писал выше (правда это несколько заблокировано тем, что IDE плагин на FIR сейчас в очень ранней стадии, а к IC мы только приступили). Уже поверх этого API можно будет выстраивать дополнительные уровни API, которые позволят легко обрабатывать описанные вами сценарии
PS: а почему парсинг исходников и генерация PSI не рассматривается как часть фронтэнда?Т.к. компилятор изначально писался так, чтобы его можно было использовать в IDE, то фронтенд на вход принимал уже готовый PSI, который был заранее построен идеей. Плюс сейчас парсер выносится в совсем отдельную компоненту, и для одного и того же фронтенда (FIR) можно использовать разные парсеры, которые порождают разное промежуточное представление (сейчас мы используем парсеры в PSI и LightTree, но в будущем никто не помешает энтузиастам добавить что-нибудь своё)
aleksey.tomin
01/07/2021, 12:39 PM