ghc-tags-plugin: A compiler plugin which generates tags file from GHC parsed syntax tree.

[ development, library, mpl ] [ Propose Tags ] [ Report a vulnerability ]

etags format. For a standalone `ghc-tags` command look for or ghc-tags or its fork (the fork is using ghc-tags-core, the same library which `ghc-tags-plugin` is using).


[Skip to Readme]

Flags

Automatic Flags
NameDescriptionDefault
gtp-checkDisabled

Use -f <flag> to enable a flag, or -f -<flag> to disable that flag. More info

Downloads

Versions [RSS] 0.1.0.0, 0.1.0.1, 0.1.0.2, 0.1.1.0, 0.1.2.0, 0.1.2.1, 0.1.3.0, 0.1.4.0, 0.1.5.0, 0.1.6.0, 0.2.0.0, 0.2.1.0, 0.2.2.0, 0.2.3.0, 0.2.4.0, 0.2.4.1, 0.3.0.0, 0.4.0.0, 0.4.0.1, 0.5.0.0, 0.5.1.0, 0.5.2.0, 0.5.3.0, 0.6.0.0, 0.6.0.1, 0.6.1.0, 0.6.1.1, 0.6.2.0, 1.0.0.0 (info)
Change log CHANGELOG.md
Dependencies base (>=4.12 && <4.23), bytestring (>=0.10 && <0.13), containers (>=0.6.0.1 && <0.9), deepseq (>=1.4 && <1.6), directory (>=1.3 && <1.4), filepath (>=1.4 && <1.6), ghc (>=8.4 && <10), ghc-tags-core (>=1.0.0.0 && <1.1), ghc-tags-pipes (>=0.2.0.1 && <0.3), ghc-tags-plugin, lukko (>=0.1 && <0.2), mtl (>=2.2 && <2.4), optparse-applicative (>=0.15.1 && <0.20), pipes (>=4.3 && <4.4), pipes-bytestring (>=2.1 && <2.2), pipes-safe (>=2.3 && <2.4), text (>=1.2 && <2.2) [details]
Tested with ghc ==9.6 || ==9.8 || ==9.10 || ==9.12
License MPL-2.0
Copyright (c) 2020-2026, Marcin Szamotulski
Author Marcin Szamotulski
Maintainer coot@coot.me
Uploaded by coot at 2026-10-29T19:46:21Z
Category Development
Home page https://github.com/coot/ghc-tags-plugin#readme
Bug tracker https://github.com/coot/ghc-tags-plugin/issues
Source repo head: git clone https://github.com/coot/ghc-tags-plugin
Distributions
Executables gtp-check
Downloads 5712 total (97 in the last 30 days)
Rating 2.0 (votes: 1) [estimated by Bayesian average]
Your Rating
  • λ
  • λ
  • λ
Status Docs available [build log]
Last success reported on 2026-10-29 [all 1 reports]

Readme for ghc-tags-plugin-1.0.0.0

[back to package description]

Ghc Tags Compiler Plugin

Haskell MPL-2.0 License Haskell CI

A library and a GHC compiler plugin which generates tags for each compiled module or component.

● Requirements

The plugin requires at least: ghc >= 9.6.

● Plugin options

Usage: <program> [-e|--etags] [--stream] [--debug] [--qualified] [-F|--no-filter] [file_path]
  write tags from ghc abstract syntax tree

Available options:
  -e,--etags               produce emacs etags file
  --stream                 stream existing tags (ctags only)
  file_path                tags file relative to cabal.project or stack.yaml:
                           default tags or TAGS (with --etags)
  --debug                  tag counts, file sizes, and update timings
  -q,--qualified           also generate names qualified by the defining module
  -F,--no-filter           retain both signatures and definitions

The tags path can be absolute or relative to the nearest directory containing cabal.project, cabal.project.local, or stack.yaml, searching upward from the compiled package. Packages using the same project root can share one tags file:

