Skip to content

Fix #12212: docs lost for re-exports across internal sub-libraries - #12226

Open
nikita-volkov wants to merge 6 commits into
haskell:masterfrom
nikita-volkov:fix-sublib-haddock
Open

Fix #12212: docs lost for re-exports across internal sub-libraries#12226
nikita-volkov wants to merge 6 commits into
haskell:masterfrom
nikita-volkov:fix-sublib-haddock

Conversation

@nikita-volkov

Copy link
Copy Markdown

cabal haddock --haddock-for-hackage silently dropped documentation for identifiers re-exported from internal sub-libraries, emitting a "Couldn't find .haddock for export" warning and showing 0% coverage for affected modules.

Root cause: during build, an internal sub-library's .haddock interface path is registered using ForDevelopment directory naming (<pkgname>), but haddock --haddock-for-hackage writes the interface under ForHackage naming (<pkgname>-<version>-docs). Since cabal invokes haddock separately per component, the main library's haddock phase still saw the stale build-step registration and couldn't find the interface file.

Fix:

  1. inplaceInstalledPackageInfo now takes a HaddockTarget parameter so the haddock phase registers the correct directory naming.
  2. haddockPackagePaths falls back to the alternate HaddockTarget directory naming when the registered .haddock path doesn't exist, handling stale in-memory indices carried over from the build phase.

Fixes #12212.


Reproduces the issue where documentation is silently dropped for
identifiers re-exported through more than one hop across internal
sub-libraries when running 'cabal haddock --haddock-for-hackage'.

The root cause is that the internal sub-library's .haddock interface
file is generated under the ForHackage directory naming
('<pkg>-<version>-docs'), but the inplace package registration records
the path using ForDevelopment naming (just '<pkg>'), so the interface
file cannot be found when the main library's haddocks are built.
…ries

When 'cabal haddock --haddock-for-hackage' was run on a package with
internal sub-libraries, documentation for identifiers re-exported through
those sub-libraries was silently dropped, with a 'Couldn't find .haddock
for export' warning.

Root cause: the internal sub-library's .haddock interface path was
registered during 'build' using ForDevelopment directory naming
(<pkgname>), but 'haddock --haddock-for-hackage' writes the interface to
ForHackage naming (<pkgname>-<version>-docs). Since cabal invokes haddock
separately per component, the main library's haddock phase still saw the
stale build-step registration and could not locate the interface file.

Fix:
1. inplaceInstalledPackageInfo now takes a HaddockTarget parameter so the
   haddock phase registers the correct directory naming.
2. haddockPackagePaths falls back to the alternate HaddockTarget directory
   naming when the registered .haddock path does not exist, handling stale
   in-memory indices from the build phase.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes missing Haddock documentation for re-exports from internal sub-libraries by aligning interface paths across Haddock targets.

Changes:

  • Makes in-place registration target-aware.
  • Adds fallback interface-path discovery.
  • Adds single-hop and multi-hop regression tests.

Reviewed changes

Copilot reviewed 17 out of 17 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
changelog.d/12212.md Documents the fix.
Cabal/src/Distribution/Simple/Register.hs Adds target-aware registration paths.
Cabal/src/Distribution/Simple/Haddock.hs Adds alternate interface-path fallback.
Cabal/src/Distribution/Simple/Build.hs Uses development paths during builds.
cabal-testsuite/PackageTests/NewHaddock/HaddockReexportSingleHop/src/sub/Sub.hs Defines documented dependency.
cabal-testsuite/PackageTests/NewHaddock/HaddockReexportSingleHop/src/lib/Mini.hs Re-exports dependency identifier.
cabal-testsuite/PackageTests/NewHaddock/HaddockReexportSingleHop/single-hop-reexport.cabal Configures single-hop fixture.
cabal-testsuite/PackageTests/NewHaddock/HaddockReexportSingleHop/cabal.test.hs Verifies retained documentation.
cabal-testsuite/PackageTests/NewHaddock/HaddockReexportSingleHop/cabal.project Defines test project.
cabal-testsuite/PackageTests/NewHaddock/HaddockReexportSingleHop/cabal.out Records expected output.
cabal-testsuite/PackageTests/NewHaddock/HaddockReexportMultiHop/src/Public.hs Defines public re-export.
cabal-testsuite/PackageTests/NewHaddock/HaddockReexportMultiHop/libs/internal/Internal/Leaf.hs Defines documented identifier.
cabal-testsuite/PackageTests/NewHaddock/HaddockReexportMultiHop/libs/internal/Internal/Aggregate.hs Adds intermediate re-export.
cabal-testsuite/PackageTests/NewHaddock/HaddockReexportMultiHop/haddock-reexport-multihop.cabal Configures multi-hop fixture.
cabal-testsuite/PackageTests/NewHaddock/HaddockReexportMultiHop/cabal.test.hs Verifies multi-hop documentation.
cabal-testsuite/PackageTests/NewHaddock/HaddockReexportMultiHop/cabal.project Defines test project.
cabal-testsuite/PackageTests/NewHaddock/HaddockReexportMultiHop/cabal.out Records expected output.
Suppressed comments (1)

Cabal/src/Distribution/Simple/Haddock.hs:1450

  • This replaces every path component equal to the package name, not just the generated Haddock directory. In the common layout where package foo is checked out under a directory named foo, the fallback rewrites both the checkout root and doc/html/foo, so it probes a nonexistent path and the original documentation-loss bug remains. Replace only the matching component nearest the interface file (or otherwise anchor the replacement to the Haddock output suffix).
          replaceDir d
            | d == devDir = hackageDir
            | d == hackageDir = devDir
            | otherwise = d
       in joinPath (map replaceDir (splitDirectories path))

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread Cabal/src/Distribution/Simple/Haddock.hs
…addock path

The interface-path fallback rewrites every path component matching the
package name, not just the haddock output directory. Haddock output always
lives under doc/html/<dir>, so a package named 'html' has the name twice in
its interface path and the fallback probes a path that does not exist.
The interface-path fallback swapped the HaddockTarget directory naming on
every path component matching the package name, so any ancestor carrying
the same name was rewritten too and the probed path did not exist. Rewrite
only the component nearest the interface file.
Under --haddock-for-hackage the HTML half of --read-interface is the
--html-location template that this mode always sets, so it stays correct
when the interface half is rewritten to the ForHackage naming by the
haddockPackagePaths fallback.
@nikita-volkov

Copy link
Copy Markdown
Author

Fixes pushed. I don't know what the Whitespace failure is, seems like an unrelated issue, isn't it?

The interface half of the '--read-interface' assertion checks a real
filesystem path, so a hardcoded '/'-separated literal only matched on
Unix. Build the expected fragment with System.FilePath instead.
@jappeace

Copy link
Copy Markdown
Collaborator

inplaceInstalledPackageInfo now takes a HaddockTarget parameter so the haddock phase registers the correct directory naming.|

Could you eloborate on this? We couldn't find the HaddockTarget when doing pair review in the cabal meeting.

@nikita-volkov

Copy link
Copy Markdown
Author

@jappeace jappeace left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Solid, thanks 🙏🏽

It scares me though how complicated it is to use haddock but that's a story for another day.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Documentation lost for identifiers re-exported through more than one hop across internal sub-libraries

4 participants