12. Расширение и использование GHC как библиотеки
GHC предоставляет своим пользователям доступ к внутренним API через встроенный пакет ghc. Это позволяет создавать программы, использующие весь компилятор GHC для аналитики или программной компиляции кода Haskell. Кроме того, GHC позволяет загружать плагины компилятора во время компиляции — модули, которым разрешено просматривать и изменять внутреннее промежуточное представление GHC, Core. Плагины подходят для таких задач, как экспериментальные оптимизации или анализ, и предоставляют меньший порог входа в разработку компиляторов для многих распространенных случаев.
Кроме того, GHC предлагает механизм лёгких аннотаций, которые можно использовать для аннотирования исходного кода метаданными, которые можно затем просмотреть с помощью API компилятора или плагина компилятора.
12.1. Аннотации исходного кода
Аннотации — это небольшие директивы, которые позволяют прикреплять данные к идентификаторам в исходном коде, сохраняемые при компиляции. Эти данные можно затем просматривать и использовать при использовании GHC как библиотеки или написании плагина компилятора.
12.1.1. Аннотация значений
Любое выражение, имеющее как Typeable , так и Data экземпляры, может быть прикреплено к связыванию значения верхнего уровня с помощью директивы ANN. В частности, это означает, что вы можете использовать ANN для аннотации конструкторов данных (например, Just) так же, как и обычных значений (например, take). В качестве примера, чтобы аннотировать функцию foo с аннотацией Just "Hello", сделайте следующее:
{-# ANN foo (Just "Hello") #-}
foo = ...
Применяется ряд ограничений к использованию аннотаций:
- Связующая переменная, которая аннотируется, должна быть на верхнем уровне (т. е. без вложенных связующих переменных)
- Связующая переменная, которая аннотируется, должна быть объявлена в текущем модуле
- Выражение, которое вы аннотируете, должно иметь тип с
TypeableиDataэкземплярами -
Применяются ограничения стадии Template Haskell для выражения, которое аннотируется, так, например, вы не можете запустить функцию из модуля, который компилируется.
Точнее, аннотация
{-# ANN x e #-}корректна, если и только если$(e)была бы корректной (не учитывая обычные ограничения типов синтаксиса вставки и обычное ограничение вставки внутри вставки —$([|1|])подходит в качестве аннотации, хотя и избыточно).
Если вы считаете, что какие-либо из этих ограничений слишком обременительны, обратитесь в команду GHC.
Однако, помимо этих ограничений, разрешается много вещей, включая выражения, которые не полностью вычислены! Выражения аннотаций будут вычислены компилятором так же, как и вставки Template Haskell. Таким образом, эта аннотация допустима:
{-# ANN f SillyAnnotation { foo = (id 10) + $([| 20 |]), bar = 'f } #-}
f = ...
12.1.2. Аннотация типов
Вы можете аннотировать типы с помощью директивы ANN , используя ключевое слово type. Например:
{-# ANN type Foo (Just "A `Maybe String' annotation") #-}
data Foo = ...
12.1.3. Аннотация модулей
Вы можете аннотировать модули с помощью директивы ANN , используя ключевое слово module. Например:
{-# ANN module (Just "A `Maybe String' annotation") #-}
12.2. Использование GHC как библиотеки
Пакет ghc предоставляет пользователям большую часть переднего плана GHC и, таким образом, позволяет создавать программы, использующие его. Эта библиотека фактически является той же самой, которая используется внутренним драйвером компиляции переднего плана GHC, и позволяет создавать инструменты, которые программно компилируют исходный код и проверяют его. Такая функциональность полезна для создания таких инструментов, как IDE или инструменты рефакторинга. В качестве простого примера, вот программа, которая компилирует модуль, очень похожая на то, что делает ghc по умолчанию, когда вызывается:
import GHC
import GHC.Paths ( libdir )
import DynFlags ( defaultFatalMessager, defaultFlushOut )
main =
defaultErrorHandler defaultFatalMessager defaultFlushOut $ do
runGhc (Just libdir) $ do
dflags <- getSessionDynFlags
setSessionDynFlags dflags
target <- guessTarget "test_main.hs" Nothing
setTargets [target]
load LoadAllTargets
Аргумент для runGhc немного сложен. GHC нуждается в нём, чтобы найти свои библиотеки, поэтому аргумент должен ссылаться на каталог, который выводится ghc --print-libdir для той же версии GHC, с которой компилируется программа. Поэтому выше мы используем пакет ghc-paths, который предоставляет это за нас.
Компиляция приводит к результату:
$ cat test_main.hs main = putStrLn "hi" $ ghc -package ghc simple_ghc_api.hs [1 of 1] Compiling Main ( simple_ghc_api.hs, simple_ghc_api.o ) Linking simple_ghc_api ... $ ./simple_ghc_api $ ./test_main hi $
Для получения дополнительной информации об использовании API, а также дополнительных примеров и ссылок, обратитесь к этой странице вики Haskell.org.
12.3. Плагины компилятора
GHC имеет возможность загружать плагины компилятора во время компиляции. Эта функция похожа на ту, что предоставляет GCC, и позволяет пользователям писать плагины, которые могут изменять поведение решателя ограничений, просматривать и изменять конвейер компиляции, а также преобразовывать и просматривать промежуточный язык GHC, Core. Плагины подходят для экспериментального анализа или оптимизации и не требуют изменений в исходном коде GHC для использования.
Плагины не могут оптимизировать/просматривать C--, а также реализовывать такие вещи, как изменения парсера/переднего плана, как в GCC, за исключением ограниченных изменений решателя ограничений. Если вы считаете, что какие-либо из этих ограничений слишком обременительны, обратитесь в команду GHC.
Плагины не работают с -fexternal-interpreter. Если вам нужно запускать плагины с -fexternal-interpreter, сообщите об этом разработчикам GHC в #14335.
12.3.1. Использование плагинов компилятора
Плагины могут быть добавлены в командной строке с опцией -fplugin=⟨module⟩, где ⟨module⟩ — это модуль в зарегистрированном пакете, который экспортирует плагин. Аргументы могут быть переданы плагинам с помощью опции -fplugin-opt=⟨module⟩:⟨args⟩. Список включённых плагинов можно сбросить с помощью опции -fclear-plugins.
-
-fplugin=⟨module⟩ -
Загрузить плагин из указанного модуля. Модуль должен быть частью пакета, зарегистрированного в базе данных пакетов GHC.
-
-fplugin-opt=⟨module⟩:⟨args⟩ -
Передать аргументы модулю плагина; модуль должен быть указан с помощью
-fplugin=⟨module⟩.
-
-fplugin-trustworthy -
По умолчанию, когда модуль компилируется с плагинами, он будет помечен как небезопасный. С этой флагом все плагины считаются надёжными, и вычисление безопасности больше не будет влиять.
-
-fclear-plugins -
Очистить список плагинов, ранее указанных с помощью
-fplugin. Это полезно в GHCi, где простое удаление опций-fpluginиз командной строки невозможно. Вместо этого можно использовать:set -fclear-plugins.
В качестве примера, для загрузки плагина, экспортированного Foo.Plugin в пакете foo-ghc-plugin, и передачи ему параметра «baz», мы вызовем GHC так:
$ ghc -fplugin Foo.Plugin -fplugin-opt Foo.Plugin:baz Test.hs [1 of 1] Compiling Main ( Test.hs, Test.o ) Loading package ghc-prim ... linking ... done. Loading package integer-gmp ... linking ... done. Loading package base ... linking ... done. Loading package ffi-1.0 ... linking ... done. Loading package foo-ghc-plugin-0.1 ... linking ... done. ... Linking Test ... $
В качестве альтернативы, плагины для Core могут быть указаны с помощью Template Haskell.
addCorePlugin "Foo.Plugin"
Это вставляет плагин как проход Core-to-Core. В отличие от -fplugin=(module), модуль плагина не может находиться в том же пакете, что и модуль, вызывающий Language.Haskell.TH.Syntax.addCorePlugin. Таким образом, реализация может ожидать, что плагин будет собран к моменту его необходимости.
Модули плагинов существуют в отдельном пространстве имён от пространства имён пользователя. По умолчанию эти два пространства имён совпадают; однако, существуют несколько опций командной строки, которые управляют конкретно пакетами плагинов:
-
-plugin-package ⟨pkg⟩ -
Эта опция заставляет установленный пакет ⟨pkg⟩ быть доступным для плагинов, таких как
-fplugin=⟨module⟩. Пакет ⟨pkg⟩ может быть указан полностью с номером версии (например,network-1.0) или номер версии может быть опущен, если установлен только один вариант пакета. Если установлено несколько версий ⟨pkg⟩ и-hide-all-plugin-packagesне указана, то все остальные версии станут скрытыми.-plugin-package ⟨pkg⟩поддерживает сжатие и переименование, описанные в Уменьшение и переименование модулей.В отличие от
-package ⟨pkg⟩, эта опция НЕ вызывает связывание пакета ⟨pkg⟩ в результирующий исполняемый файл или общий объект.
-
-plugin-package-id ⟨pkg-id⟩ -
Предоставляет доступ к пакету в пространстве имён плагинов, как и
-plugin-package ⟨pkg⟩, но пакет называется по его установленному идентификатору пакета, а не по имени. Это более надёжный способ именования пакетов и может использоваться для выбора пакетов, которые в противном случае были бы затенены. Cabal передаёт флаги-plugin-package-id ⟨pkg-id⟩в GHC.-plugin-package-id ⟨pkg-id⟩поддерживает сжатие и переименование, описанные в Уменьшение и переименование модулей.
-
-hide-all-plugin-packages -
По умолчанию все экспортируемые пакеты в обычном пространстве импорта исходного кода также доступны для плагинов. Это приводит к тому, что эти пакеты по умолчанию скрыты. Если вы используете этот флаг, то любые пакеты с необходимыми вам плагинами необходимо явно экспортировать с помощью
-plugin-package ⟨pkg⟩опций.
В настоящее время единственный способ указать зависимость от плагина в Cabal — поместить её в build-depends (который использует обычный -package-id ⟨unit-id⟩ флаг); однако в будущем будет отдельное поле для указания зависимостей от плагинов.
12.3.2. Написание плагинов компилятора
Плагины — это модули, которые экспортируют по меньшей мере один идентификатор, plugin, типа GhcPlugins.Plugin. Все плагины должны import GhcPlugins , поскольку он определяет интерфейс с конвейером компиляции.
Plugin фактически содержит функцию, которая устанавливает этап компиляции в конвейер компилятора. По умолчанию существует пустой плагин, который ничего не делает, GhcPlugins.defaultPlugin, который вы должны переопределить с помощью синтаксиса записей, чтобы указать свою функцию установки. Поскольку точные поля типа Plugin могут быть изменены, это лучший способ гарантировать, что ваши плагины будут продолжать работать в будущем с минимальным воздействием на интерфейс.
Plugin экспортирует поле installCoreToDos, которое является функцией типа [CommandLineOption] -> [CoreToDo] -> CoreM [CoreToDo]. CommandLineOption фактически просто String, а CoreToDo — в основном функция типа Core -> Core. CoreToDo даёт вашему этапу имя и выполняет его над каждым скомпилированным модулем при вызове GHC.
В качестве простого примера здесь представлен простой плагин, который просто ничего не делает и просто возвращает исходный конвейер компиляции, без изменений, и говорит «Привет»:
module DoNothing.Plugin (plugin) where
import GhcPlugins
plugin :: Plugin
plugin = defaultPlugin {
installCoreToDos = install
}
install :: [CommandLineOption] -> [CoreToDo] -> CoreM [CoreToDo]
install _ todo = do
putMsgS "Hello!"
return todo
При условии, что вы скомпилировали этот плагин и зарегистрировали его в пакете (например, с помощью cabal), вы можете использовать его, просто указав -fplugin=DoNothing.Plugin в командной строке, и во время компиляции вы должны увидеть, как GHC говорит «Привет».
Поддержка выполнения нескольких плагинов также предусмотрена путём передачи нескольких -fplugin=... опций. GHC загрузит плагины в том порядке, в котором они указаны в командной строке, и, при необходимости, комбинирует их эффекты в том же порядке. То есть, если у нас есть два плагина Core, Plugin1 и Plugin2, каждый определяющий функцию install, как та, что выше, то GHC сначала выполнит Plugin1.install на стандартном [CoreToDo], возьмёт результат и передаст его Plugin2.install. -fplugin=Plugin1 -fplugin=Plugin2 обновит конвейер Core, применив Plugin1.install opts1 >=> Plugin2.install opts2 (где opts1 и opts2 — опции, переданные каждому плагину с помощью -fplugin-opt=...). Это не относится конкретно к плагинам Core, но справедливо для всех типов плагинов, которые могут быть каким-либо образом объединены или упорядочены: первый плагин, появившийся в командной строке GHC, всегда действует первым.
12.3.3. Плагины Core более подробно
CoreToDo — это фактически тип данных, описывающий все виды оптимизационных этапов, которые GHC выполняет над Core. Существуют этапы упрощения, CSE и т. д. Существует специальный случай для плагинов, CoreDoPluginPass :: String -> PluginPass -> CoreToDo, который следует всегда использовать при вставке собственного этапа в конвейер. Первый параметр — имя плагина, второй — этап, который вы хотите вставить.
CoreM — это монада, внутри которой живут и работают все оптимизации Core.
Функция установки плагина (install в приведенном выше примере) принимает список CoreToDo и возвращает список CoreToDo. Перед началом компиляции модулей GHC перечисляет все необходимые плагины, которые вы указали для загрузки, и выполняет все их функции установки, изначально на списке этапов, которые сам GHC определяет. После выполнения этого для каждого плагина окончательный список этапов передаётся оптимизатору и выполняется просто по порядку.
Следует быть осторожными с вашей функцией установки, потому что список этапов, который вы возвращаете, не проверяется и не проверяется дважды GHC на момент написания этой статьи. Функция установки, подобная следующей:
install :: [CommandLineOption] -> [CoreToDo] -> CoreM [CoreToDo] install _ _ = return []
безусловно допустима, но также безусловно нежелательна.
12.3.3.1. Работа с связываниями
В предыдущем разделе мы увидели, что помимо имени, CoreDoPluginPass принимает этап типа PluginPass. PluginPass — синоним для (ModGuts -> CoreM ModGuts). ModGuts — тип, представляющий один модуль, который компилируется GHC в данный момент.
ModGuts содержит все глобальные связывания модуля, которые мы можем просмотреть. Эти связывания имеют тип CoreBind и фактически представляют связывание имени с телом кода. Глобальные связывания модуля являются частью ModGuts в поле mg_binds. Реализация этапа, который изменяет глобальные связывания, просто требует итерации по этому полю и возврата нового ModGuts с обновлённым полем mg_binds. Поскольку это такой распространённый случай, предоставлена функция под названием bindsOnlyPass, которая поднимает функцию типа ([CoreBind] -> CoreM [CoreBind]) к типу (ModGuts -> CoreM ModGuts).
Продолжая наш пример из предыдущего раздела, мы можем написать простой плагин, который просто выводит имя всех нерекурсивных связываний в модуле, который он компилирует:
module SayNames.Plugin (plugin) where
import GhcPlugins
plugin :: Plugin
plugin = defaultPlugin {
installCoreToDos = install
}
install :: [CommandLineOption] -> [CoreToDo] -> CoreM [CoreToDo]
install _ todo = do
return (CoreDoPluginPass "Say name" pass : todo)
pass :: ModGuts -> CoreM ModGuts
pass guts = do dflags <- getDynFlags
bindsOnlyPass (mapM (printBind dflags)) guts
where printBind :: DynFlags -> CoreBind -> CoreM CoreBind
printBind dflags bndr@(NonRec b _) = do
putMsgS $ "Non-recursive binding named " ++ showSDoc dflags (ppr b)
return bndr
printBind _ bndr = return bndr
12.3.3.2. Использование аннотаций
Ранее мы обсуждали аннотации прагм (Исходные аннотации), которые, как мы упоминали, могут быть использованы для предоставления дополнительного руководства или информации плагинам компилятора. Аннотации модуля могут быть получены плагином, но вы должны пройти через модули ModGuts , чтобы получить их. Поскольку аннотации могут быть произвольными экземплярами Data и Typeable, вам нужно указать аннотацию типа, определяющую правильный тип данных для извлечения из файла интерфейса, и вам нужно убедиться, что тип аннотации, используемый вашими пользователями, совпадает с типом аннотации, который использует ваш плагин. По этой причине мы рекомендуем распространять аннотации как часть пакета, который также предоставляет плагины компилятора, если это возможно.
Чтобы получить аннотации отдельного связывания, вы можете использовать getAnnotations и указать соответствующий тип. Вот пример, который выведет имя любого глобального нерекурсивного связывания с аннотацией SomeAnn:
{-# LANGUAGE DeriveDataTypeable #-}
module SayAnnNames.Plugin (plugin, SomeAnn(..)) where
import GhcPlugins
import Control.Monad (unless)
import Data.Data
data SomeAnn = SomeAnn deriving Data
plugin :: Plugin
plugin = defaultPlugin {
installCoreToDos = install
}
install :: [CommandLineOption] -> [CoreToDo] -> CoreM [CoreToDo]
install _ todo = do
return (CoreDoPluginPass "Say name" pass : todo)
pass :: ModGuts -> CoreM ModGuts
pass g = do
dflags <- getDynFlags
mapM_ (printAnn dflags g) (mg_binds g) >> return g
where printAnn :: DynFlags -> ModGuts -> CoreBind -> CoreM CoreBind
printAnn dflags guts bndr@(NonRec b _) = do
anns <- annotationsOn guts b :: CoreM [SomeAnn]
unless (null anns) $ putMsgS $ "Annotated binding found: " ++ showSDoc dflags (ppr b)
return bndr
printAnn _ _ bndr = return bndr
annotationsOn :: Data a => ModGuts -> CoreBndr -> CoreM [a]
annotationsOn guts bndr = do
anns <- getAnnotations deserializeWithData guts
return $ lookupWithDefaultUFM anns [] (varUnique bndr)
Дополнительную информацию о том, как использовать внутренние API и т. д., см. в документации по API GHC.
12.3.4. Плагины проверки типов
В дополнение к плагинам Core, GHC имеет экспериментальную поддержку плагинов проверки типов, которые позволяют изменять поведение решателя ограничений. Например, они позволяют связать компилятор с решателем SMT, чтобы поддержать более богатую теорию выражений арифметики на уровне типов, чем та, что встроенна в GHC (см. Вычисления с натуральными числами на уровне типов).
Тип Plugin имеет поле tcPlugin типа [CommandLineOption] -> Maybe TcPlugin, где тип TcPlugin определён следующим образом:
data TcPlugin = forall s . TcPlugin
{ tcPluginInit :: TcPluginM s
, tcPluginSolve :: s -> TcPluginSolver
, tcPluginStop :: s -> TcPluginM ()
}
type TcPluginSolver = [Ct] -> [Ct] -> [Ct] -> TcPluginM TcPluginResult
data TcPluginResult = TcPluginContradiction [Ct] | TcPluginOk [(EvTerm,Ct)] [Ct]
(Подробности этой структуры могут измениться по мере приобретения нами большего опыта написания плагинов проверки типов. Не следует полагать, что она останется стабильной между выпусками GHC.)
Основная идея заключается в следующем:
- При проверке типа модуля GHC вызовет
tcPluginInitодин раз перед началом решения ограничений. Это позволяет плагину искать информацию в контексте, инициализировать переменное состояние или открыть соединение с внешним процессом (например, с внешним решателем SMT). Плагин может возвратить результат любого типа, который он хочет, и этот результат будет передан другим двум полям. - Во время решения ограничений GHC многократно вызовет
tcPluginSolve. Эта функция получает текущий набор ограничений и должна вернутьTcPluginResult, указывающий, найдено ли противоречие или достигнут прогресс. Если решатель плагина сделал прогресс, GHC перезапустит конвейер решения ограничений, циклично повторяя, пока не будет достигнут фиксированная точка. - Наконец, GHC вызовет
tcPluginStopпосле завершения решения ограничений, позволяя плагину освободить любые выделенные ресурсы (например, завершить процесс решателя SMT).
Код плагина выполняется в монаде TcPluginM, которая предоставляет ограниченный интерфейс к функциональности API GHC, которая имеет отношение к плагинам проверки типов, включая IO и чтение окружения. Если вам нужна функциональность, которая не представлена в модуле TcPluginM, вы можете использовать unsafeTcPluginTcM :: TcM a -> TcPluginM a, но вам рекомендуется обратиться в команду GHC, чтобы предложить дополнения к интерфейсу. Обратите внимание, что TcPluginM может выполнять произвольные операции ввода/вывода с помощью tcPluginIO :: IO a -> TcPluginM a, хотя необходимо проявлять осторожность с побочными эффектами (особенно в tcPluginSolve). В целом, автор плагина должен убедиться, что все выполняемые им операции ввода/вывода безопасны.
12.3.4.1. Решение ограничений с помощью плагинов
Ключевой компонент плагина проверки типов — это функция типа TcPluginSolver, например:
solve :: [Ct] -> [Ct] -> [Ct] -> TcPluginM TcPluginResult solve givens deriveds wanteds = ...
Эта функция будет вызываться в двух точках процесса решения ограничений: после упрощения заданных ограничений и после расслоения желаемых ограничений. Два этапа можно отличить, потому что deriveds и wanteds будут пустыми в первом случае. В каждом случае плагин должен либо
- вернуть
TcPluginContradictionсо списком недопустимых ограничений (который должен быть подмножеством переданных ограничений), чтобы их можно было преобразовать в ошибки; или - вернуть
TcPluginOkсо списками решённых и новых ограничений (первые должны быть подмножеством переданных ограничений и должны быть снабжены соответствующими терминами доказательства).
Если плагин не может добиться прогресса, он должен вернуть TcPluginOk [] []. В противном случае, если существуют новые ограничения, основной решатель ограничений будет вызван для их упрощения, затем плагин будет вызван снова. Плагин отвечает за обеспечение того, что этот процесс в конечном итоге завершится.
Плагины предоставляются со всеми доступными ограничениями (включая равенства и ограничения типов), но для них легко отбросить те, которые не относятся к их области, потому что им нужно вернуть только те ограничения, для которых они добились прогресса (либо путем решения, либо путем противоречия).
Ограничения, которые были решены плагином, должны быть предоставлены с доказательством в виде EvTerm типа ограничения. Это доказательство игнорируется для заданных и выведенных ограничений, которые GHC «решает», просто отбрасывая их; как правило, это используется, когда они не содержат информации (например, рефлексивные уравнения). Для желаемых ограничений доказательство станет частью ядра термина, который генерируется после проверки типов, и может быть проверено -dcore-lint. Плагин может создавать аксиомы равенства для использования в терминах доказательств, но GHC не проверяет их непротиворечивость, а несовместимые наборы аксиом могут привести к сбою сегментации или другим проблемам во время выполнения.
12.3.5. Плагины исходного кода
Помимо плагинов ядра и проверки типов, вы можете установить плагины, которые могут получить доступ к различным представлениям исходного кода. Основная цель этих плагинов — упростить реализацию инструментов разработки.
Существует несколько различных точек входа, которые вы можете использовать для определения плагинов, которые получают доступ к представлениям. Все эти поля получают список CommandLineOption строк, которые передаются компилятору с помощью флагов -fplugin-opt=⟨module⟩:⟨args⟩.
plugin :: Plugin
plugin = defaultPlugin {
parsedResultAction = parsed
, typeCheckResultAction = typechecked
, spliceRunAction = spliceRun
, interfaceLoadAction = interfaceLoad
, renamedResultAction = renamed
}
12.3.5.1. Разбор представления
Когда вы хотите определить плагин, который использует синтаксическое дерево исходного кода, вы хотите переопределить поле parsedResultAction. Этот пункт доступа позволяет вам получить доступ к информации о лексемах и комментариях в исходном коде, а также к исходному синтаксическому дереву компилируемого модуля.
parsed :: [CommandLineOption] -> ModSummary -> HsParsedModule
-> Hsc HsParsedModule
В ModSummary содержится полезная метаинформация о компилируемом модуле. HsParsedModule содержит лексематическую и синтаксическую информацию, о которой мы упоминали ранее. Возвращаемый результат будет изменять результат разбора. Если вы не хотите изменять результат, просто верните HsParsedModule , который вы получили в качестве аргумента.
12.3.5.2. Проверенное по типу представление
Если вы хотите определить плагин, которому нужна семантическая информация об исходном коде, используйте поле typeCheckResultAction. Например, если ваш плагин должен определить, ссылаются ли два имени на одно и то же определение, или ему нужно проверить тип функции, он использует семантическую информацию. В этом случае вам нужно получить доступ к переименованной или проверенной по типу версии синтаксического дерева с помощью typeCheckResultAction или renamedResultAction.
typechecked :: [CommandLineOption] -> ModSummary -> TcGblEnv -> TcM TcGblEnv renamed :: [CommandLineOption] -> TcGblEnv -> HsGroup GhcRn -> TcM (TcGblEnv, HsGroup GhcRn)
Переопределяя поле renamedResultAction, мы можем изменить каждый HsGroup после его переименования. Исходный файл разделен на группы в зависимости от расположения вставки шаблона Haskell, поэтому содержимое этих групп может быть неинтуитивным. Чтобы сохранить все переименованное AST для проверки в конце проверки типов, вы можете установить renamedResultAction в keepRenamedSource, который предоставляется модулем Plugins. Это важно, потому что некоторые части переименованного синтаксического дерева (например, импорты) не находятся в проверенном по типу.
12.3.5.3. Вычисленный код
Когда компилятор проверяет исходный код, вставки шаблона Haskell и квазицитирование шаблона Haskell будут заменены фрагментами синтаксического дерева, сгенерированными из них. Однако для инструментов, работающих с исходным кодом, код генератора обычно более интересен, чем сгенерированный код. По этой причине мы включили spliceRunAction. Это поле вызывается для каждого выражения перед их вычислением. Вход проверяется по типу, поэтому семантическая информация доступна для этих фрагментов синтаксического дерева. Если вы вернете другое выражение, вы можете изменить сгенерированный код.
spliceRun :: [CommandLineOption] -> LHsExpr GhcTc -> TcM (LHsExpr GhcTc)
Однако обратите внимание, что сгенерированные определения все еще находятся на входе typeCheckResultAction. Если вы не позаботитесь о фильтрации ввода, проверенного по типу, поведение вашего инструмента может быть несогласованным.
12.3.5.4. Файлы интерфейса
Иногда при написании инструмента знания исходного кода недостаточно, вам также необходимо знать подробности о модулях, которые вы импортируете. В этом случае мы рекомендуем использовать interfaceLoadAction. Это будет вызываться каждый раз, когда код уже скомпилированного модуля загружается. Оно будет вызываться для модулей из установленных пакетов и даже для модулей, установленных с GHC. Оно НЕ будет вызываться для ваших собственных модулей.
interfaceLoad :: forall lcl . [CommandLineOption] -> ModIface
-> IfM lcl ModIface
В типе данных ModIface вы найдете много полезной информации, включая экспортированные определения и экземпляры типов классов.
12.3.5.5. Пример плагина исходного кода
В этом примере мы проверяем все доступные детали скомпилированного исходного кода. Мы не изменяем ни одного из представлений, но выводим детали в стандартный вывод. В выходных данных также будет красочный вывод синтаксического дерева разбора, переименования и проверки типа, а также вычисленные вставки и квазицитаты. Будут также отображаться имена загружаемых интерфейсов.
module SourcePlugin where
import Control.Monad.IO.Class
import DynFlags (getDynFlags)
import Plugins
import HscTypes
import TcRnTypes
import GHC.Hs.Extension
import GHC.Hs.Decls
import GHC.Hs.Expr
import GHC.Hs.ImpExp
import Avail
import Outputable
import GHC.Hs.Doc
plugin :: Plugin
plugin = defaultPlugin
{ parsedResultAction = parsedPlugin
, renamedResultAction = renamedAction
, typeCheckResultAction = typecheckPlugin
, spliceRunAction = metaPlugin
, interfaceLoadAction = interfaceLoadPlugin
}
parsedPlugin :: [CommandLineOption] -> ModSummary -> HsParsedModule -> Hsc HsParsedModule
parsedPlugin _ _ pm
= do dflags <- getDynFlags
liftIO $ putStrLn $ "parsePlugin: \n" ++ (showSDoc dflags $ ppr $ hpm_module pm)
return pm
renamedAction :: [CommandLineOption] -> TcGblEnv -> HsGroup GhcRn -> TcM (TcGblEnv, HsGroup GhcRn)
renamedAction _ tc gr = do
dflags <- getDynFlags
liftIO $ putStrLn $ "typeCheckPlugin (rn): " ++ (showSDoc dflags $ ppr gr)
return (tc, gr)
typecheckPlugin :: [CommandLineOption] -> ModSummary -> TcGblEnv -> TcM TcGblEnv
typecheckPlugin _ _ tc
= do dflags <- getDynFlags
liftIO $ putStrLn $ "typeCheckPlugin (rn): \n" ++ (showSDoc dflags $ ppr $ tcg_rn_decls tc)
liftIO $ putStrLn $ "typeCheckPlugin (tc): \n" ++ (showSDoc dflags $ ppr $ tcg_binds tc)
return tc
metaPlugin :: [CommandLineOption] -> LHsExpr GhcTc -> TcM (LHsExpr GhcTc)
metaPlugin _ meta
= do dflags <- getDynFlags
liftIO $ putStrLn $ "meta: " ++ (showSDoc dflags $ ppr meta)
return meta
interfaceLoadPlugin :: [CommandLineOption] -> ModIface -> IfM lcl ModIface
interfaceLoadPlugin _ iface
= do dflags <- getDynFlags
liftIO $ putStrLn $ "interface loaded: " ++ (showSDoc dflags $ ppr $ mi_module iface)
return iface
Когда вы компилируете простой модуль, содержащий вставку Template Haskell
{-# OPTIONS_GHC -fplugin SourcePlugin #-}
{-# LANGUAGE TemplateHaskell #-}
module A where
a = ()
$(return [])
с флагами компилятора -fplugin SourcePlugin , он выдаст следующий результат:
parsePlugin:
module A where
a = ()
$(return [])
interface loaded: Prelude
interface loaded: GHC.Float
interface loaded: GHC.Base
interface loaded: Language.Haskell.TH.Lib.Internal
interface loaded: Language.Haskell.TH.Syntax
interface loaded: GHC.Types
meta: return []
interface loaded: GHC.Integer.Type
typeCheckPlugin (rn):
Just a = ()
typeCheckPlugin (tc):
{$trModule = Module (TrNameS "main"#) (TrNameS "A"#), a = ()}
12.3.6. Плагины подстановки дыр
Плагины подстановки дыр — это плагины, которые вызываются при генерации сообщения об ошибке типа дыры и позволяют получить доступ к информации о дыре с типом во время компиляции, а также позволяют настроить допустимые предложения подстановки дыр.
С помощью плагинов подстановки дыр вы можете расширить поведение допустимых предложений подстановки дыр, используя, например, Hoogle или другие внешние инструменты для поиска и/или синтеза допустимых подстановок дыр с той же информацией о дыре с типом, что и GHC.
Для определения плагинов подстановки дыр объединены две точки входа: плагин кандидатов и плагин подстановки, для модификации кандидатов на проверку и подстановок соответственно.
type CandPlugin = TypedHole -> [HoleFitCandidate] -> TcM [HoleFitCandidate]
type FitPlugin = TypedHole -> [HoleFit] -> TcM [HoleFit]
data HoleFitPlugin = HoleFitPlugin
{ candPlugin :: CandPlugin
-- ^ A plugin for modifying hole fit candidates before they're checked
, fitPlugin :: FitPlugin
-- ^ A plugin for modifying valid hole fits after they've been found.
}
Где TypedHole содержит всю информацию о дыре, доступную GHC при генерации ошибки.
data TypedHole = TyH { tyHRelevantCts :: Cts
-- ^ Any relevant Cts to the hole
, tyHImplics :: [Implication]
-- ^ The nested implications of the hole with the
-- innermost implication first.
, tyHCt :: Maybe Ct
-- ^ The hole constraint itself, if available.
}
HoleFitPlugins определяются следующим образом
plugin :: Plugin
plugin = defaultPlugin {
holeFitPlugin = (fmap . fmap) fromPureHFPlugin hfPlugin
}
hfPlugin :: [CommandLineOption] -> Maybe HoleFitPlugin
Где fromPureHFPlugin :: HoleFitPlugin -> HoleFitPluginR — это функция удобства, предоставляемая модулем TcHoleErrors, для определения плагинов, которым не требуется внутреннее состояние.
12.3.6.1. Состоятельные плагины подстановки дыр
HoleFitPlugins обернуты в HoleFitPluginR, который предоставляет TcRef для плагина для отслеживания внутреннего состояния и облегчения связи между плагином кандидатов и плагином подстановки.
-- | HoleFitPluginR adds a TcRef to hole fit plugins so that plugins can
-- track internal state. Note the existential quantification, ensuring that
-- the state cannot be modified from outside the plugin.
data HoleFitPluginR = forall s. HoleFitPluginR
{ hfPluginInit :: TcM (TcRef s)
-- ^ Initializes the TcRef to be passed to the plugin
, hfPluginRun :: TcRef s -> HoleFitPlugin
-- ^ The function defining the plugin itself
, hfPluginStop :: TcRef s -> TcM ()
-- ^ Cleanup of state, guaranteed to be called even on error
}
Плагин затем определяется путем предоставления значения для поля holeFitPlugin, функции, которая принимает CommandLineOption строки, передаваемые компилятору с помощью флагов -fplugin-opt=⟨module⟩:⟨args⟩, и возвращает HoleFitPluginR. Эта функция может использоваться для передачи CommandLineOption строк плагинам кандидатов и подстановок соответственно.
12.3.6.2. Пример плагина подстановки дыр
Следующие плагины позволяют пользователям ограничивать поиск допустимых подстановок дыр определенными модулями, сортировать подстановки дыр по месту их происхождения (в возрастающем или убывающем порядке), а также устанавливать ограничения на время, затраченное на поиск допустимых подстановок дыр, после чего новые поиски прерываются.
{-# LANGUAGE TypeApplications, RecordWildCards #-}
module HolePlugin where
import GhcPlugins hiding ((<>))
import TcHoleErrors
import Data.List (stripPrefix, sortOn)
import TcRnTypes
import TcRnMonad
import Data.Time (UTCTime, NominalDiffTime)
import qualified Data.Time as Time
import Text.Read
data HolePluginState = HPS { timeAlloted :: Maybe NominalDiffTime
, elapsedTime :: NominalDiffTime
, timeCurStarted :: UTCTime }
bumpElapsed :: NominalDiffTime -> HolePluginState -> HolePluginState
bumpElapsed ad (HPS a e t) = HPS a (e + ad) t
setAlloted :: Maybe NominalDiffTime -> HolePluginState -> HolePluginState
setAlloted a (HPS _ e t) = HPS a e t
setCurStarted :: UTCTime -> HolePluginState -> HolePluginState
setCurStarted nt (HPS a e _) = HPS a e nt
hpStartState :: HolePluginState
hpStartState = HPS Nothing zero undefined
where zero = fromInteger @NominalDiffTime 0
initPlugin :: [CommandLineOption] -> TcM (TcRef HolePluginState)
initPlugin [msecs] = newTcRef $ hpStartState { timeAlloted = alloted }
where
errMsg = "Invalid amount of milliseconds given to plugin: " <> show msecs
alloted = case readMaybe @Integer msecs of
Just millisecs -> Just $ fromInteger @NominalDiffTime millisecs / 1000
_ -> error errMsg
initPlugin _ = newTcRef hpStartState
fromModule :: HoleFitCandidate -> [String]
fromModule (GreHFCand gre) =
map (moduleNameString . importSpecModule) $ gre_imp gre
fromModule _ = []
toHoleFitCommand :: TypedHole -> String -> Maybe String
toHoleFitCommand TyH{tyHCt = Just (CHoleCan _ h)} str
= stripPrefix ("_" <> str) $ occNameString $ holeOcc h
toHoleFitCommand _ _ = Nothing
-- | This candidate plugin filters the candidates by module,
-- using the name of the hole as module to search in
modFilterTimeoutP :: [CommandLineOption] -> TcRef HolePluginState -> CandPlugin
modFilterTimeoutP _ ref hole cands = do
curTime <- liftIO Time.getCurrentTime
HPS {..} <- readTcRef ref
updTcRef ref (setCurStarted curTime)
return $ case timeAlloted of
-- If we're out of time we remove all the candidates. Then nothing is checked.
Just sofar | elapsedTime > sofar -> []
_ -> case toHoleFitCommand hole "only_" of
Just modName -> filter (inScopeVia modName) cands
_ -> cands
where inScopeVia modNameStr cand@(GreHFCand _) =
elem (toModName modNameStr) $ fromModule cand
inScopeVia _ _ = False
toModName = replace '_' '.'
replace :: Eq a => a -> a -> [a] -> [a]
replace _ _ [] = []
replace a b (x:xs) = (if x == a then b else x):replace a b xs
modSortP :: [CommandLineOption] -> TcRef HolePluginState -> FitPlugin
modSortP _ ref hole hfs = do
curTime <- liftIO Time.getCurrentTime
HPS {..} <- readTcRef ref
updTcRef ref $ bumpElapsed (Time.diffUTCTime curTime timeCurStarted)
return $ case timeAlloted of
-- If we're out of time, remove any candidates, so nothing is checked.
Just sofar | elapsedTime > sofar -> [RawHoleFit $ text msg]
_ -> case toHoleFitCommand hole "sort_by_mod" of
-- If only_ is on, the fits will all be from the same module.
Just ('_':'d':'e':'s':'c':_) -> reverse hfs
Just _ -> orderByModule hfs
_ -> hfs
where orderByModule :: [HoleFit] -> [HoleFit]
orderByModule = sortOn (fmap fromModule . mbHFCand)
mbHFCand :: HoleFit -> Maybe HoleFitCandidate
mbHFCand HoleFit {hfCand = c} = Just c
mbHFCand _ = Nothing
msg = hang (text "Error: The time ran out, and the search was aborted for this hole.")
7 $ text "Try again with a longer timeout."
plugin :: Plugin
plugin = defaultPlugin { holeFitPlugin = holeFitP, pluginRecompile = purePlugin}
holeFitP :: [CommandLineOption] -> Maybe HoleFitPluginR
holeFitP opts = Just (HoleFitPluginR initP pluginDef stopP)
where initP = initPlugin opts
stopP = const $ return ()
pluginDef ref = HoleFitPlugin { candPlugin = modFilterTimeoutP opts ref
, fitPlugin = modSortP opts ref }
Когда вы затем компилируете модуль, содержащий следующее
{-# OPTIONS -fplugin=HolePlugin
-fplugin-opt=HolePlugin:600
-funclutter-valid-hole-fits #-}
module Main where
import Prelude hiding (head, last)
import Data.List (head, last)
f, g, h, i, j :: [Int] -> Int
f = _too_long
j = _
i = _sort_by_mod_desc
g = _only_Data_List
h = _only_Prelude
main :: IO ()
main = return ()
Вывод выглядит следующим образом:
Main.hs:12:5: error:
• Found hole: _too_long :: [Int] -> Int
Or perhaps ‘_too_long’ is mis-spelled, or not in scope
• In the expression: _too_long
In an equation for ‘f’: f = _too_long
• Relevant bindings include
f :: [Int] -> Int (bound at Main.hs:12:1)
Valid hole fits include
Error: The time ran out, and the search was aborted for this hole.
Try again with a longer timeout.
|
12 | f = _too_long
| ^^^^^^^^^
Main.hs:13:5: error:
• Found hole: _ :: [Int] -> Int
• In the expression: _
In an equation for ‘j’: j = _
• Relevant bindings include
j :: [Int] -> Int (bound at Main.hs:13:1)
Valid hole fits include
j :: [Int] -> Int
f :: [Int] -> Int
g :: [Int] -> Int
h :: [Int] -> Int
i :: [Int] -> Int
head :: forall a. [a] -> a
(Some hole fits suppressed; use -fmax-valid-hole-fits=N or -fno-max-valid-hole-fits)
|
13 | j = _
| ^
Main.hs:14:5: error:
• Found hole: _sort_by_mod_desc :: [Int] -> Int
Or perhaps ‘_sort_by_mod_desc’ is mis-spelled, or not in scope
• In the expression: _sort_by_mod_desc
In an equation for ‘i’: i = _sort_by_mod_desc
• Relevant bindings include
i :: [Int] -> Int (bound at Main.hs:14:1)
Valid hole fits include
sum :: forall (t :: * -> *) a. (Foldable t, Num a) => t a -> a
product :: forall (t :: * -> *) a. (Foldable t, Num a) => t a -> a
minimum :: forall (t :: * -> *) a. (Foldable t, Ord a) => t a -> a
maximum :: forall (t :: * -> *) a. (Foldable t, Ord a) => t a -> a
length :: forall (t :: * -> *) a. Foldable t => t a -> Int
last :: forall a. [a] -> a
(Some hole fits suppressed; use -fmax-valid-hole-fits=N or -fno-max-valid-hole-fits)
|
14 | i = _sort_by_mod_desc
| ^^^^^^^^^^^^^^^^^
Main.hs:15:5: error:
• Found hole: _only_Data_List :: [Int] -> Int
Or perhaps ‘_only_Data_List’ is mis-spelled, or not in scope
• In the expression: _only_Data_List
In an equation for ‘g’: g = _only_Data_List
• Relevant bindings include
g :: [Int] -> Int (bound at Main.hs:15:1)
Valid hole fits include
head :: forall a. [a] -> a
last :: forall a. [a] -> a
|
15 | g = _only_Data_List
| ^^^^^^^^^^^^^^^
Main.hs:16:5: error:
• Found hole: _only_Prelude :: [Int] -> Int
Or perhaps ‘_only_Prelude’ is mis-spelled, or not in scope
• In the expression: _only_Prelude
In an equation for ‘h’: h = _only_Prelude
• Relevant bindings include
h :: [Int] -> Int (bound at Main.hs:16:1)
Valid hole fits include
length :: forall (t :: * -> *) a. Foldable t => t a -> Int
maximum :: forall (t :: * -> *) a. (Foldable t, Ord a) => t a -> a
minimum :: forall (t :: * -> *) a. (Foldable t, Ord a) => t a -> a
product :: forall (t :: * -> *) a. (Foldable t, Num a) => t a -> a
sum :: forall (t :: * -> *) a. (Foldable t, Num a) => t a -> a
|
16 | h = _only_Prelude
| ^^^^^^^^^^^^^
12.3.7. Управление повторной компиляцией
По умолчанию модули, скомпилированные с помощью плагинов, всегда перекомпилируются, даже если исходный файл не изменен. Эта наиболее консервативная опция выбрана из-за возможности плагинов выполнять произвольные действия ввода-вывода. Для управления поведением повторной компиляции можно изменить поле pluginRecompile в Plugin.
plugin :: Plugin
plugin = defaultPlugin {
installCoreToDos = install,
pluginRecompile = purePlugin
}
Просмотрев пример plugin , определенный выше, мы можем видеть, что он чистый. Это означает, что если у двух модулей одинаковый отпечаток, то плагин всегда будет возвращать один и тот же результат. Объявление плагина как чистого означает, что плагин никогда не приведет к перекомпиляции модуля.
В общем случае поле pluginRecompile имеет следующий тип:
pluginRecompile :: [CommandLineOption] -> IO PluginRecompile
Тип данных PluginRecompile — это перечисление, определяющее, как плагин должен влиять на повторную компиляцию.
data PluginRecompile = ForceRecompile | NoForceRecompile | MaybeRecompile Fingerprint
Плагин, который объявляет себя нечистым с помощью ForceRecompile, всегда будет запускать повторную компиляцию текущего модуля. NoForceRecompile используется для «чистых» плагинов, которые не нужно запускать повторно, если модуль не нужно перекомпилировать обычно. MaybeRecompile вычисляет Fingerprint , и если этот Fingerprint отличается от ранее вычисленного Fingerprint для плагина, то мы перекомпилируем модуль.
Таким образом, purePlugin определяется как функция, которая всегда возвращает NoForceRecompile.
purePlugin :: [CommandLineOption] -> IO PluginRecompile purePlugin _ = return NoForceRecompile
Пользователи могут использовать те же функции, что и GHC, для вычисления отпечатков. Модуль GHC.Fingerprint предоставляет полезные функции для построения отпечатков. Например, объединение fingerprintFingerprints и fingerprintString обеспечивает простой способ получения отпечатка аргументов плагина.
pluginFlagRecompile :: [CommandLineOption] -> IO PluginRecompile pluginFlagRecompile = return . MaybeRecompile . fingerprintFingerprints . map fingerprintString . sort
defaultPlugin определяет pluginRecompile как impurePlugin, что является наиболее консервативной и совместимой с предыдущими версиями опцией.
impurePlugin :: [CommandLineOption] -> IO PluginRecompile impurePlugin _ = return ForceRecompile
12.3.8. Плагины фронтального конца
Подключаемый модуль интерфейса позволяет добавлять новые основные режимы в GHC. Возможно, вы предпочтёте его традиционной программе, которая вызывает API GHC, так как GHC управляет множеством флагов разбора и административными бессмыслицами, которые сложно управлять вручную. Для загрузки подключаемого модуля интерфейса, экспортируемого Foo.FrontendPlugin, достаточно вызвать GHC с флагом --frontend ⟨module⟩ следующим образом:
$ ghc --frontend Foo.FrontendPlugin ...other options...
Подключаемые модули интерфейса, как и подключаемые модули компилятора, экспортируются зарегистрированными подключаемыми модулями. Однако, в отличие от модулей компилятора, подключаемые модули интерфейса — это модули, которые экспортируют по крайней мере один идентификатор frontendPlugin типа GhcPlugins.FrontendPlugin.
FrontendPlugin экспортирует поле frontend, которое является функцией [String] -> [(String, Maybe Phase)] -> Ghc (). Первый аргумент — список дополнительных флагов, передаваемых в интерфейс с -ffrontend-opt; второй аргумент — список аргументов, обычно исходные файлы и имена модулей, подлежащие компиляции (Phase указывает, был ли установлен флаг -x), и интерфейс просто выполняет некоторую операцию в монаде Ghc (которая, помимо прочего, имеет Session).
В качестве быстрого примера приведён подключаемый модуль интерфейса, который выводит аргументы, переданные ему, а затем завершается.
module DoNothing.FrontendPlugin (frontendPlugin) where
import GhcPlugins
frontendPlugin :: FrontendPlugin
frontendPlugin = defaultFrontendPlugin {
frontend = doNothing
}
doNothing :: [String] -> [(String, Maybe Phase)] -> Ghc ()
doNothing flags args = do
liftIO $ print flags
liftIO $ print args
При условии, что вы скомпилировали этот подключаемый модуль и зарегистрировали его в пакете, вы можете использовать его, указав --frontend DoNothing.FrontendPlugin в командной строке GHC.
12.3.9. Подключаемые модули DynFlags
Подключаемый модуль DynFlags позволяет изменять DynFlags , который GHC будет использовать при обработке заданного(ых) файла(ов). DynFlags — это запись, содержащая всевозможные данные конфигурации и командной строки, от уровня подробности до используемой целочисленной библиотеки, включая крючки компилятора, подключаемые модули и опции форматирования. Подключаемые модули DynFlags позволяют авторам подключаемых модулей обновлять любые из этих значений до того, как GHC начнёт выполнение реальной работы, фактически означая, что обновления, указанные подключаемым модулем, будут приняты во внимание и повлияют на поведение GHC.
Одним из мотивирующих примеров была возможность регистрации крючков компилятора из подключаемого модуля. Например, можно изменить способ выполнения кода Template Haskell. Это достижимо путём обновления поля hooks типа DynFlags, записывая наш пользовательский «мета-крючок» в нужном месте. Простой пример этого можно увидеть ниже:
module DynFlagsPlugin (plugin) where
import BasicTypes
import GhcPlugins
import GHC.Hs.Expr
import GHC.Hs.Extension
import GHC.Hs.Lit
import Hooks
import TcRnMonad
plugin :: Plugin
plugin = defaultPlugin { dynflagsPlugin = hooksP }
hooksP :: [CommandLineOption] -> DynFlags -> IO DynFlags
hooksP opts dflags = return $ dflags
{ hooks = (hooks dflags)
{ runMetaHook = Just (fakeRunMeta opts) }
}
-- This meta hook doesn't actually care running code in splices,
-- it just replaces any expression splice with the "0"
-- integer literal, and errors out on all other types of
-- meta requests.
fakeRunMeta :: [CommandLineOption] -> MetaHook TcM
fakeRunMeta opts (MetaE r) _ = do
liftIO . putStrLn $ "Options = " ++ show opts
pure $ r zero
where zero :: LHsExpr GhcPs
zero = L noSrcSpan $ HsLit NoExtField $
HsInt NoExtField (mkIntegralLit (0 :: Int))
fakeRunMeta _ _ _ = error "fakeRunMeta: unimplemented"
Этот простой подключаемый модуль перехватывает выполнение кода Template Haskell, заменяя любую вставку выражения на 0 (типа Int) и выдавая ошибку для любого другого типа вставки.
Поэтому, если мы запустим GHC против следующего кода с использованием подключаемого модуля из вышеприведённого примера:
{-# OPTIONS -fplugin=DynFlagsPlugin #-}
{-# LANGUAGE TemplateHaskell #-}
module Main where
main :: IO ()
main = print $( [|1|] )
Это не приведет к фактической оценке [|1|], а вместо этого заменит её на литерал 0 :: Int.
Так же, как и другие типы подключаемых модулей, вы можете написать подключаемые модули DynFlags, которые могут использовать опции, которые вы можете указать с помощью флага -fplugin-opt. В приведённом выше коде DynFlagsPlugin, указанные опции будут доступны в аргументе opts функции hooksP.
Наконец, поскольку эти обновления DynFlags происходят после загрузки подключаемых модулей, вы не можете из подключаемого модуля DynFlags регистрировать другие подключаемые модули, просто добавляя их в поле plugins типа DynFlags. Для достижения этого вам нужно будет загрузить их самостоятельно и сохранить результат в поле cachedPlugins типа DynFlags.
© 2002–2007 The University Court of the University of Glasgow. All rights reserved.
Licensed under the Glasgow Haskell Compiler License.
https://downloads.haskell.org/~ghc/8.10.2/docs/html/users_guide/extending_ghc.html