-fplugin-opt=Plugin.GhcTags:tags

A cabal.project.local file is a project-root marker even without a cabal.project beside it. A nearer marker takes precedence over configurations in parent directories. For example, with /repo/cabal.project and /repo/package/cabal.project.local, compiling that package writes to /repo/package/tags.

If STACK_YAML points to an existing configuration file, its directory is used instead. If no project configuration is found, the plugin prints a warning and resolves the path relative to the nearest *.cabal package file. When no package file is found either, it uses the compiler's working directory. Absolute output paths are used unchanged.

● Qualified names

To generate module-qualified names alongside ordinary names, enable:

-fplugin-opt=Plugin.GhcTags:--qualified

For example, a declaration lookup in Data.Map produces both lookup and Data.Map.lookup, pointing to the same declaration. This works with ctags, streaming ctags, etags, and declarations generated by Template Haskell. Module tags themselves are kept unchanged. Modules without a module header use Main.

The prefix identifies the module containing the declaration. It does not resolve import aliases or re-exports: import qualified Data.Map as M does not add M.lookup. Local bindings also receive module-prefixed names; those names identify their containing module, rather than a valid qualified import.

Only recompiled modules are updated. Rebuild all modules after enabling or disabling this option to update existing tags consistently.

● Signature and definition filtering

By default, a type signature replaces the corresponding function or pattern synonym definition in the tags file. A data constructor also takes precedence over a type constructor with the same name. To retain both tags, use:

-fplugin-opt=Plugin.GhcTags:--no-filter

The short form is -fplugin-opt=Plugin.GhcTags:-F. This applies to ctags, streaming ctags, etags, and Template Haskell signature/definition pairs. Tags remain sorted. Recompile all modules after changing this option to update existing tags consistently.

● Update timings

Enable timing diagnostics with:

-fplugin-opt=Plugin.GhcTags:--debug

Each completed update reports monotonic wall-clock durations in milliseconds:

  • lock-wait: opening and acquiring the tags-file lock.
  • read: reading the existing tags file.
  • parse: parsing its records.
  • generate: extracting and preparing tags for the current module or splice.
  • merge: combining old and new tags, including sorting where required.
  • write: formatting, writing, and flushing the replacement's contents.
  • total: elapsed time from before lock acquisition until after release, including temporary-file close/rename, bookkeeping, and other debug output.

Reports include the module, tags path, output mode, and whether the update came from parsing or a Template Haskell declaration splice. Streaming ctags measures each stage as records flow through it, excluding upstream work from downstream phase times. It does not load the existing file into memory for timing.

Debug mode forces lazy results within their measured phase and adds measurement overhead, especially when streaming. The phase times need not sum to total. With debug disabled, timing does not read the clock or force additional results.

● Emacs support

To produce etags file you will need to pass the following option

-fplugin-opt=Plugin.GhcTags:--etags

● Editor configuration

By default the generated tags file is put in the nearest directory containing cabal.project, cabal.project.local, or stack.yaml. To let Vim or Neovim find it while editing files in package subdirectories, enable searching parent directories:

:set tags+=./tags;

You can also pass an option to change where tags are written, see below.

● Configuration: Ghc / Cabal / Stack

● Cabal: quick start with GHCi

To generate tags in a REPL session without installing the plugin separately or editing any .cabal files, run:

cabal repl TARGET \
  --build-depends=ghc-tags-plugin \
  --repl-options=-fplugin=Plugin.GhcTags \
  --repl-options=-fplugin-opt=Plugin.GhcTags:tags

Replace TARGET with a component such as lib:my-package or exe:my-program. Cabal builds the plugin and makes it available to that REPL session. This command was tested with Cabal 3.16.1.0 and GHC 9.10.3 using a local plugin checkout and this repository's project configuration. See Cabal's --build-depends documentation.

The plugin's dependencies are solved together with the target project's dependencies, using that project's index-state, constraints, and allow-newer settings. You might need to modify allow-newer via --allow-newer documentation.

