0103c485

Created Started Finished

Author: Tom Sydney Kerckhove <syd@cs-syd.eu>

Committer: Tom Sydney Kerckhove <NorfairKing@users.noreply.github.com>

Give every rule a directory, and every fact its own module

Hopinion.Facts was one module holding fourteen types and re-exporting four
more, and a rule was one module with its fact in that pile and the reading of
the parse tree that produces it in the shared Extract. So nothing said what a
rule cost, nothing said which facts a module needed, and the two shared
modules were the two that grew with every rule added.

A rule is now a directory. Rule.hs is the check, and beside it goes whatever
only that rule needs: Fact.hs for a fact nothing else reads, Extract.hs for the
reading that produces it. Five of the six rules need neither and are the one
module, in a directory of their own so the shape is the same for all of them.
HsNoSemigroupOnText is the one with all three, and it is 178 lines that were
spread across three shared modules: 30 of rule, 44 of fact, 104 of reading. The
last number was invisible where it sat, and it is what M5 has to stop a later
expression rule from paying again.

What more than one rule reads is still under Hopinion.Facts, one type per
module, and no module re-exports another. A consumer names what it uses. This
costs import lines rather than saving them, and that is the trade: what each
line says is now true.

What no rule owns is in Hopinion.Extract.Ghc: a GHC span as a fact's span, the
declaration a span falls inside, and taking an expression apart far enough to
see what it is. A rule's own extraction asks that rather than importing
Hopinion.Extract, which would have made the two import each other.

Three dependencies were hiding behind the re-exports. Hopinion.Compiled and the
GenValidSpec rule both use the .hie reader, which reached them through Facts
re-exporting Hopinion.Hie, so neither file mentioned it. And validity-aeson was
a dependency of the library only because Facts imported Data.Validity.Aeson for
orphan instances no Validity instance here needs; the import alone satisfied
-Wunused-packages, so removing it took the dependency with it.

NameFact is in Facts.Occurrence rather than Facts.Name, where a name-shaped
thing would seem to belong. It carries a Span and a ScopeKey, and Facts.Place
already imports Facts.Name, so that would have closed a cycle. The name it
ended up with is the more honest one: it is an occurrence of a name.

Two things the split would otherwise have cost are in the plan instead. The
reason an enum-shaped fact is stored as its own spelling was one comment over
grouped instances and would have become three copies, which is the shape that
goes stale in two places and stays right in the third. And the doctrine that a
type belongs to whatever gives it meaning was stated only in the header of the
module this change deletes, which is the doctrine this change implements.

ModuleContext still carries a field per module-level fact, so it imports the
fact of any rule that brings one. That is the one edge left pointing from the
middle of the tool at a rule, and it is why the rule's directory holds the fact
rather than the fact holding the rule.

No behaviour change: no type, field, constructor or function was renamed, added
or removed.