This configures the REPL session only. For normal builds and HLS, use the persistent configuration below.

● Cabal: persistent configuration

This setup enables the plugin through cabal.project.local, without changing any .cabal files.

First, install the plugin into Cabal's store:

cabal install --lib ghc-tags-plugin --package-env=ghc-tags

If installation encounters the GHC 9.14 dependency bounds described above, add --allow-newer=pipes-safe:base,ghc-tags-pipes:base to this command too.

The named ghc-tags environment keeps this installation separate from the default GHC package environment. The configuration below loads the plugin through its package database, so it does not require selecting that environment. See Cabal's package environment documentation.

Use the same GHC to install the plugin and build the target project. If the project selects a particular compiler, pass -w /absolute/path/to/ghc to the installation command as well. Reinstall the plugin when changing compilers.

With Cabal 3.16.1.0 or newer, run this in the target project to find its compiler and the corresponding store directory:

cabal path --compiler-info

Append /package.db to the reported compiler-store-path to obtain PACKAGE_DB. For example, the resulting path might be:

/home/alice/.cabal/store/ghc-9.10.3-1be3/package.db

Use the reported path rather than constructing it from the GHC version: compiler ABI suffixes and custom store locations can change the directory name. With older Cabal versions, locate the compiler's package.db in the Cabal store (the store location can be queried with cabal path --store-dir where available).

Add the following program-options stanza to cabal.project.local:

program-options
    ghc-options: -package-db=PACKAGE_DB
                 -plugin-package=ghc-tags-plugin
                 -fplugin=Plugin.GhcTags
                 -fplugin-opt=Plugin.GhcTags:tags

Replace PACKAGE_DB with the absolute path found above. Environment variables such as $HOME are not expanded in this file. If the path contains spaces, quote the entire argument, for example "-package-db=C:/Users/My Name/cabal/store/ghc-VERSION/package.db".

The stanza applies to all local packages. You can now run cabal build or cabal repl normally. When HLS uses a Cabal cradle, restart it after changing the configuration so it reloads the compiler options.

The default relative path shares a tags file across packages that resolve to the same project root. To choose an absolute destination, use:

-fplugin-opt=Plugin.GhcTags:/absolute/path/to/tags

Relative paths in the examples above are resolved from the project configuration directory. For example, ../tags writes one directory above the selected cabal.project, cabal.project.local, or stack.yaml.

To apply the plugin to dependencies as well, use a package * stanza instead of program-options. This can rebuild dependencies and applies the options to the plugin's own dependencies too; install the plugin before enabling it.

● GHC directly

After installing the plugin, pass its package database and enable it:

ghc -package-db=PACKAGE_DB \
    -plugin-package=ghc-tags-plugin \
    -fplugin=Plugin.GhcTags Main.hs

Replace PACKAGE_DB as described above. If multiple plugin versions are installed, select one with -plugin-package=ghc-tags-plugin-VERSION, or use -plugin-package-id=UNIT_ID to select a particular installed instance. Inspect the database with:

ghc-pkg --package-db=PACKAGE_DB list ghc-tags-plugin

See GHC's package documentation and compiler plugin documentation.

● Stack

Add ghc-tags-plugin to build-depends in the relevant components of your .cabal files, optionally behind a development flag. If the plugin is not in your snapshot, add a release compatible with the snapshot's GHC to extra-deps in stack.yaml.

Configure the target package in stack.yaml under ghc-options:

ghc-options:
  my-package: -plugin-package=ghc-tags-plugin
              -fplugin=Plugin.GhcTags
              -fplugin-opt=Plugin.GhcTags:tags

Replace my-package with your package name. This Stack setup uses a declared build dependency; the Cabal setups above avoid changing .cabal files.

● Ghcid

For automatic tag updates while editing, use the REPL quick start with ghcid:

ghcid --command="cabal repl TARGET --build-depends=ghc-tags-plugin --repl-options=-fplugin=Plugin.GhcTags --repl-options=-fplugin-opt=Plugin.GhcTags:tags"

If you have already enabled the plugin in cabal.project.local, use:

ghcid --command="cabal repl TARGET"

Replace TARGET with the component you want ghcid to load.

● Makefile

The Makefile contains some useful commands, e.g. install, uninstall or reinstall the package in a package.db (by default into cabal store). This is mostly for development, but it could be useful in other scenarios as well.

● Checking and comparing tags files

The optional gtp-check development tool reports tag counts, ctags metadata, parser diagnostics, sorting problems, repeated declaration locations, missing or unreadable source files, and line numbers outside their source files:

cabal run -fgtp-check gtp-check -- tags
cabal run -fgtp-check gtp-check -- --etags TAGS

Source paths are resolved relative to the directory containing each tags file, so the command can be run from another directory. Duplicate locations mean entries with the same source file, tag name and line number; kinds and columns are not used to distinguish them.

For a tags file copied elsewhere, pass --source-root /path/to/original/root to resolve source paths from its original directory. This override applies to both files when comparing snapshots.

To compare the declarations in two files:

cabal run -fgtp-check gtp-check -- tags --compare TAGS
cabal run -fgtp-check gtp-check -- --etags TAGS --compare tags

The format of the comparison file is detected automatically. Both files can also use the same format. Comparison uses canonical source paths, names and line numbers, and preserves duplicate counts. Differences are reported with concrete declaration locations. Tags without numeric line addresses are reported and excluded from line checks and comparison; a ctags line: field can supply the line number for an entry addressed by a search pattern.

Each diagnostic category shows up to ten examples by default. Use --max-examples N to change the limit, or --max-examples 0 to show all. Tags-file read or parse errors and unequal comparisons return a nonzero exit status. Other findings are warnings.

● Exceptions

If a GHC plugin throws an exception, GHC stops. This plugin wraps IOExceptions, to make it obvious that it filed rather than GHC. This might mean you misconfigured the plugin (by passing wrong options). The result might look like this:

ghc: panic! (the 'impossible' happened)
  (GHC version 8.6.5 for x86_64-unknown-linux):
        GhcTagsPluginIOException ../: openFile: inappropriate type (Is a directory)

● Tips

  • If GHC cannot find the installed plugin, check that installation and compilation use the same compiler and package database:

    ghc-pkg --package-db=PACKAGE_DB list ghc-tags-plugin
    

    After changing GHC versions, reinstall the plugin with the new compiler and update PACKAGE_DB in cabal.project.local.

  • The plugin is safe for concurrent compilation, i.e. setting jobs: $ncpus is safe. The plugin holds an exclusive (advisory) lock on a lock file. This will create synchronisation between threads / process which are using the same tags file.

  • Tag updates are written to a temporary file and renamed over the destination only after a successful write. Interrupted compilation (including editor cancellation) leaves the previous tags file intact. If tags were lost with an older plugin version, rebuild the affected modules with -fforce-recomp to regenerate them; an incremental build may skip those modules.

  • If you are working on a larger project, it might be better to not collect all tags in a single tags file, since at every compilation step one will need to parse a large tags file. Working with tag files of size 10000 tags (or ~1.5MB) is ok - though this will depend on the hardware.

  • If you're working on a project that is using safe-haskell, you will likely need to pass -fplugin-trustworthy ghc flag.

● Security implications of compiler plugins

Such plugins can:

  • run arbitrary IO;
  • modify abstract syntax tree in some way; a malicious plugin could change some security parameter in your code exposing a security hole.

This plugin only reads & writes to tags file (and updates a shared mutable state) as of IO, and does not modify/ the syntax tree.

● ghc-tags - standalone program

ghc-tags-fork is a fork of ghc-tags-hackage. Both provide a standalone ghc-tags command. Unlike the hackage version [ghc-tags-fork] is using the latest ghc-tags-core version.